'2026-09-01', 'date_to' => '2026-09-30', 'client_id' => 7, 'technician_id' => 4, 'status' => 'open', 'priority' => 'high', 'sla' => 'warning', ]); $entries = [ ['client_id' => 7, 'client_name' => 'Acme', 'technician_id' => 4, 'technician_name' => 'Tess', 'work_date' => '2026-09-10', 'status' => 'open', 'priority' => 'high', 'sla_status' => 'warning', 'hours' => 1.25], ['client_id' => 7, 'client_name' => 'Acme', 'technician_id' => 4, 'technician_name' => 'Tess', 'work_date' => '2026-09-11', 'status' => 'open', 'priority' => 'high', 'sla_status' => 'warning', 'hours' => 1.75], ['client_id' => 8, 'client_name' => 'Beta', 'technician_id' => 4, 'work_date' => '2026-09-12', 'status' => 'open', 'priority' => 'high', 'sla_status' => 'warning', 'hours' => 9], ]; report_services_assert_same( [['client_id' => 7, 'client_name' => 'Acme', 'hours' => 3.0]], (new HoursPerClientReport($filters))->build($entries, ReportAudience::CLIENT), 'Hours-per-client should apply the common filters before aggregation and return a client-safe projection.' ); $workload = (new TechnicianWorkloadReport($filters))->build($entries, ReportAudience::INTERNAL); report_services_assert_same( [['technician_id' => 4, 'technician_name' => 'Tess', 'hours' => 3.0, 'sla_hours' => 0.0]], $workload, 'Technician workload should aggregate filtered hours with deterministic internal fields.' ); $clientWorkload = (new TechnicianWorkloadReport())->build($entries, ReportAudience::CLIENT); report_services_assert_same( [['client_id' => 7, 'client_name' => 'Acme', 'hours' => 3.0, 'sla_hours' => 0.0], ['client_id' => 8, 'client_name' => 'Beta', 'hours' => 9.0, 'sla_hours' => 0.0]], $clientWorkload, 'Client workload rows should preserve client attribution without technician identity.' ); $filtered = (new SlaReport(ReportFilters::fromArray(['sla' => 'critical'])))->build([ ['client_id' => 1, 'client_name' => 'Acme', 'allocated_hours' => 10, 'hours' => [9]], ['client_id' => 2, 'client_name' => 'Beta', 'allocated_hours' => 10, 'hours' => [2]], ], ReportAudience::CLIENT); report_services_assert_same(1, count($filtered), 'SLA status filtering should use the computed usage status.'); report_services_assert_same(1, $filtered[0]['client_id'], 'SLA status filtering should retain the critical agreement.'); $html = (new PrintReportRenderer())->renderRecords('Rows', [ ['name' => 'Acme', 'hours' => 3.0], ]); if (!str_contains($html, 'application/pdf') || !str_contains($html, '<b>Acme</b>') || !str_contains($html, '@page')) { throw new RuntimeException('Print renderer should emit PDF-ready metadata, print CSS, and escaped record cells.'); } printf("Focused report service tests: 5 passed\n");