mirror of
https://github.com/itflow-org/itflow
synced 2026-08-05 15:17:17 +00:00
Response/resolution targets stamped at creation from per-client/priority assignments, business-hours due date math, warn/breach alert stages via cron/ticket_sla.php, ticket list coloring, per-ticket SLA override, admin page for plans/assignments/business hours. DB update 2.5.0. No behavior change unless SLAs are assigned. Bulk reply now only counts Public replies as first response.
275 lines
14 KiB
PHP
275 lines
14 KiB
PHP
<?php
|
|
|
|
require_once "includes/inc_all_admin.php";
|
|
|
|
// Current SLA / business hours settings
|
|
$sql_sla_settings = mysqli_query($mysqli, "SELECT config_business_days, config_business_hours_start, config_business_hours_end, config_sla_warning_percent, config_sla_notification_email FROM settings WHERE company_id = 1");
|
|
$row = mysqli_fetch_assoc($sql_sla_settings);
|
|
$config_business_days = explode(',', $row['config_business_days']);
|
|
$config_business_hours_start = substr($row['config_business_hours_start'], 0, 5);
|
|
$config_business_hours_end = substr($row['config_business_hours_end'], 0, 5);
|
|
$config_sla_warning_percent = intval($row['config_sla_warning_percent']);
|
|
$config_sla_notification_email = escapeHtml($row['config_sla_notification_email']);
|
|
|
|
// SLA plans (active first)
|
|
$sql_slas = mysqli_query($mysqli, "SELECT * FROM slas ORDER BY sla_archived_at IS NOT NULL, sla_name ASC");
|
|
|
|
// Active plans for the assignment dropdowns
|
|
$active_slas = [];
|
|
$sql_active_slas = mysqli_query($mysqli, "SELECT sla_id, sla_name FROM slas WHERE sla_archived_at IS NULL ORDER BY sla_name ASC");
|
|
while ($active_sla_row = mysqli_fetch_assoc($sql_active_slas)) {
|
|
$active_slas[intval($active_sla_row['sla_id'])] = $active_sla_row['sla_name'];
|
|
}
|
|
|
|
// Assignments, keyed for easy lookup: [client_id][priority] = sla_id
|
|
$assignments = [];
|
|
$assignment_client_names = [];
|
|
$sql_assignments = mysqli_query($mysqli, "SELECT sla_assignment_client_id, sla_assignment_priority, sla_assignment_sla_id, client_name FROM sla_assignments
|
|
LEFT JOIN clients ON sla_assignment_client_id = client_id
|
|
ORDER BY client_name ASC"
|
|
);
|
|
while ($assignment_row = mysqli_fetch_assoc($sql_assignments)) {
|
|
$assignment_client_id = intval($assignment_row['sla_assignment_client_id']);
|
|
$assignments[$assignment_client_id][$assignment_row['sla_assignment_priority']] = intval($assignment_row['sla_assignment_sla_id']);
|
|
if ($assignment_client_id) {
|
|
$assignment_client_names[$assignment_client_id] = $assignment_row['client_name'];
|
|
}
|
|
}
|
|
|
|
// Wording for an assignment cell: mapped plan, explicit none, or global default
|
|
function slaAssignmentWording($assignments, $active_slas, $client_id, $priority)
|
|
{
|
|
if (!isset($assignments[$client_id][$priority])) {
|
|
return "<span class='text-secondary'>Default</span>";
|
|
}
|
|
$sla_id = $assignments[$client_id][$priority];
|
|
if ($sla_id == 0) {
|
|
return "<span class='text-secondary'>None</span>";
|
|
}
|
|
if (isset($active_slas[$sla_id])) {
|
|
return escapeHtml($active_slas[$sla_id]);
|
|
}
|
|
return "<span class='text-secondary'>None (archived)</span>";
|
|
}
|
|
|
|
?>
|
|
|
|
<div class="card card-dark">
|
|
<div class="card-header py-2">
|
|
<h3 class="card-title mt-2"><i class="fas fa-fw fa-stopwatch mr-2"></i>SLAs</h3>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/sla/sla_add.php"><i class="fas fa-plus mr-2"></i>New SLA</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
|
|
<p class="text-muted">SLAs are optional. Tickets only get response/resolution targets when an assignment below matches their client and priority - with nothing assigned, nothing changes.</p>
|
|
|
|
<div class="table-responsive-sm">
|
|
<table class="table table-striped table-borderless table-hover">
|
|
<thead class="text-dark">
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Description</th>
|
|
<th>Response</th>
|
|
<th>Resolution</th>
|
|
<th>Status</th>
|
|
<th class="text-center">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php while ($sla_row = mysqli_fetch_assoc($sql_slas)) {
|
|
|
|
$sla_id = intval($sla_row['sla_id']);
|
|
$sla_name = escapeHtml($sla_row['sla_name']);
|
|
$sla_description = escapeHtml($sla_row['sla_description']);
|
|
$sla_response_minutes = intval($sla_row['sla_response_minutes']);
|
|
$sla_resolution_minutes = intval($sla_row['sla_resolution_minutes']);
|
|
$sla_archived_at = $sla_row['sla_archived_at'];
|
|
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<a href="#" class="ajax-modal" data-modal-url="modals/sla/sla_edit.php?id=<?= $sla_id ?>">
|
|
<?= $sla_name ?>
|
|
</a>
|
|
</td>
|
|
<td><?= $sla_description ?></td>
|
|
<td><?= $sla_response_minutes ?> min</td>
|
|
<td><?= $sla_resolution_minutes ? "$sla_resolution_minutes min" : "-" ?></td>
|
|
<td>
|
|
<?php if ($sla_archived_at) { ?>
|
|
<div class="text-secondary">Archived</div>
|
|
<?php } else { ?>
|
|
<div class="text-success text-bold">Active</div>
|
|
<?php } ?>
|
|
</td>
|
|
<td>
|
|
<div class="dropdown dropleft text-center">
|
|
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-h"></i>
|
|
</button>
|
|
<div class="dropdown-menu">
|
|
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/sla/sla_edit.php?id=<?= $sla_id ?>">
|
|
<i class="fas fa-fw fa-edit mr-2"></i>Edit
|
|
</a>
|
|
<div class="dropdown-divider"></div>
|
|
<?php if ($sla_archived_at) { ?>
|
|
<a class="dropdown-item" href="post.php?unarchive_sla=<?= $sla_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
|
|
<i class="fas fa-fw fa-undo mr-2"></i>Restore
|
|
</a>
|
|
<?php } else { ?>
|
|
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?archive_sla=<?= $sla_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
|
|
<i class="fas fa-fw fa-archive mr-2"></i>Archive
|
|
</a>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card card-dark">
|
|
<div class="card-header py-2">
|
|
<h3 class="card-title mt-2"><i class="fas fa-fw fa-random mr-2"></i>SLA Assignments</h3>
|
|
<div class="card-tools">
|
|
<button type="button" class="btn btn-primary ajax-modal" data-modal-url="modals/sla/sla_assignment_client.php"><i class="fas fa-plus mr-2"></i>Client Override</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
|
|
<form action="post.php" method="post" autocomplete="off">
|
|
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
|
|
|
<strong>Default (all clients)</strong>
|
|
<div class="form-row mt-2">
|
|
<?php foreach (['Low', 'Medium', 'High'] as $priority) {
|
|
$selected_sla_id = $assignments[0][$priority] ?? 0;
|
|
?>
|
|
<div class="form-group col-md-3">
|
|
<label><?= $priority ?></label>
|
|
<select class="form-control" name="global_sla_<?= strtolower($priority) ?>">
|
|
<option value="0">None</option>
|
|
<?php foreach ($active_slas as $active_sla_id => $active_sla_name) { ?>
|
|
<option value="<?= $active_sla_id ?>" <?php if ($selected_sla_id == $active_sla_id) { echo "selected"; } ?>><?= escapeHtml($active_sla_name) ?></option>
|
|
<?php } ?>
|
|
</select>
|
|
</div>
|
|
<?php } ?>
|
|
<div class="form-group col-md-3 d-flex align-items-end">
|
|
<button type="submit" name="save_sla_assignments" class="btn btn-primary"><i class="fas fa-check mr-2"></i>Save Defaults</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<hr>
|
|
|
|
<strong>Client overrides</strong>
|
|
<div class="table-responsive-sm mt-2">
|
|
<table class="table table-striped table-borderless table-hover">
|
|
<thead class="text-dark <?php if (empty($assignment_client_names)) { echo "d-none"; } ?>">
|
|
<tr>
|
|
<th>Client</th>
|
|
<th>Low</th>
|
|
<th>Medium</th>
|
|
<th>High</th>
|
|
<th class="text-center">Action</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php if (empty($assignment_client_names)) { ?>
|
|
<tr><td colspan="5" class="text-secondary">No client overrides - everyone follows the defaults above.</td></tr>
|
|
<?php } ?>
|
|
<?php foreach ($assignment_client_names as $override_client_id => $override_client_name) { ?>
|
|
<tr>
|
|
<td>
|
|
<a href="#" class="ajax-modal" data-modal-url="modals/sla/sla_assignment_client.php?client_id=<?= $override_client_id ?>">
|
|
<?= escapeHtml($override_client_name) ?>
|
|
</a>
|
|
</td>
|
|
<td><?= slaAssignmentWording($assignments, $active_slas, $override_client_id, 'Low') ?></td>
|
|
<td><?= slaAssignmentWording($assignments, $active_slas, $override_client_id, 'Medium') ?></td>
|
|
<td><?= slaAssignmentWording($assignments, $active_slas, $override_client_id, 'High') ?></td>
|
|
<td>
|
|
<div class="dropdown dropleft text-center">
|
|
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
|
|
<i class="fas fa-ellipsis-h"></i>
|
|
</button>
|
|
<div class="dropdown-menu">
|
|
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/sla/sla_assignment_client.php?client_id=<?= $override_client_id ?>">
|
|
<i class="fas fa-fw fa-edit mr-2"></i>Edit
|
|
</a>
|
|
<div class="dropdown-divider"></div>
|
|
<a class="dropdown-item text-danger text-bold confirm-link" href="post.php?delete_client_sla_assignments=<?= $override_client_id ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>">
|
|
<i class="fas fa-fw fa-trash mr-2"></i>Remove
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card card-dark">
|
|
<div class="card-header py-2">
|
|
<h3 class="card-title mt-2"><i class="fas fa-fw fa-business-time mr-2"></i>Business Hours & Notifications</h3>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
|
|
<form action="post.php" method="post" autocomplete="off">
|
|
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
|
|
|
<div class="form-group">
|
|
<label>Business days</label>
|
|
<div>
|
|
<?php foreach ([1 => 'Mon', 2 => 'Tue', 3 => 'Wed', 4 => 'Thu', 5 => 'Fri', 6 => 'Sat', 7 => 'Sun'] as $day_number => $day_name) { ?>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="business_days[]" value="<?= $day_number ?>" id="business_day_<?= $day_number ?>" <?php if (in_array($day_number, $config_business_days)) { echo "checked"; } ?>>
|
|
<label class="form-check-label" for="business_day_<?= $day_number ?>"><?= $day_name ?></label>
|
|
</div>
|
|
<?php } ?>
|
|
</div>
|
|
<small class="text-muted">SLA clocks only run during business hours on these days. Unchecking everything makes SLAs count 24x7.</small>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group col-md-3">
|
|
<label>Business hours start</label>
|
|
<input type="time" class="form-control" name="business_hours_start" value="<?= $config_business_hours_start ?>" required>
|
|
</div>
|
|
<div class="form-group col-md-3">
|
|
<label>Business hours end</label>
|
|
<input type="time" class="form-control" name="business_hours_end" value="<?= $config_business_hours_end ?>" required>
|
|
</div>
|
|
<div class="form-group col-md-3">
|
|
<label>Warn at % of target</label>
|
|
<input type="number" class="form-control" name="warning_percent" min="1" max="99" value="<?= $config_sla_warning_percent ?>" required>
|
|
</div>
|
|
<div class="form-group col-md-3">
|
|
<label>Notification email <small class="text-muted">(optional)</small></label>
|
|
<input type="email" class="form-control" name="notification_email" placeholder="dispatch@example.com" value="<?= $config_sla_notification_email ?>">
|
|
</div>
|
|
</div>
|
|
|
|
<small class="text-muted d-block mb-3">Warnings and breach alerts also notify the assigned agent in-app and by email. Requires cron/ticket_sla.php running every minute.</small>
|
|
|
|
<button type="submit" name="edit_sla_settings" class="btn btn-primary"><i class="fas fa-check mr-2"></i>Save Settings</button>
|
|
</form>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<?php require_once "../includes/footer.php";
|