security->getUser(); // Endpoint protégé: on exige un utilisateur authentifié. if (!$user instanceof User) { throw new AccessDeniedHttpException('Authentication required.'); } $workDate = $this->resolveWorkDate(); $employees = $this->employeeRepository->findScoped($user); $absences = $this->absenceRepository->findByDateAndEmployees($workDate, $employees); $rowsByEmployeeId = []; foreach ($employees as $employee) { $employeeId = $employee->getId(); if (!$employeeId) { continue; } // On initialise toutes les lignes, même sans absence ce jour-là. $rowsByEmployeeId[$employeeId] = new DayContextRow( employeeId: $employeeId, hasContractAtDate: null !== $this->contractResolver->resolveForEmployeeAndDate($employee, $workDate) ); } $dateKey = $workDate->format('Y-m-d'); foreach ($absences as $absence) { $employeeId = $absence->getEmployee()?->getId(); // Ignore les absences orphelines ou hors scope utilisateur. if (!$employeeId || !isset($rowsByEmployeeId[$employeeId])) { continue; } [$absentMorning, $absentAfternoon] = $this->absenceSegmentsResolver->resolveForDate($absence, $dateKey); // Pas de segment absent sur ce jour: rien à injecter dans la ligne. if (!$absentMorning && !$absentAfternoon) { continue; } // Calcule le crédit d'heures selon la politique métier (type d'absence + contrat). $creditedMinutes = $this->workedHoursCreditPolicy->computeCreditedMinutes($absence, $dateKey, $absentMorning, $absentAfternoon); $creditedPresenceUnits = $this->workedHoursCreditPolicy->computeCreditedPresenceUnits($absence, $dateKey, $absentMorning, $absentAfternoon); $rowsByEmployeeId[$employeeId]->addAbsence( label: $absence->getType()?->getLabel(), color: $absence->getType()?->getColor(), morning: $absentMorning, afternoon: $absentAfternoon, creditedMinutes: $creditedMinutes, creditedPresenceUnits: $creditedPresenceUnits ); } $response = new WorkHourDayContext(); $response->workDate = $dateKey; $response->rows = array_map( static fn (DayContextRow $row): array => $row->toArray(), array_values($rowsByEmployeeId) ); return $response; } private function resolveWorkDate(): DateTimeImmutable { $query = $this->requestStack->getCurrentRequest()?->query; $raw = (string) ($query?->get('workDate') ?? ''); // Sans paramètre, on cible la date du jour. if ('' === $raw) { return new DateTimeImmutable('today'); } $date = DateTimeImmutable::createFromFormat('Y-m-d', $raw); // Validation stricte du format pour éviter les ambiguïtés de parsing. if (!$date || $date->format('Y-m-d') !== $raw) { throw new UnprocessableEntityHttpException('workDate must use Y-m-d format.'); } return $date; } }