2026-09-01 18:54:47 +02:00
|
|
|
<?php
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2026-09-01 21:03:38 +02:00
|
|
|
require_once __DIR__ . '/ReportFilters.php';
|
|
|
|
|
require_once __DIR__ . '/ReportDataMapper.php';
|
|
|
|
|
require_once __DIR__ . '/ReportAudience.php';
|
|
|
|
|
|
2026-09-01 18:54:47 +02:00
|
|
|
final class SlaReport
|
|
|
|
|
{
|
2026-09-01 21:03:38 +02:00
|
|
|
public function __construct(
|
|
|
|
|
private readonly ?ReportFilters $filters = null,
|
|
|
|
|
private readonly ?ReportDataMapper $mapper = null,
|
|
|
|
|
) {}
|
|
|
|
|
|
2026-09-01 18:54:47 +02:00
|
|
|
/** @param list<array<string, mixed>> $agreements
|
|
|
|
|
* @return list<array<string, mixed>>
|
|
|
|
|
*/
|
2026-09-01 21:03:38 +02:00
|
|
|
public function rows(array $agreements, string $audience = ReportAudience::INTERNAL): array
|
2026-09-01 18:54:47 +02:00
|
|
|
{
|
2026-09-01 21:03:38 +02:00
|
|
|
ReportAudience::validate($audience);
|
|
|
|
|
$filters = $this->filters ?? new ReportFilters();
|
|
|
|
|
$mapper = $this->mapper ?? new ReportDataMapper();
|
2026-09-01 18:54:47 +02:00
|
|
|
$rows = [];
|
|
|
|
|
foreach ($agreements as $agreement) {
|
2026-09-01 21:03:38 +02:00
|
|
|
if (!$filters->matches($agreement)) continue;
|
2026-09-01 18:54:47 +02:00
|
|
|
$allocated = max(0.0, (float)($agreement['allocated_hours'] ?? 0));
|
|
|
|
|
$used = 0.0;
|
2026-09-01 21:03:38 +02:00
|
|
|
foreach ((array)($agreement['hours'] ?? []) as $hours) $used += max(0.0, (float)$hours);
|
2026-09-01 18:54:47 +02:00
|
|
|
$used = round($used, 2);
|
|
|
|
|
$remaining = round(max(0.0, $allocated - $used), 2);
|
2026-09-01 21:03:38 +02:00
|
|
|
$percentage = $allocated > 0 ? round(($used / $allocated) * 100, 2) : ($used > 0 ? 100.0 : 0.0);
|
|
|
|
|
$status = $used > $allocated ? 'exceeded' : ($percentage >= 90 ? 'critical' : ($percentage >= 75 ? 'warning' : 'within_limit'));
|
|
|
|
|
$record = [
|
2026-09-01 18:54:47 +02:00
|
|
|
'client_id' => (int)($agreement['client_id'] ?? 0),
|
|
|
|
|
'client_name' => (string)($agreement['client_name'] ?? ''),
|
|
|
|
|
'allocated_hours' => $allocated,
|
|
|
|
|
'used_hours' => $used,
|
|
|
|
|
'remaining_hours' => $remaining,
|
|
|
|
|
'usage_percentage' => $percentage,
|
|
|
|
|
'status' => $status,
|
|
|
|
|
];
|
2026-09-01 21:03:38 +02:00
|
|
|
$rows[] = $audience === ReportAudience::CLIENT ? $mapper->clientSla($record) : $mapper->internalSla($record);
|
2026-09-01 18:54:47 +02:00
|
|
|
}
|
2026-09-01 21:03:38 +02:00
|
|
|
usort($rows, static fn(array $a, array $b): int => strcmp((string)$a['client_name'], (string)$b['client_name']) ?: ((int)$a['client_id'] <=> (int)$b['client_id']));
|
2026-09-01 18:54:47 +02:00
|
|
|
return $rows;
|
|
|
|
|
}
|
2026-09-01 21:03:38 +02:00
|
|
|
|
|
|
|
|
public function build(array $agreements, string $audience = ReportAudience::INTERNAL): array
|
|
|
|
|
{
|
|
|
|
|
return $this->rows($agreements, $audience);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function query(array $agreements, string $audience = ReportAudience::INTERNAL): array
|
|
|
|
|
{
|
|
|
|
|
return $this->rows($agreements, $audience);
|
|
|
|
|
}
|
2026-09-01 18:54:47 +02:00
|
|
|
}
|