99) { $warning_percent = 0; // Out of range = warnings disabled, breach alerts only } $sla_notification_email = trim(strval($sla_settings['notification_email'])); $now = time(); /* * Stop any clock that is running when it should not be. * * Every status change calls syncTicketSlaClock(), so this is normally a no-op. It * matters when the pause rules themselves change underneath tickets that are already * parked - the 2.7.0 update makes On Hold pause, and those tickets are still holding * an open interval that would otherwise keep counting as consumed budget. Doing it * here rather than in the migration keeps the business-hours maths in a process that * has the app timezone set, which scripts/update_cli.php does not. * * Only the stopping direction is reconciled. Starting a clock re-bases the resolution * deadline on the remaining budget, which is a real decision about a ticket and belongs * with the status change that caused it, not with a background sweep. */ $sql_running = mysqli_query($mysqli, "SELECT DISTINCT ticket_id FROM sla_history JOIN tickets ON sla_history_ticket_id = ticket_id LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id WHERE sla_history_ended_at IS NULL AND (COALESCE(ticket_status_pauses_sla, 0) = 1 OR ticket_resolved_at IS NOT NULL OR ticket_closed_at IS NOT NULL OR ticket_archived_at IS NOT NULL)" ); while ($running = mysqli_fetch_assoc($sql_running)) { syncTicketSlaClock(intval($running['ticket_id'])); } // --- Response SLA track --- // Open tickets awaiting a first response, not yet marked breached $sql_response = mysqli_query($mysqli, "SELECT ticket_id, ticket_prefix, ticket_number, ticket_subject, ticket_client_id, ticket_created_at, ticket_response_due_at, ticket_response_sla_alert_stage, sla_response_minutes, user_email, user_name FROM tickets LEFT JOIN slas ON ticket_sla_id = sla_id LEFT JOIN users ON ticket_assigned_to = user_id WHERE ticket_sla_id > 0 AND ticket_response_due_at IS NOT NULL AND ticket_first_response_at IS NULL AND ticket_resolved_at IS NULL AND ticket_closed_at IS NULL AND ticket_archived_at IS NULL AND ticket_response_sla_alert_stage < 2" ); while ($ticket = mysqli_fetch_assoc($sql_response)) { $ticket_id = intval($ticket['ticket_id']); $stage = intval($ticket['ticket_response_sla_alert_stage']); $due = strtotime($ticket['ticket_response_due_at']); if ($now >= $due) { // Breached without a response - the verdict is final, record the miss mysqli_query($mysqli, "UPDATE tickets SET ticket_response_sla_alert_stage = 2, ticket_response_sla_met = 0 WHERE ticket_id = $ticket_id"); ticketSlaAlert($ticket_id, 'response', true, $ticket['ticket_response_due_at'], $sla_notification_email); } elseif ($stage < 1 && $warning_percent) { $warn_at = strtotime(addBusinessMinutes($ticket['ticket_created_at'], floor(intval($ticket['sla_response_minutes']) * $warning_percent / 100))); if ($now >= $warn_at) { mysqli_query($mysqli, "UPDATE tickets SET ticket_response_sla_alert_stage = 1 WHERE ticket_id = $ticket_id"); ticketSlaAlert($ticket_id, 'response', false, $ticket['ticket_response_due_at'], $sla_notification_email); } } } // --- Resolution SLA track --- // Open tickets with a resolution target, not yet resolved or marked breached $sql_resolution = mysqli_query($mysqli, "SELECT ticket_id, ticket_prefix, ticket_number, ticket_subject, ticket_client_id, ticket_created_at, ticket_resolution_due_at, ticket_resolution_sla_alert_stage, sla_resolution_minutes, user_email, user_name FROM tickets LEFT JOIN slas ON ticket_sla_id = sla_id LEFT JOIN users ON ticket_assigned_to = user_id LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id WHERE ticket_sla_id > 0 AND COALESCE(ticket_status_pauses_sla, 0) = 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 AND ticket_resolution_sla_alert_stage < 2" ); while ($ticket = mysqli_fetch_assoc($sql_resolution)) { $ticket_id = intval($ticket['ticket_id']); $stage = intval($ticket['ticket_resolution_sla_alert_stage']); $due = strtotime($ticket['ticket_resolution_due_at']); if ($now >= $due) { mysqli_query($mysqli, "UPDATE tickets SET ticket_resolution_sla_alert_stage = 2, ticket_resolution_sla_met = 0 WHERE ticket_id = $ticket_id"); ticketSlaAlert($ticket_id, 'resolution', true, $ticket['ticket_resolution_due_at'], $sla_notification_email); } elseif ($stage < 1 && $warning_percent) { // Measured against consumed clock time, so paused spells don't warn early $warn_after_minutes = floor(intval($ticket['sla_resolution_minutes']) * $warning_percent / 100); if (getTicketSlaConsumedMinutes($ticket_id) >= $warn_after_minutes) { mysqli_query($mysqli, "UPDATE tickets SET ticket_resolution_sla_alert_stage = 1 WHERE ticket_id = $ticket_id"); ticketSlaAlert($ticket_id, 'resolution', false, $ticket['ticket_resolution_due_at'], $sla_notification_email); } } }