Files
itflow/agent/reports/ticket_by_client.php
johnnyq cd7ebc0a5f Security: enforce per-user client restrictions on client, financial and bulk handlers and per-client reports
Agents restricted from a client could still edit, archive, restore or delete it, run bulk actions on it (edits, tags, archive, email, tickets) and add credits. Every selected client is now checked before anything is written.

Expense, trip and recurring expense edits now check the client the record already belongs to, not just the submitted one. Recurring expense delete, revenue edit/delete and task approval handlers gained the same check.

Per-client reports and the dashboard payment/ticket lists now hide clients the agent is denied. Company-wide totals are unchanged.
2026-10-08 16:17:46 -04:00

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 " . clientScopeSql('client_id') . " 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 me-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 me-2"></i>Print</button>
</div>
</div>
<div class="card-body">
<form class="mb-3">
<select onchange="this.form.submit()" class="form-select" 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-select" 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 me-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-end">Raised</th>
<th class="text-end">Priority: Low</th>
<th class="text-end">Priority: Med</th>
<th class="text-end">Priority: High</th>
<th class="text-end">Priority: Urgent</th>
<th class="text-end">Resolved</th>
<th class="text-end">Total Time worked <i>(H:M:S)</i></th>
<th class="text-end">Avg time to respond</th>
<th class="text-end">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-end"><?= $ticket_raised_count ?></td>
<td class="text-end"><?= $low_ticket_count ?></td>
<td class="text-end"><?= $med_ticket_count ?></td>
<td class="text-end"><?= $high_ticket_count ?></td>
<td class="text-end"><?= $urgent_ticket_count ?></td>
<td class="text-end"><?= $ticket_resolved_count ?></td>
<td class="text-end"><?= $ticket_total_time_worked ?></td>
<td class="text-end"><?= $avg_time_to_respond ?></td>
<td class="text-end"><?= $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 me-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-end">Raised</th>
<th class="text-end">Priority: Low</th>
<th class="text-end">Priority: Med</th>
<th class="text-end">Priority: High</th>
<th class="text-end">Priority: Urgent</th>
<th class="text-end">Resolved</th>
<th class="text-end">Total Time worked <i>(H:M:S)</i></th>
<th class="text-end">Avg time to respond</th>
<th class="text-end">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-end"><?= $ticket_raised_count ?></td>
<td class="text-end"><?= $low_ticket_count ?></td>
<td class="text-end"><?= $med_ticket_count ?></td>
<td class="text-end"><?= $high_ticket_count ?></td>
<td class="text-end"><?= $urgent_ticket_count ?></td>
<td class="text-end"><?= $ticket_resolved_count ?></td>
<td class="text-end"><?= $ticket_total_time_worked ?></td>
<td class="text-end"><?= $avg_time_to_respond ?></td>
<td class="text-end"><?= $avg_time_to_resolve ?></td>
</tr>
<?php
}
}
?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php
require_once "../../includes/footer.php";