security->getUser(); if (!$user instanceof User) { throw new AccessDeniedHttpException('Authentication required.'); } [$from, $to] = $this->resolveRange(); // ?driver=1 → ne garder que les conducteurs (écran Heures Conducteurs) ; // défaut → ne garder que les non-conducteurs (écran Heures). $driverOnly = filter_var( $this->requestStack->getCurrentRequest()?->query->get('driver'), FILTER_VALIDATE_BOOLEAN ); $employees = $this->employeeRepository->findScoped($user); $workHours = $this->workHourRepository->findByDateRangeAndEmployees($from, $to, $employees); // Agrégation par jour : total = lignes non-conducteur, pending = lignes isValid=false. /** @var array $byDate */ $byDate = []; // Mémoïsation de la résolution conducteur par (employé, jour) : un même // couple peut revenir et resolveIsDriver... interroge la BDD. $driverCache = []; foreach ($workHours as $workHour) { $employee = $workHour->getEmployee(); if (!$employee instanceof Employee) { continue; } $date = DateTimeImmutable::createFromInterface($workHour->getWorkDate()); $dateKey = $date->format('Y-m-d'); $cacheKey = $employee->getId().'|'.$dateKey; $isDriver = $driverCache[$cacheKey] ??= $this->contractResolver->resolveIsDriverForEmployeeAndDate($employee, $date); if ($isDriver !== $driverOnly) { continue; } $bucket = &$byDate[$dateKey]; $bucket ??= ['total' => 0, 'pending' => 0]; ++$bucket['total']; if (!$workHour->isValid()) { ++$bucket['pending']; } unset($bucket); } $validatedDays = []; foreach ($byDate as $dateKey => $counts) { if ($counts['total'] > 0 && 0 === $counts['pending']) { $validatedDays[] = $dateKey; } } sort($validatedDays); $response = new WorkHourValidationStatus(); $response->from = $from->format('Y-m-d'); $response->to = $to->format('Y-m-d'); $response->validatedDays = $validatedDays; return $response; } /** * @return array{0: DateTimeImmutable, 1: DateTimeImmutable} */ private function resolveRange(): array { $query = $this->requestStack->getCurrentRequest()?->query; $from = $this->parseDate((string) ($query?->get('from') ?? ''), 'from'); $to = $this->parseDate((string) ($query?->get('to') ?? ''), 'to'); if ($from > $to) { throw new UnprocessableEntityHttpException('from must be before or equal to to.'); } if ($from->diff($to)->days > self::MAX_RANGE_DAYS) { throw new UnprocessableEntityHttpException(sprintf('Range must not exceed %d days.', self::MAX_RANGE_DAYS)); } return [$from, $to]; } private function parseDate(string $raw, string $field): DateTimeImmutable { $date = DateTimeImmutable::createFromFormat('Y-m-d', $raw); if (!$date || $date->format('Y-m-d') !== $raw) { throw new UnprocessableEntityHttpException(sprintf('%s must use Y-m-d format.', $field)); } // Normalise à minuit pour comparer des jours, pas des instants. return $date->setTime(0, 0); } }