diff --git a/public/index.php b/public/index.php index 93789b4..a0b078f 100644 --- a/public/index.php +++ b/public/index.php @@ -740,9 +740,13 @@ if ($route === 'reports') { $reportStmt->execute($params); $reportRows = $reportStmt->fetchAll(); $rows = array_map(static fn (array $row): array => [$row['client_name'], (int)$row['jobcards'], round((float)$row['hours'], 2)], $reportRows); - $reportClients = $user['role_name'] === 'Technician' ? db()->prepare('SELECT DISTINCT c.id, c.name FROM clients c JOIN jobcards j ON j.client_id = c.id JOIN jobcard_assignments ja ON ja.jobcard_id = j.id AND ja.user_id = :user ORDER BY c.name') : db()->query('SELECT id, name FROM clients ORDER BY name'); - if ($reportClients instanceof PDOStatement) $reportClients->execute(['user' => $user['id']]); - $reportClients = $reportClients->fetchAll(); + if ($user['role_name'] === 'Technician') { + $reportClientsStmt = db()->prepare('SELECT DISTINCT c.id, c.name FROM clients c JOIN jobcards j ON j.client_id = c.id JOIN jobcard_assignments ja ON ja.jobcard_id = j.id AND ja.user_id = :user ORDER BY c.name'); + $reportClientsStmt->execute(['user' => $user['id']]); + $reportClients = $reportClientsStmt->fetchAll(); + } else { + $reportClients = db()->query('SELECT id, name FROM clients ORDER BY name')->fetchAll(); + } $filterQuery = http_build_query(array_filter(['client_id' => $filters->clientId, 'date_from' => $filters->dateFrom, 'date_to' => $filters->dateTo, 'technician_id' => $filters->technicianId, 'status' => $filters->status, 'priority' => $filters->priority], static fn($value): bool => $value !== null && $value !== '')); if ($format === 'print') { header('Content-Type: text/html; charset=UTF-8'); header('Cache-Control: no-store'); echo (new \PrintReportRenderer())->render('Hours per client', ['Client', 'Jobcards', 'Hours'], $rows); exit; } if ($format === 'csv') { diff --git a/tests/JobcardSearchCloseReportTest.php b/tests/JobcardSearchCloseReportTest.php index 3454898..41c1e88 100644 --- a/tests/JobcardSearchCloseReportTest.php +++ b/tests/JobcardSearchCloseReportTest.php @@ -15,5 +15,7 @@ jobcard_feature_assert(str_contains($front, "j.status <> 'closed'"), 'Default Jo jobcard_feature_assert(str_contains($front, "j.status = 'closed'"), 'Closed filter must explicitly select closed records.'); jobcard_feature_assert(str_contains($front, 'All clients') && str_contains($front, '$reportClient'), 'Reports must use a client-name dropdown.'); jobcard_feature_assert(str_contains($front, 'format=xls&detail=1'), 'Reports must expose detailed XLS export.'); +jobcard_feature_assert(str_contains($front, "if (\$user['role_name'] === 'Technician') {") && str_contains($front, '$reportClientsStmt->execute'), 'Report client dropdown must bind parameters only on the technician prepared-query branch.'); +jobcard_feature_assert(!str_contains($front, 'if ($reportClients instanceof PDOStatement) $reportClients->execute'), 'Report client dropdown must not execute parameters against the non-technician query branch.'); foreach (['Work requested', 'Technician notes / Work performed', 'Hours notes'] as $heading) jobcard_feature_assert(str_contains($front, $heading), "Detailed report must include {$heading}."); printf("Jobcard search/close/report tests: 10 passed\n");