makeEmployee(1, 'Dupont', 'Jean'); // Janvier : un jour 4h de nuit (>=240 -> 1 jour) + un jour 3h59 (<240 -> 0 jour). $whFull = new WorkHour()->setEmployee($employee) ->setWorkDate(new DateTimeImmutable('2026-01-10')) ->setEveningFrom('21:00')->setEveningTo('01:00') // 240 min nuit ; $whShort = new WorkHour()->setEmployee($employee) ->setWorkDate(new DateTimeImmutable('2026-01-11')) ->setEveningFrom('21:00')->setEveningTo('00:59') // 239 min nuit ; $workHourRepo = $this->createStub(WorkHourReadRepositoryInterface::class); $workHourRepo->method('findByDateRangeAndEmployees')->willReturn([$whFull, $whShort]); $contractResolver = $this->createStub(EmployeeContractResolver::class); $contractResolver->method('resolveIsDriverForEmployeesAndDays')->willReturn([ 1 => ['2026-01-10' => false, '2026-01-11' => false], ]); $builder = new NightContingentExportBuilder( $workHourRepo, $contractResolver, new NightHoursCalculator(), ); $rows = $builder->buildRows([$employee], 2026); self::assertCount(1, $rows); self::assertSame(479, $rows[0]->months[1]['nightMinutes']); // 240 + 239 self::assertSame(1, $rows[0]->months[1]['nightDays']); // seul le jour >=240 self::assertSame(0, $rows[0]->months[2]['nightMinutes']); // fevrier vide self::assertSame(0, $rows[0]->months[2]['nightDays']); } public function testDriverUsesManualNightMinutes(): void { $employee = $this->makeEmployee(2, 'Martin', 'Paul'); $wh = new WorkHour()->setEmployee($employee) ->setWorkDate(new DateTimeImmutable('2026-03-05')) ->setNightHoursMinutes(300) ->setMorningFrom('08:00')->setMorningTo('12:00') // ignore (driver) ; $workHourRepo = $this->createStub(WorkHourReadRepositoryInterface::class); $workHourRepo->method('findByDateRangeAndEmployees')->willReturn([$wh]); $contractResolver = $this->createStub(EmployeeContractResolver::class); $contractResolver->method('resolveIsDriverForEmployeesAndDays')->willReturn([ 2 => ['2026-03-05' => true], ]); $builder = new NightContingentExportBuilder( $workHourRepo, $contractResolver, new NightHoursCalculator(), ); $rows = $builder->buildRows([$employee], 2026); self::assertSame(300, $rows[0]->months[3]['nightMinutes']); self::assertSame(1, $rows[0]->months[3]['nightDays']); // 300 >= 240 } public function testEmployeeWithoutWorkHoursYieldsAllZeroMonths(): void { $employee = $this->makeEmployee(3, 'Durand', 'Marie'); $workHourRepo = $this->createStub(WorkHourReadRepositoryInterface::class); $workHourRepo->method('findByDateRangeAndEmployees')->willReturn([]); $contractResolver = $this->createStub(EmployeeContractResolver::class); $contractResolver->method('resolveIsDriverForEmployeesAndDays')->willReturn([]); $builder = new NightContingentExportBuilder( $workHourRepo, $contractResolver, new NightHoursCalculator(), ); $rows = $builder->buildRows([$employee], 2026); self::assertCount(1, $rows); for ($m = 1; $m <= 12; ++$m) { self::assertSame(0, $rows[0]->months[$m]['nightMinutes']); self::assertSame(0, $rows[0]->months[$m]['nightDays']); } } private function makeEmployee(int $id, string $last, string $first): Employee { $employee = new Employee(); $employee->setLastName($last)->setFirstName($first); $ref = new ReflectionProperty(Employee::class, 'id'); $ref->setValue($employee, $id); return $employee; } }