mirror of
https://github.com/itflow-org/itflow
synced 2026-08-05 07:07:14 +00:00
Add SLA pausing, SLA reports, SLA filtering and kanban SLA state (phase 3)
Statuses can be flagged to pause the resolution clock; sla_history records the intervals a ticket's clock actually ran and the deadline is re-based on remaining budget when it resumes. Adds SLA Summary and SLA by Client reports, an SLA state filter on the ticket list, SLA colouring on kanban cards, and an Urgent column on the Tickets by Client report. DB update 2.5.1. Also fixes resolution SLA verdicts being skipped when resolving via kanban or the client portal.
This commit is contained in:
41
admin/database_updates/2.5.1.php
Normal file
41
admin/database_updates/2.5.1.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* ITFlow - Database update to version 2.5.1 (from 2.5.0)
|
||||
* Included by admin/database_updates.php - do not access directly
|
||||
*/
|
||||
|
||||
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
|
||||
|
||||
// SLA pausing. sla_history records the intervals during which a ticket's
|
||||
// resolution clock was actually running - one open row (ended_at NULL) per
|
||||
// running ticket. Statuses flagged below stop the clock, and the ticket's
|
||||
// resolution due date is recomputed from the remaining budget on resume.
|
||||
mysqli_query($mysqli, "CREATE TABLE `sla_history` (
|
||||
`sla_history_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`sla_history_started_at` datetime NOT NULL,
|
||||
`sla_history_ended_at` datetime DEFAULT NULL,
|
||||
`sla_history_minutes` int(11) DEFAULT NULL,
|
||||
`sla_history_ticket_id` int(11) NOT NULL,
|
||||
PRIMARY KEY (`sla_history_id`),
|
||||
KEY `sla_history_ticket_id` (`sla_history_ticket_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci");
|
||||
|
||||
// Which statuses pause the resolution clock. Nothing pauses by default, so
|
||||
// SLA behaviour is unchanged until an admin opts a status in.
|
||||
mysqli_query($mysqli, "ALTER TABLE `ticket_statuses`
|
||||
ADD COLUMN `ticket_status_pauses_sla` tinyint(1) NOT NULL DEFAULT 0 AFTER `ticket_status_active`");
|
||||
|
||||
// Backfill an open interval for every ticket already running a resolution
|
||||
// clock, anchored at creation. Without this their consumed time would read
|
||||
// as zero and the first pause/resume would hand back the full budget.
|
||||
mysqli_query($mysqli, "INSERT INTO sla_history (sla_history_started_at, sla_history_ticket_id)
|
||||
SELECT ticket_created_at, ticket_id
|
||||
FROM tickets
|
||||
LEFT JOIN slas ON ticket_sla_id = sla_id
|
||||
WHERE ticket_sla_id > 0
|
||||
AND sla_resolution_minutes > 0
|
||||
AND ticket_resolution_due_at IS NOT NULL
|
||||
AND ticket_resolved_at IS NULL
|
||||
AND ticket_closed_at IS NULL
|
||||
AND ticket_archived_at IS NULL");
|
||||
@@ -33,6 +33,20 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label>SLA</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
|
||||
</div>
|
||||
<select class="form-control select2" name="pauses_sla">
|
||||
<option value="0">Resolution clock keeps running</option>
|
||||
<option value="1">Pause the resolution clock</option>
|
||||
</select>
|
||||
</div>
|
||||
<small class="text-muted">Tickets sitting in a paused status never warn or breach on resolution. Time already spent is kept and the deadline moves out when the ticket comes back.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" name="add_ticket_status" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Create</button>
|
||||
|
||||
@@ -10,6 +10,7 @@ $ticket_status_name = escapeHtml($row['ticket_status_name']);
|
||||
$ticket_status_color = escapeHtml($row['ticket_status_color']);
|
||||
$ticket_status_order = intval($row['ticket_status_order']);
|
||||
$ticket_status_active = intval($row['ticket_status_active']);
|
||||
$ticket_status_pauses_sla = intval($row['ticket_status_pauses_sla']);
|
||||
|
||||
// Generate the HTML form content using output buffering.
|
||||
ob_start();
|
||||
@@ -70,6 +71,20 @@ ob_start();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>SLA</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text"><i class="fa fa-fw fa-stopwatch"></i></span>
|
||||
</div>
|
||||
<select class="form-control select2" name="pauses_sla">
|
||||
<option <?php if ($ticket_status_pauses_sla == 0) { echo "selected "; } ?>value="0">Resolution clock keeps running</option>
|
||||
<option <?php if ($ticket_status_pauses_sla == 1) { echo "selected "; } ?>value="1">Pause the resolution clock</option>
|
||||
</select>
|
||||
</div>
|
||||
<small class="text-muted">Tickets sitting in a paused status never warn or breach on resolution. Time already spent is kept and the deadline moves out when the ticket comes back.</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" name="edit_ticket_status" class="btn btn-primary text-bold"><i class="fas fa-check mr-2"></i>Save</button>
|
||||
|
||||
@@ -9,7 +9,9 @@ if (isset($_POST['add_ticket_status'])) {
|
||||
$name = escapeSql($_POST['name']);
|
||||
$color = escapeSql($_POST['color']);
|
||||
|
||||
mysqli_query($mysqli, "INSERT INTO ticket_statuses SET ticket_status_name = '$name', ticket_status_color = '$color'");
|
||||
$pauses_sla = intval($_POST['pauses_sla'] ?? 0);
|
||||
|
||||
mysqli_query($mysqli, "INSERT INTO ticket_statuses SET ticket_status_name = '$name', ticket_status_color = '$color', ticket_status_pauses_sla = $pauses_sla");
|
||||
|
||||
$ticket_status_id = mysqli_insert_id($mysqli);
|
||||
|
||||
@@ -31,7 +33,15 @@ if (isset($_POST['edit_ticket_status'])) {
|
||||
$order = intval($_POST['order']);
|
||||
$status = intval($_POST['status']);
|
||||
|
||||
mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_name = '$name', ticket_status_color = '$color', ticket_status_order = $order, ticket_status_active = $status WHERE ticket_status_id = $ticket_status_id");
|
||||
$pauses_sla = intval($_POST['pauses_sla'] ?? 0);
|
||||
|
||||
mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_name = '$name', ticket_status_color = '$color', ticket_status_order = $order, ticket_status_active = $status, ticket_status_pauses_sla = $pauses_sla WHERE ticket_status_id = $ticket_status_id");
|
||||
|
||||
// Tickets already sitting in this status need their clock reconciled
|
||||
$sql_status_tickets = mysqli_query($mysqli, "SELECT ticket_id FROM tickets WHERE ticket_status = $ticket_status_id AND ticket_closed_at IS NULL AND ticket_archived_at IS NULL");
|
||||
while ($status_ticket_row = mysqli_fetch_assoc($sql_status_tickets)) {
|
||||
syncTicketSlaClock($status_ticket_row['ticket_id']);
|
||||
}
|
||||
|
||||
logAudit("Ticket Status", "Edit", "$session_name edited custom ticket status $name", 0, $ticket_status_id);
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
||||
Status <?php if ($sort == 'ticket_status_active') { echo $order_icon; } ?>
|
||||
</a>
|
||||
</th>
|
||||
<th>SLA</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -72,6 +73,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
||||
$ticket_status_name = escapeHtml($row['ticket_status_name']);
|
||||
$ticket_status_color = escapeHtml($row['ticket_status_color']);
|
||||
$ticket_status_active = intval($row['ticket_status_active']);
|
||||
$ticket_status_pauses_sla = intval($row['ticket_status_pauses_sla']);
|
||||
if ($ticket_status_active) {
|
||||
$ticket_status_display = "<div class='text-success text-bold'>Active</div>";
|
||||
} else {
|
||||
@@ -92,6 +94,13 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
||||
<td>
|
||||
<span class='badge badge-pill text-light p-2' style="background-color: <?= $ticket_status_color ?>"><?= $ticket_status_name ?></span>
|
||||
<td><?= $ticket_status_display ?></td>
|
||||
<td>
|
||||
<?php if ($ticket_status_pauses_sla) { ?>
|
||||
<span class="text-warning"><i class="fas fa-fw fa-pause-circle mr-1"></i>Paused</span>
|
||||
<?php } else { ?>
|
||||
<span class="text-secondary">Running</span>
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="dropdown dropleft text-center">
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
|
||||
|
||||
Reference in New Issue
Block a user