From 82ccbc4d5496fb89ca7fb592ecf34c1ff4ce0a39 Mon Sep 17 00:00:00 2001 From: Marco0300 Date: Wed, 2 Sep 2026 00:53:02 +0200 Subject: [PATCH] feat: show sla tracking only for active sla clients --- public/index.php | 6 ++++-- tests/JobcardSearchCloseReportTest.php | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/index.php b/public/index.php index cc84d95..ed4385c 100644 --- a/public/index.php +++ b/public/index.php @@ -244,6 +244,8 @@ if ($route === 'jobcard') { $jobcard = $jobcardStmt->fetch(); if (!$jobcard) { http_response_code(404); exit('Jobcard not found'); } if (!can_access_jobcard($jobcardId)) { http_response_code(404); exit('Jobcard not found'); } + $clientSlaStmt = db()->prepare('SELECT 1 FROM sla_agreements WHERE client_id = :client AND enabled = 1 AND CURDATE() BETWEEN COALESCE(start_date, \'1000-01-01\') AND COALESCE(end_date, \'9999-12-31\') LIMIT 1'); + $hasClientSla = (bool)$clientSlaStmt->execute(['client' => $jobcard['client_id']]) && (bool)$clientSlaStmt->fetchColumn(); $actionErrors = []; if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') { verify_csrf(); @@ -313,7 +315,7 @@ if ($route === 'jobcard') { $technicianId = $user['role_name'] === 'Technician' ? (int)$user['id'] : filter_var(scalar_input($_POST['technician_id'] ?? null), FILTER_VALIDATE_INT); $technicianCheck = db()->prepare("SELECT u.id FROM users u JOIN roles r ON r.id = u.role_id WHERE u.id = :id AND u.is_active = 1 AND r.name = 'Technician'"); $technicianCheck->execute(['id' => $technicianId]); - $timeInput = [...$_POST, 'jobcard_id' => $jobcardId, 'technician_id' => $technicianId, 'counts_toward_sla' => isset($_POST['counts_toward_sla']) ? '1' : '0']; + $timeInput = [...$_POST, 'jobcard_id' => $jobcardId, 'technician_id' => $technicianId, 'counts_toward_sla' => $hasClientSla && isset($_POST['counts_toward_sla']) ? '1' : '0']; $time = (new \App\Domain\Jobcard\TimeEntryCommand())->validate($timeInput); $actionErrors = array_values($time['errors']); if (!$technicianCheck->fetchColumn()) $actionErrors[] = 'Time must be attributed to an active technician.'; @@ -380,7 +382,7 @@ if ($route === 'jobcard') { if (can('jobcards.internal_notes')) echo '

Internal notes

'; echo '

Time entries

' . e(number_format($totalHours, 2)) . ' hours
'; foreach ($timeEntries as $entry) echo '
' . e($entry['technician_name']) . ' · ' . e($entry['work_date']) . ' · ' . e(number_format((float)$entry['hours'], 2)) . 'h
' . e((string)($entry['notes'] ?? '')) . '
' . (can('time_entries.record') ? 'Correct or void' : '') . '
'; - if (can('time_entries.record')) { echo '
'; if ($user['role_name'] !== 'Technician') { echo '
'; } echo '
'; } + if (can('time_entries.record')) { echo '
'; if ($user['role_name'] !== 'Technician') { echo '
'; } echo '
' . ($hasClientSla ? '
' : '') . '
'; } echo '

Status

Assigned technicians

'; diff --git a/tests/JobcardSearchCloseReportTest.php b/tests/JobcardSearchCloseReportTest.php index 3b37bd6..082bd2f 100644 --- a/tests/JobcardSearchCloseReportTest.php +++ b/tests/JobcardSearchCloseReportTest.php @@ -24,6 +24,8 @@ jobcard_feature_assert(str_contains($front, '$reportClientsStmt->execute([(int)$ foreach (['Technician notes / Work performed', 'totalReportHours'] as $heading) jobcard_feature_assert(str_contains($front, $heading), "Detailed report must include {$heading}."); jobcard_feature_assert(!str_contains($front, 'name=\"technician_notes\"') && !str_contains($front, 'Hours notes'), 'Jobcards must use time-entry technician notes only.'); jobcard_feature_assert(str_contains($front, 'remaining_hours') && str_contains($front, 'SLA hours remaining'), 'Client pages must show remaining SLA hours.'); +jobcard_feature_assert(str_contains($front, '$hasClientSla ?') && str_contains($front, "'counts_toward_sla' => \$hasClientSla &&"), 'SLA checkbox and submitted flag must be conditional on an active client SLA.'); + jobcard_feature_assert(str_contains($front, 'j.client_id = :client') && str_contains($front, 'te.counts_toward_sla = 1'), 'Client SLA usage must aggregate active-period SLA time entries regardless of jobcard status.'); jobcard_feature_assert(str_contains($front, "['TOTAL HOURS'") && str_contains($front, 'totalReportHours'), 'Detailed report downloads must include total hours.'); jobcard_feature_assert(str_contains($front, "if (\$route === 'settings')") && str_contains($front, "role_name'] ?? '') !== 'Administrator'"), 'Branding settings must be Administrator-only.');