feat: add administration reporting and correction services
This commit is contained in:
@@ -6,6 +6,7 @@ require_once __DIR__ . '/../app/Domain/Reporting/ReportDataMapper.php';
|
||||
require_once __DIR__ . '/../app/Domain/Reporting/ClientJobcardReport.php';
|
||||
require_once __DIR__ . '/../app/Domain/Reporting/ClientHistoryReport.php';
|
||||
require_once __DIR__ . '/../app/Domain/Reporting/TechnicianActivityReport.php';
|
||||
require_once __DIR__ . '/../app/Domain/Reporting/SlaReport.php';
|
||||
require_once __DIR__ . '/../app/Domain/Reporting/PrintReportRenderer.php';
|
||||
|
||||
$filters = ReportFilters::fromArray([
|
||||
@@ -47,9 +48,38 @@ if ($activity !== [['technician_id' => 4, 'technician_name' => 'Tess', 'client_i
|
||||
throw new RuntimeException('Technician activity must aggregate hours deterministically.');
|
||||
}
|
||||
|
||||
$crossClientActivity = (new TechnicianActivityReport())->build([
|
||||
['id' => 20, 'technician_id' => 4, 'technician_name' => 'Tess', 'client_id' => 8, 'client_name' => 'Beta', 'work_date' => '2026-09-02', 'hours' => 2, 'counts_toward_sla' => true, 'credentials' => 'secret'],
|
||||
['id' => 10, 'technician_id' => 4, 'technician_name' => 'Tess', 'client_id' => 7, 'client_name' => 'Acme', 'work_date' => '2026-09-02', 'hours' => 1, 'counts_toward_sla' => true, 'internal_notes' => 'secret'],
|
||||
], 'internal');
|
||||
if ($crossClientActivity !== [
|
||||
['technician_id' => 4, 'technician_name' => 'Tess', 'client_id' => 7, 'client_name' => 'Acme', 'hours' => 1.0, 'sla_hours' => 1.0],
|
||||
['technician_id' => 4, 'technician_name' => 'Tess', 'client_id' => 8, 'client_name' => 'Beta', 'hours' => 2.0, 'sla_hours' => 2.0],
|
||||
]) {
|
||||
throw new RuntimeException('Technician activity must preserve client attribution and sort by technician then client.');
|
||||
}
|
||||
$clientActivity = (new TechnicianActivityReport())->build([
|
||||
['technician_id' => 4, 'technician_name' => 'Tess', 'client_id' => 7, 'client_name' => 'Acme', 'work_date' => '2026-09-02', 'hours' => 1, 'counts_toward_sla' => true, 'password' => 'secret'],
|
||||
], 'client');
|
||||
if ($clientActivity !== [['client_id' => 7, 'client_name' => 'Acme', 'hours' => 1.0, 'sla_hours' => 1.0]]) {
|
||||
throw new RuntimeException('Client technician activity projection must exclude technician and secret fields.');
|
||||
}
|
||||
|
||||
$filteredSla = (new SlaReport(ReportFilters::fromArray(['client_id' => 7, 'date_from' => '2026-09-01', 'date_to' => '2026-09-30'])))->build([
|
||||
['client_id' => 8, 'client_name' => 'Beta', 'allocated_hours' => 10, 'hours' => [1], 'start_date' => '2026-09-01'],
|
||||
['client_id' => 7, 'client_name' => 'Acme', 'allocated_hours' => 10, 'hours' => [2], 'start_date' => '2026-09-01', 'credentials' => 'secret'],
|
||||
], 'client');
|
||||
if ($filteredSla !== [['client_id' => 7, 'client_name' => 'Acme', 'allocated_hours' => 10.0, 'used_hours' => 2.0, 'remaining_hours' => 8.0, 'usage_percentage' => 20.0, 'status' => 'within_limit']]) {
|
||||
throw new RuntimeException('SLA report must apply filters and expose a safe audience projection.');
|
||||
}
|
||||
|
||||
$html = (new PrintReportRenderer())->render('Client Jobcards', ['Reference', 'Status'], [['JC-2', 'open']]);
|
||||
if (!str_contains($html, '@media print') || !str_contains($html, '<th>Reference</th>') || !str_contains($html, 'JC-2') || str_contains($html, '<script>')) {
|
||||
throw new RuntimeException('Print report renderer must emit escaped, print-friendly HTML.');
|
||||
}
|
||||
$escapedHtml = (new PrintReportRenderer())->render('Report <x>', ['Value'], [['<script>alert(1)</script>']]);
|
||||
if (str_contains($escapedHtml, '<script>alert') || !str_contains($escapedHtml, '<script>')) {
|
||||
throw new RuntimeException('PDF-ready HTML must escape report content.');
|
||||
}
|
||||
|
||||
printf("Report workflow tests: 5 passed\n");
|
||||
|
||||
Reference in New Issue
Block a user