> $rows @return list> */ public function build(array $rows, string $audience = ReportAudience::INTERNAL): array { ReportAudience::validate($audience); $filters = $this->filters ?? new ReportFilters(); $totals = []; foreach ($rows as $row) { if (!$filters->matches($row)) continue; $technicianId = (int)($row['technician_id'] ?? 0); $clientId = (int)($row['client_id'] ?? 0); $key = $audience === ReportAudience::CLIENT ? 'client:' . $clientId : 'technician:' . $technicianId; if (!isset($totals[$key])) { $totals[$key] = $audience === ReportAudience::CLIENT ? ['client_id' => $clientId, 'client_name' => (string)($row['client_name'] ?? ''), 'hours' => 0.0, 'sla_hours' => 0.0] : ['technician_id' => $technicianId, 'technician_name' => (string)($row['technician_name'] ?? ''), 'hours' => 0.0, 'sla_hours' => 0.0]; } $hours = max(0.0, (float)($row['hours'] ?? 0)); $totals[$key]['hours'] += $hours; if (!empty($row['counts_toward_sla'])) $totals[$key]['sla_hours'] += $hours; } $result = array_values($totals); foreach ($result as &$item) { $item['hours'] = round($item['hours'], 2); $item['sla_hours'] = round($item['sla_hours'], 2); } unset($item); usort($result, static fn (array $a, array $b): int => array_key_exists('client_id', $a) ? (strcmp((string)($a['client_name'] ?? ''), (string)($b['client_name'] ?? '')) ?: ((int)$a['client_id'] <=> (int)$b['client_id'])) : (strcmp((string)($a['technician_name'] ?? ''), (string)($b['technician_name'] ?? '')) ?: ((int)$a['technician_id'] <=> (int)$b['technician_id']))); return $result; } public function query(array $rows, string $audience = ReportAudience::INTERNAL): array { return $this->build($rows, $audience); } }