diff --git a/functions/sla.php b/functions/sla.php index 9abf7a63..28c2aafc 100644 --- a/functions/sla.php +++ b/functions/sla.php @@ -462,7 +462,7 @@ function setTicketResolutionSlaMet($ticket_id) $ticket_id = intval($ticket_id); - $sql = mysqli_query($mysqli, "SELECT ticket_resolution_due_at, ticket_resolved_at FROM tickets WHERE ticket_id = $ticket_id LIMIT 1"); + $sql = mysqli_query($mysqli, "SELECT ticket_resolution_due_at, ticket_resolved_at, ticket_resolution_sla_met FROM tickets WHERE ticket_id = $ticket_id LIMIT 1"); if (!$sql || !mysqli_num_rows($sql)) { return; } @@ -472,6 +472,15 @@ function setTicketResolutionSlaMet($ticket_id) return; } + // A recorded miss is final at judge time. Reopening an exhausted ticket + // re-bases its deadline to the present, so without this a resolve in the + // same clock second would grade against that deadline and flip the miss + // to a met. Only an explicit re-stamp (applyTicketSla) may re-judge. + if (!is_null($row['ticket_resolution_sla_met']) && intval($row['ticket_resolution_sla_met']) === 0) { + syncTicketSlaClock($ticket_id); + return; + } + $ended_at = !empty($row['ticket_resolved_at']) ? strtotime($row['ticket_resolved_at']) : time(); $resolution_met = $ended_at <= strtotime($row['ticket_resolution_due_at']) ? 1 : 0; @@ -483,12 +492,31 @@ function setTicketResolutionSlaMet($ticket_id) // A reopened ticket goes back on the resolution clock. syncTicketSlaClock // reopens an interval and re-bases the deadline on the budget that is left, so // a ticket that was resolved with time to spare gets that remainder back. +// +// A missed verdict survives the reopen when the budget is already spent. +// Without this, re-basing would hand an exhausted ticket a zero-length fresh +// window, and resolving it again straight away would overwrite the recorded +// miss with a met - reopen must never be a way to launder a breach. function resetTicketResolutionSla($ticket_id) { global $mysqli; $ticket_id = intval($ticket_id); + $sql = mysqli_query($mysqli, "SELECT ticket_resolution_sla_met, sla_resolution_minutes FROM tickets LEFT JOIN slas ON ticket_sla_id = sla_id WHERE ticket_id = $ticket_id LIMIT 1"); + if ($sql && mysqli_num_rows($sql)) { + $row = mysqli_fetch_assoc($sql); + $resolution_minutes = intval($row['sla_resolution_minutes']); + $was_missed = !is_null($row['ticket_resolution_sla_met']) && intval($row['ticket_resolution_sla_met']) === 0; + + if ($was_missed && $resolution_minutes > 0 && getTicketSlaConsumedMinutes($ticket_id) >= $resolution_minutes) { + // Budget gone: keep the miss and the breach stage (so the cron does + // not re-alert), just restart the clock for the time-spent record + syncTicketSlaClock($ticket_id); + return; + } + } + mysqli_query($mysqli, "UPDATE tickets SET ticket_resolution_sla_met = NULL, ticket_resolution_sla_alert_stage = 0 WHERE ticket_id = $ticket_id"); syncTicketSlaClock($ticket_id);