0 AND " . reportRangeSql('ticket_created_at', $period); $clients = []; $sql = mysqli_query($mysqli, "SELECT client_id, client_name, client_abbreviation, " . REPORT_SLA_COLUMNS . " FROM tickets JOIN clients ON ticket_client_id = client_id WHERE $filter GROUP BY client_id, client_name, client_abbreviation"); while ($row = mysqli_fetch_assoc($sql)) { $clients[intval($row['client_id'])] = ['client_id' => intval($row['client_id']), 'client' => $row['client_name'], 'abbr' => $row['client_abbreviation'], 'response' => null, 'resolve_clock' => null, 'breached_open' => 0] + reportSlaFigures($row); } $responses = []; $sql = mysqli_query($mysqli, "SELECT ticket_client_id AS client_id, TIMESTAMPDIFF(SECOND, ticket_created_at, ticket_first_response_at) AS s FROM tickets WHERE $filter AND ticket_first_response_at IS NOT NULL"); while ($row = mysqli_fetch_assoc($sql)) { $responses[intval($row['client_id'])][] = floatval($row['s']); } // The SLA clock spent on resolved tickets - logged business minutes, or for a // ticket with none logged (a response-only plan, or one resolved before // pausing existed) the business time between being raised and resolved $clock = []; $sql = mysqli_query($mysqli, "SELECT ticket_client_id AS client_id, ticket_created_at, ticket_resolved_at, SUM(sla_history_minutes) AS logged FROM tickets LEFT JOIN sla_history ON sla_history_ticket_id = ticket_id WHERE $filter AND ticket_resolved_at IS NOT NULL GROUP BY ticket_id, ticket_client_id, ticket_created_at, ticket_resolved_at"); while ($row = mysqli_fetch_assoc($sql)) { $minutes = $row['logged'] === null ? businessMinutesBetween($row['ticket_created_at'], $row['ticket_resolved_at']) : intval($row['logged']); $clock[intval($row['client_id'])][] = $minutes * 60; } $sql = mysqli_query($mysqli, "SELECT ticket_client_id AS client_id, COUNT(ticket_id) AS n FROM tickets WHERE " . reportTicketSql(0, $agent_id) . " AND " . TICKET_OPEN_SQL . " AND " . dashboardSlaBreachedSql() . " GROUP BY ticket_client_id"); while ($row = mysqli_fetch_assoc($sql)) { if (isset($clients[intval($row['client_id'])])) { $clients[intval($row['client_id'])]['breached_open'] = intval($row['n']); } } foreach ($clients as $id => &$row) { $row['response'] = reportMedian($responses[$id] ?? []); $row['resolve_clock'] = !empty($clock[$id]) ? array_sum($clock[$id]) / count($clock[$id]) : null; $row['worst'] = min($row['response_percent'] ?? 101, $row['resolution_percent'] ?? 101); } unset($row); uasort($clients, function ($a, $b) { return $a['worst'] <=> $b['worst'] ?: $b['tickets'] <=> $a['tickets']; }); $clients = array_values($clients); // tickets.php's assigned= filter, so a click keeps to the same agent $agent_link = $agent_id ? "&assigned=$agent_id" : ''; $columns = [ 'client' => ['label' => 'Client', 'html' => function ($row) { return reportClientCell($row['client_id'], $row['client'], $row['abbr'], 'tickets.php'); }], 'tickets' => ['label' => 'Tickets', 'type' => 'count'], // The late counts sit beside their share on screen; the export keeps them apart 'response_missed' => ['label' => 'Replied late', 'type' => 'count', 'screen' => false], 'response_percent' => ['label' => 'Response', 'type' => 'percent', 'title' => 'First replies in time, of those judged', 'html' => function ($row) { return reportRateHtml($row['response_percent']) . reportLateHtml($row['response_missed']); }], 'resolution_missed' => ['label' => 'Resolved late', 'type' => 'count', 'screen' => false], 'resolution_percent' => ['label' => 'Resolution', 'type' => 'percent', 'title' => 'Resolved in time, of those judged', 'html' => function ($row) { return reportRateHtml($row['resolution_percent']) . reportLateHtml($row['resolution_missed']); }], 'response' => ['label' => 'First reply', 'type' => 'duration', 'title' => 'Typical (middle) time to the first reply, on the clock'], 'resolve_clock' => ['label' => 'Clock to resolve', 'type' => 'duration', 'title' => 'Average business time with the SLA clock running, to resolve'], 'breached_open' => ['label' => 'Breached now', 'type' => 'count', 'csv_label' => 'Breached and still open', 'title' => 'Open tickets that have missed a target, whenever they were raised', 'html' => function ($row) use ($agent_link) { return $row['breached_open'] ? '' . $row['breached_open'] . '' : '–'; }], ]; $overall = reportSlaCompliance($filter); $totals = ['client' => 'Total', 'tickets' => $overall['tickets'], 'response_missed' => $overall['response_missed'], 'response_percent' => $overall['response_percent'], 'resolution_missed' => $overall['resolution_missed'], 'resolution_percent' => $overall['resolution_percent'], 'breached_open' => array_sum(array_column($clients, 'breached_open'))]; if (reportExporting()) { reportCsv('SLA by Client ' . $period['start'] . ' to ' . $period['end'], $columns, $clients, $totals); } require_once "includes/inc_report_layout.php"; $below = array_filter($clients, function ($c) { return $c['worst'] < 80; }); $chart_rows = array_slice($clients, 0, 15); ?>