mirror of
https://github.com/itflow-org/itflow
synced 2026-08-05 23:27:17 +00:00
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.
317 lines
22 KiB
PHP
317 lines
22 KiB
PHP
<?php
|
|
|
|
require_once "includes/inc_all_reports.php";
|
|
|
|
enforceUserPermission('module_support');
|
|
|
|
if (isset($_GET['year'])) {
|
|
$year = intval($_GET['year']);
|
|
} else {
|
|
$year = date('Y');
|
|
}
|
|
|
|
if (isset($_GET['month'])) {
|
|
$month = intval($_GET['month']);
|
|
} else {
|
|
$month = date('m');
|
|
}
|
|
|
|
$sql_ticket_years = mysqli_query($mysqli, "SELECT DISTINCT YEAR(ticket_created_at) AS ticket_year FROM tickets ORDER BY ticket_year DESC");
|
|
|
|
$sql_clients = mysqli_query($mysqli, "SELECT client_id, client_name FROM clients WHERE client_archived_at IS NULL ORDER BY client_name ASC");
|
|
|
|
?>
|
|
|
|
<div class="card card-dark">
|
|
<div class="card-header py-2">
|
|
<h3 class="card-title mt-2"><i class="fas fa-fw fa-life-ring mr-2"></i>Tickets By Client</h3>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-primary d-print-none" onclick="window.print();"><i class="fas fa-fw fa-print mr-2"></i>Print</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<form class="mb-3">
|
|
<select onchange="this.form.submit()" class="form-control" name="year">
|
|
<?php
|
|
while ($row = mysqli_fetch_assoc($sql_ticket_years)) {
|
|
$ticket_year = intval($row['ticket_year']); ?>
|
|
<option <?php if ($year == $ticket_year) { ?> selected <?php } ?> > <?= $ticket_year ?></option>
|
|
<?php } ?>
|
|
</select>
|
|
<select onchange="this.form.submit()" class="form-control" name="month">
|
|
<option <?php if ($month == 1) { echo 'selected'; } ?> value="1">January</option>
|
|
<option <?php if ($month == 2) { echo 'selected'; } ?> value="2">February</option>
|
|
<option <?php if ($month == 3) { echo 'selected'; } ?> value="3">March</option>
|
|
<option <?php if ($month == 4) { echo 'selected'; } ?> value="4">April</option>
|
|
<option <?php if ($month == 5) { echo 'selected'; } ?> value="5">May</option>
|
|
<option <?php if ($month == 6) { echo 'selected'; } ?> value="6">June</option>
|
|
<option <?php if ($month == 7) { echo 'selected'; } ?> value="7">July</option>
|
|
<option <?php if ($month == 8) { echo 'selected'; } ?> value="8">August</option>
|
|
<option <?php if ($month == 9) { echo 'selected'; } ?> value="9">September</option>
|
|
<option <?php if ($month == 10) { echo 'selected'; } ?> value="10">October</option>
|
|
<option <?php if ($month == 11) { echo 'selected'; } ?> value="11">November</option>
|
|
<option <?php if ($month == 12) { echo 'selected'; } ?> value="12">December</option>
|
|
</select>
|
|
</form>
|
|
|
|
<div class="card card-dark mb-3">
|
|
<div class="card-header">
|
|
<h3 class="card-title"><i class="fas fa-fw fa-chart-area mr-2"></i>Yearly (<?= $year ?>)</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive-sm">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Client</th>
|
|
<th class="text-right">Raised</th>
|
|
<th class="text-right">Priority: Low</th>
|
|
<th class="text-right">Priority: Med</th>
|
|
<th class="text-right">Priority: High</th>
|
|
<th class="text-right">Priority: Urgent</th>
|
|
<th class="text-right">Resolved</th>
|
|
<th class="text-right">Total Time worked <i>(H:M:S)</i></th>
|
|
<th class="text-right">Avg time to respond</th>
|
|
<th class="text-right">Avg time to resolve</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
while ($row = mysqli_fetch_assoc($sql_clients)) {
|
|
$client_id = intval($row['client_id']);
|
|
$client_name = escapeHtml($row['client_name']);
|
|
|
|
// Calculate total tickets raised in period
|
|
$sql_ticket_raised_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS ticket_raised_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id");
|
|
$row = mysqli_fetch_assoc($sql_ticket_raised_count);
|
|
$ticket_raised_count = intval($row['ticket_raised_count']);
|
|
|
|
// Calculate total tickets raised in period that are resolved
|
|
$sql_ticket_resolved_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS ticket_resolved_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id AND ticket_resolved_at IS NOT NULL");
|
|
$row = mysqli_fetch_assoc($sql_ticket_resolved_count);
|
|
$ticket_resolved_count = intval($row['ticket_resolved_count']);
|
|
|
|
// Breakdown tickets for each priority - Low
|
|
$sql_low_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS low_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id AND ticket_priority = 'Low'");
|
|
$row = mysqli_fetch_assoc($sql_low_ticket_count);
|
|
$low_ticket_count = intval($row['low_ticket_count']);
|
|
|
|
// Breakdown tickets for each priority - Low
|
|
$sql_med_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS med_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id AND ticket_priority = 'Medium'");
|
|
$row = mysqli_fetch_assoc($sql_med_ticket_count);
|
|
$med_ticket_count = intval($row['med_ticket_count']);
|
|
|
|
// Breakdown tickets for each priority - Low
|
|
$sql_high_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS high_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id AND ticket_priority = 'High'");
|
|
$row = mysqli_fetch_assoc($sql_high_ticket_count);
|
|
$high_ticket_count = intval($row['high_ticket_count']);
|
|
|
|
// Breakdown tickets for each priority - Urgent
|
|
$sql_urgent_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS urgent_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id AND ticket_priority = 'Urgent'");
|
|
$row = mysqli_fetch_assoc($sql_urgent_ticket_count);
|
|
$urgent_ticket_count = intval($row['urgent_ticket_count']);
|
|
|
|
// Used to calculate average time to respond to tickets that were raised in period specified
|
|
$sql_tickets_respond = mysqli_query($mysqli, "SELECT ticket_created_at, ticket_first_response_at FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id");
|
|
|
|
// Used to calculate average time to resolve tickets that were raised in period specified
|
|
$sql_tickets_resolved = mysqli_query($mysqli, "SELECT ticket_created_at, ticket_resolved_at FROM tickets WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id AND ticket_resolved_at IS NOT NULL");
|
|
|
|
// Calculate total time tracked towards tickets in the period
|
|
$sql_time = mysqli_query($mysqli, "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(ticket_reply_time_worked))) as total_time FROM ticket_replies LEFT JOIN tickets ON tickets.ticket_id = ticket_replies.ticket_reply_ticket_id WHERE YEAR(ticket_created_at) = $year AND ticket_client_id = $client_id AND ticket_reply_time_worked IS NOT NULL");
|
|
$row = mysqli_fetch_assoc($sql_time);
|
|
$ticket_total_time_worked = escapeHtml($row['total_time']);
|
|
|
|
if ($ticket_raised_count > 0 || $ticket_resolved_count > 0) {
|
|
|
|
// Calculate average time to respond
|
|
$avg_time_to_respond = '-';
|
|
$count = 0;
|
|
$total = 0;
|
|
while ($row = mysqli_fetch_assoc($sql_tickets_respond)) {
|
|
if (!empty($row['ticket_first_response_at'])) {
|
|
$openedTime = new DateTime($row['ticket_created_at']);
|
|
$respondTime = new DateTime($row['ticket_first_response_at']);
|
|
$total += ($respondTime->getTimestamp() - $openedTime->getTimestamp());
|
|
$count++;
|
|
}
|
|
}
|
|
if ($count > 0) {
|
|
$avg_time_to_respond = secondsToTime($total / $count); // Avoids DivisionByZeroError
|
|
}
|
|
|
|
// Calculate average time to solve
|
|
$avg_time_to_resolve = '-';
|
|
if ($ticket_resolved_count > 0) {
|
|
$count = 0;
|
|
$total = 0;
|
|
while ($row = mysqli_fetch_assoc($sql_tickets_resolved)) {
|
|
$openedTime = new DateTime($row['ticket_created_at']);
|
|
$resolvedTime = new DateTime($row['ticket_resolved_at']);
|
|
|
|
$total += ($resolvedTime->getTimestamp() - $openedTime->getTimestamp());
|
|
$count++;
|
|
}
|
|
$avg_time_to_resolve = secondsToTime($total / $count);
|
|
|
|
}
|
|
|
|
?>
|
|
|
|
<tr>
|
|
<td><?= $client_name ?></td>
|
|
<td class="text-right"><?= $ticket_raised_count ?></td>
|
|
<td class="text-right"><?= $low_ticket_count ?></td>
|
|
<td class="text-right"><?= $med_ticket_count ?></td>
|
|
<td class="text-right"><?= $high_ticket_count ?></td>
|
|
<td class="text-right"><?= $urgent_ticket_count ?></td>
|
|
<td class="text-right"><?= $ticket_resolved_count ?></td>
|
|
<td class="text-right"><?= $ticket_total_time_worked ?></td>
|
|
<td class="text-right"><?= $avg_time_to_respond ?></td>
|
|
<td class="text-right"><?= $avg_time_to_resolve ?></td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="card card-dark mb-3">
|
|
<div class="card-header">
|
|
<h3 class="card-title"><i class="fas fa-fw fa-chart-area mr-2"></i>Monthly (<?= date("F", mktime(1, 1, 1, $month, 1)) . ' ' . $year ?>)</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive-sm">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Client</th>
|
|
<th class="text-right">Raised</th>
|
|
<th class="text-right">Priority: Low</th>
|
|
<th class="text-right">Priority: Med</th>
|
|
<th class="text-right">Priority: High</th>
|
|
<th class="text-right">Priority: Urgent</th>
|
|
<th class="text-right">Resolved</th>
|
|
<th class="text-right">Total Time worked <i>(H:M:S)</i></th>
|
|
<th class="text-right">Avg time to respond</th>
|
|
<th class="text-right">Avg time to resolve</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
mysqli_data_seek($sql_clients, 0); // Reset
|
|
while ($row = mysqli_fetch_assoc($sql_clients)) {
|
|
$client_id = intval($row['client_id']);
|
|
$client_name = escapeHtml($row['client_name']);
|
|
|
|
// Calculate total tickets raised in period
|
|
$sql_ticket_raised_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS ticket_raised_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id");
|
|
$row = mysqli_fetch_assoc($sql_ticket_raised_count);
|
|
$ticket_raised_count = intval($row['ticket_raised_count']);
|
|
|
|
// Calculate total tickets raised in period that are resolved
|
|
$sql_ticket_resolved_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS ticket_resolved_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id AND ticket_resolved_at IS NOT NULL");
|
|
$row = mysqli_fetch_assoc($sql_ticket_resolved_count);
|
|
$ticket_resolved_count = intval($row['ticket_resolved_count']);
|
|
|
|
// Breakdown tickets for each priority - Low
|
|
$sql_low_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS low_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id AND ticket_priority = 'Low'");
|
|
$row = mysqli_fetch_assoc($sql_low_ticket_count);
|
|
$low_ticket_count = intval($row['low_ticket_count']);
|
|
|
|
// Breakdown tickets for each priority - Low
|
|
$sql_med_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS med_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id AND ticket_priority = 'Medium'");
|
|
$row = mysqli_fetch_assoc($sql_med_ticket_count);
|
|
$med_ticket_count = intval($row['med_ticket_count']);
|
|
|
|
// Breakdown tickets for each priority - Low
|
|
$sql_high_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS high_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id AND ticket_priority = 'High'");
|
|
$row = mysqli_fetch_assoc($sql_high_ticket_count);
|
|
$high_ticket_count = intval($row['high_ticket_count']);
|
|
|
|
// Breakdown tickets for each priority - Urgent
|
|
$sql_urgent_ticket_count = mysqli_query($mysqli, "SELECT COUNT(ticket_id) AS urgent_ticket_count FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id AND ticket_priority = 'Urgent'");
|
|
$row = mysqli_fetch_assoc($sql_urgent_ticket_count);
|
|
$urgent_ticket_count = intval($row['urgent_ticket_count']);
|
|
|
|
// Used to calculate average time to respond to tickets that were raised in period specified
|
|
$sql_tickets_respond = mysqli_query($mysqli, "SELECT ticket_created_at, ticket_first_response_at FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id");
|
|
|
|
// Used to calculate average time to resolve tickets that were raised in period specified
|
|
$sql_tickets_resolved = mysqli_query($mysqli, "SELECT ticket_created_at, ticket_resolved_at FROM tickets WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id AND ticket_resolved_at IS NOT NULL");
|
|
|
|
// Calculate total time tracked towards tickets in the period
|
|
$sql_time = mysqli_query($mysqli, "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(ticket_reply_time_worked))) as total_time FROM ticket_replies LEFT JOIN tickets ON tickets.ticket_id = ticket_replies.ticket_reply_ticket_id WHERE YEAR(ticket_created_at) = $year AND MONTH(ticket_created_at) = $month AND ticket_client_id = $client_id AND ticket_reply_time_worked IS NOT NULL");
|
|
$row = mysqli_fetch_assoc($sql_time);
|
|
$ticket_total_time_worked = escapeHtml($row['total_time']);
|
|
|
|
if ($ticket_raised_count > 0 || $ticket_resolved_count > 0) {
|
|
|
|
// Calculate average time to respond
|
|
$avg_time_to_respond = '-';
|
|
$count = 0;
|
|
$total = 0;
|
|
while ($row = mysqli_fetch_assoc($sql_tickets_respond)) {
|
|
if (!empty($row['ticket_first_response_at'])) {
|
|
$openedTime = new DateTime($row['ticket_created_at']);
|
|
$respondTime = new DateTime($row['ticket_first_response_at']);
|
|
$total += ($respondTime->getTimestamp() - $openedTime->getTimestamp());
|
|
$count++;
|
|
}
|
|
}
|
|
if ($count > 0) {
|
|
$avg_time_to_respond = secondsToTime($total / $count); // Avoids DivisionByZeroError
|
|
}
|
|
|
|
// Calculate average time to solve
|
|
$avg_time_to_resolve = '-';
|
|
if ($ticket_resolved_count > 0) {
|
|
$count = 0;
|
|
$total = 0;
|
|
while ($row = mysqli_fetch_assoc($sql_tickets_resolved)) {
|
|
$openedTime = new DateTime($row['ticket_created_at']);
|
|
$resolvedTime = new DateTime($row['ticket_resolved_at']);
|
|
|
|
$total += ($resolvedTime->getTimestamp() - $openedTime->getTimestamp());
|
|
$count++;
|
|
}
|
|
$avg_time_to_resolve = secondsToTime($total / $count);
|
|
}
|
|
|
|
?>
|
|
|
|
<tr>
|
|
<td><?= $client_name ?></td>
|
|
<td class="text-right"><?= $ticket_raised_count ?></td>
|
|
<td class="text-right"><?= $low_ticket_count ?></td>
|
|
<td class="text-right"><?= $med_ticket_count ?></td>
|
|
<td class="text-right"><?= $high_ticket_count ?></td>
|
|
<td class="text-right"><?= $urgent_ticket_count ?></td>
|
|
<td class="text-right"><?= $ticket_resolved_count ?></td>
|
|
<td class="text-right"><?= $ticket_total_time_worked ?></td>
|
|
<td class="text-right"><?= $avg_time_to_respond ?></td>
|
|
<td class="text-right"><?= $avg_time_to_resolve ?></td>
|
|
</tr>
|
|
<?php
|
|
}
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<?php
|
|
require_once "../../includes/footer.php";
|