feat: complete jobcard management workflows and UI
This commit is contained in:
@@ -1,13 +1,32 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/ReportFilters.php';
|
||||
require_once __DIR__ . '/ReportDataMapper.php';
|
||||
require_once __DIR__ . '/ReportQuery.php';
|
||||
require_once __DIR__ . '/ReportAudience.php';
|
||||
|
||||
final class HoursPerClientReport
|
||||
/** Deterministic hours aggregation grouped by client. */
|
||||
final class HoursPerClientReport implements ReportQuery
|
||||
{
|
||||
/** @param list<array<string, mixed>> $entries
|
||||
* @return list<array{client_id:int, client_name:string, hours:float}>
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ?ReportFilters $filters = null,
|
||||
private readonly ?ReportDataMapper $mapper = null,
|
||||
) {}
|
||||
|
||||
/** @param list<array<string,mixed>> $entries @return list<array<string,mixed>> */
|
||||
public function build(array $entries, string $audience = ReportAudience::CLIENT): array
|
||||
{
|
||||
ReportAudience::validate($audience);
|
||||
$filters = $this->filters ?? new ReportFilters();
|
||||
$selected = array_values(array_filter($entries, static fn (array $entry): bool => $filters->matches($entry)));
|
||||
$rows = $this->aggregate($selected);
|
||||
// This report contains no technician or internal-note fields, so the same
|
||||
// stable projection is safe for both audiences.
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/** @param list<array<string,mixed>> $entries @return list<array{client_id:int,client_name:string,hours:float}> */
|
||||
public function aggregate(array $entries): array
|
||||
{
|
||||
$totals = [];
|
||||
@@ -15,20 +34,16 @@ final class HoursPerClientReport
|
||||
$id = (int)($entry['client_id'] ?? 0);
|
||||
$key = (string)$id;
|
||||
if (!isset($totals[$key])) {
|
||||
$totals[$key] = [
|
||||
'client_id' => $id,
|
||||
'client_name' => (string)($entry['client_name'] ?? ''),
|
||||
'hours' => 0.0,
|
||||
];
|
||||
$totals[$key] = ['client_id' => $id, 'client_name' => (string)($entry['client_name'] ?? ''), 'hours' => 0.0];
|
||||
}
|
||||
$totals[$key]['hours'] += max(0.0, (float)($entry['hours'] ?? 0));
|
||||
}
|
||||
$rows = array_values($totals);
|
||||
foreach ($rows as &$row) {
|
||||
$row['hours'] = round($row['hours'], 2);
|
||||
}
|
||||
foreach ($rows as &$row) $row['hours'] = round($row['hours'], 2);
|
||||
unset($row);
|
||||
usort($rows, static fn (array $a, array $b): int => strcmp($a['client_name'], $b['client_name']) ?: $a['client_id'] <=> $b['client_id']);
|
||||
usort($rows, static fn (array $a, array $b): int => strcmp($a['client_name'], $b['client_name']) ?: ($a['client_id'] <=> $b['client_id']));
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function query(array $entries, string $audience = ReportAudience::CLIENT): array { return $this->build($entries, $audience); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user