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.
200 lines
7.3 KiB
PHP
200 lines
7.3 KiB
PHP
<link rel="stylesheet" href="css/ticket_kanban.css">
|
|
|
|
<?php
|
|
isset($mysqli) || die("Direct file access is not allowed");
|
|
|
|
// Fetch statuses
|
|
$status_sql = mysqli_query($mysqli, "SELECT * FROM ticket_statuses WHERE ticket_status_active = 1 AND ticket_status_id != 5 ORDER BY ticket_status_order");
|
|
|
|
$statuses = array();
|
|
|
|
while ($row = mysqli_fetch_assoc($status_sql)) {
|
|
|
|
$status_id = intval($row['ticket_status_id']);
|
|
$status_name = escapeHtml($row['ticket_status_name']);
|
|
$kanban_order = intval($row['ticket_status_order']);
|
|
|
|
$statuses[$status_id] = array(
|
|
'id' => $status_id,
|
|
'name' => $status_name,
|
|
'order' => $kanban_order,
|
|
'tickets' => array()
|
|
);
|
|
}
|
|
|
|
$ordering_snippet = "ORDER BY
|
|
CASE
|
|
WHEN ticket_priority = 'Urgent' THEN 0
|
|
WHEN ticket_priority = 'High' THEN 1
|
|
WHEN ticket_priority = 'Medium' THEN 2
|
|
WHEN ticket_priority = 'Low' THEN 3
|
|
ELSE 4
|
|
END,
|
|
ticket_id DESC";
|
|
|
|
if ($config_ticket_ordering == 1) {
|
|
$ordering_snippet = "ORDER BY ticket_order ASC";
|
|
}
|
|
|
|
// Fetch tickets
|
|
$sql = mysqli_query(
|
|
$mysqli,
|
|
"SELECT SQL_CALC_FOUND_ROWS * FROM tickets
|
|
LEFT JOIN clients ON ticket_client_id = client_id
|
|
LEFT JOIN contacts ON ticket_contact_id = contact_id
|
|
LEFT JOIN users ON ticket_assigned_to = user_id
|
|
LEFT JOIN assets ON ticket_asset_id = asset_id
|
|
LEFT JOIN locations ON ticket_location_id = location_id
|
|
LEFT JOIN vendors ON ticket_vendor_id = vendor_id
|
|
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
|
|
LEFT JOIN categories ON ticket_category = category_id
|
|
WHERE $ticket_status_snippet $ticket_assigned_query
|
|
$category_query
|
|
$client_query
|
|
AND DATE(ticket_created_at) BETWEEN '$dtf' AND '$dtt'
|
|
AND (
|
|
CONCAT(ticket_prefix,ticket_number) LIKE '%$q%' OR
|
|
client_name LIKE '%$q%' OR
|
|
ticket_subject LIKE '%$q%' OR
|
|
ticket_status_name LIKE '%$q%' OR
|
|
ticket_priority LIKE '%$q%' OR
|
|
user_name LIKE '%$q%' OR
|
|
contact_name LIKE '%$q%' OR
|
|
asset_name LIKE '%$q%' OR
|
|
vendor_name LIKE '%$q%' OR
|
|
ticket_vendor_ticket_number LIKE '%$q%'
|
|
)
|
|
$ticket_project_snippet
|
|
$access_permission_query_overide
|
|
$ordering_snippet"
|
|
);
|
|
|
|
while ($row = mysqli_fetch_assoc($sql)) {
|
|
|
|
$status_id = $row['ticket_status_id'];
|
|
|
|
foreach ($row as $key => $value) {
|
|
if (is_string($value)) {
|
|
$row[$key] = escapeHtml($value);
|
|
}
|
|
}
|
|
|
|
if (isset($statuses[$status_id])) {
|
|
$statuses[$status_id]['tickets'][] = $row;
|
|
}
|
|
}
|
|
|
|
// Convert associative array to indexed array
|
|
$kanban = array_values($statuses);
|
|
?>
|
|
|
|
<div class="container-fluid" id="kanban-board">
|
|
|
|
<?php foreach ($kanban as $column) { ?>
|
|
|
|
<div class="kanban-column card card-dark" data-status-id="<?= $column['id'] ?>">
|
|
<h6 class="panel-title"><?= $column['name'] ?></h6>
|
|
|
|
<div class="kanban-status" data-column-name="<?= $column['name'] ?>" data-status-id="<?= $column['id'] ?>">
|
|
|
|
<?php foreach ($column['tickets'] as $item) {
|
|
|
|
if ($item['ticket_priority'] == "Urgent") {
|
|
$ticket_priority_color = "dark";
|
|
} elseif ($item['ticket_priority'] == "High") {
|
|
$ticket_priority_color = "danger";
|
|
} elseif ($item['ticket_priority'] == "Medium") {
|
|
$ticket_priority_color = "warning";
|
|
} else {
|
|
$ticket_priority_color = "info";
|
|
}
|
|
|
|
// SLA state, same stages the ticket list colours on. A paused
|
|
// ticket drops its at-risk flag but keeps a recorded breach.
|
|
$ticket_sla_alert_stage = max(intval($item['ticket_response_sla_alert_stage']), intval($item['ticket_resolution_sla_alert_stage']));
|
|
if (intval($item['ticket_status_pauses_sla']) && $ticket_sla_alert_stage < 2) {
|
|
$ticket_sla_alert_stage = 0;
|
|
}
|
|
$ticket_sla_class = '';
|
|
if ($ticket_sla_alert_stage == 2) {
|
|
$ticket_sla_class = ' border-danger';
|
|
} elseif ($ticket_sla_alert_stage == 1) {
|
|
$ticket_sla_class = ' border-warning';
|
|
}
|
|
?>
|
|
|
|
<div class="task grab-cursor<?= $ticket_sla_class ?>"
|
|
data-ticket-id="<?= $item['ticket_id'] ?>"
|
|
data-ticket-status-id="<?= $item['ticket_status_id'] ?>"
|
|
ondblclick="window.location.href='ticket.php?ticket_id=<?= $item['ticket_id'] ?>'">
|
|
|
|
<span class="badge badge-<?= $ticket_priority_color ?>">
|
|
<?= $item['ticket_priority'] ?>
|
|
</span>
|
|
|
|
<?php if ($ticket_sla_alert_stage == 2) { ?>
|
|
<span class="badge badge-danger" title="SLA breached"><i class="fas fa-fw fa-stopwatch"></i></span>
|
|
<?php } elseif ($ticket_sla_alert_stage == 1) { ?>
|
|
<span class="badge badge-warning" title="SLA at risk"><i class="fas fa-fw fa-stopwatch"></i></span>
|
|
<?php } ?>
|
|
|
|
<span class="badge badge-secondary">
|
|
<?= $item['category_name'] ?>
|
|
</span>
|
|
|
|
<div class="btn btn-light drag-handle-class" style="display:none;">
|
|
<i class="drag-handle-class fas fa-bars"></i>
|
|
</div>
|
|
|
|
<br>
|
|
|
|
<b>
|
|
<?php
|
|
if (!$client_url) {
|
|
if ($item['contact_name'] != "") {
|
|
echo $item['client_name'] . ' - ' . $item['contact_name'];
|
|
} else {
|
|
echo $item['client_name'];
|
|
}
|
|
} else {
|
|
echo $item['contact_name'];
|
|
}
|
|
?>
|
|
</b>
|
|
|
|
<br>
|
|
|
|
<?php if ($item['asset_name'] != "") { ?>
|
|
<i class="fa fa-fw fa-desktop text-secondary mr-2"></i><?= $item['asset_name'] ?><br>
|
|
<?php } ?>
|
|
|
|
<i class="fa fa-fw fa-life-ring text-secondary mr-2"></i><?= $item['ticket_subject'] ?><br>
|
|
|
|
<i class="fas fa-fw fa-user mr-2 text-secondary"></i><?= $item['user_name'] ?><br>
|
|
|
|
<?php if ($item['ticket_schedule'] != "") { ?>
|
|
<i class="fa fa-fw fa-calendar-check text-secondary mr-2"></i>
|
|
<span class="badge badge-warning"><?= $item['ticket_schedule'] ?></span>
|
|
<?php } ?>
|
|
|
|
</div>
|
|
|
|
<?php } ?>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<?php } ?>
|
|
|
|
</div>
|
|
|
|
<?php
|
|
echo "<script>";
|
|
echo "const CONFIG_TICKET_MOVING_COLUMNS = " . json_encode($config_ticket_moving_columns) . ";";
|
|
echo "const CONFIG_TICKET_ORDERING = " . json_encode($config_ticket_ordering) . ";";
|
|
echo "</script>";
|
|
?>
|
|
|
|
<script src="../libs/SortableJS/Sortable.min.js"></script>
|
|
<script src="js/tickets_kanban.js"></script>
|