diff --git a/src/State/AbsencePrintProvider.php b/src/State/AbsencePrintProvider.php index 3c81c68..a969910 100644 --- a/src/State/AbsencePrintProvider.php +++ b/src/State/AbsencePrintProvider.php @@ -6,6 +6,7 @@ namespace App\State; use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProviderInterface; +use App\Entity\Employee; use App\Entity\Formation; use App\Enum\ContractNature; use App\Enum\HalfDay; @@ -56,12 +57,15 @@ class AbsencePrintProvider implements ProviderInterface $fromDate = DateTimeImmutable::createFromFormat('Y-m-d', $from); $toDate = DateTimeImmutable::createFromFormat('Y-m-d', $to); + if (!$fromDate instanceof DateTimeImmutable || !$toDate instanceof DateTimeImmutable) { + return new Response('Invalid from/to date format.', Response::HTTP_BAD_REQUEST); + } $siteIds = $this->parseIds($request->query->get('sites')); $workContractIds = $this->parseIds($request->query->get('workContracts')); $contractNatures = $this->parseContractNatures($request->query->get('contractNatures')); - $employees = $this->loadEmployees($siteIds, $contractNatures, $workContractIds); + $employees = $this->loadEmployees($siteIds, $contractNatures, $workContractIds, $fromDate, $toDate); $absences = $this->loadAbsences($fromDate, $toDate, $employees); $formations = $this->formationRepository->findByDateRangeAndEmployees($fromDate, $toDate, $employees); @@ -117,21 +121,41 @@ class AbsencePrintProvider implements ProviderInterface return array_values(array_unique($ids)); } - private function loadEmployees(array $siteIds, array $contractNatures, array $workContractIds): array + private function loadEmployees(array $siteIds, array $contractNatures, array $workContractIds, DateTimeImmutable $from, DateTimeImmutable $to): array { $employees = $this->employeeRepository->findForPrintBySiteIds($siteIds); - return array_values(array_filter($employees, static function ($employee) use ($contractNatures, $workContractIds): bool { + return array_values(array_filter($employees, function ($employee) use ($contractNatures, $workContractIds, $from, $to): bool { $employeeNature = (string) $employee->getCurrentContractNature(); $employeeContractId = $employee->getContract()?->getId(); $natureMatches = [] === $contractNatures || in_array($employeeNature, $contractNatures, true); $contractMatches = [] === $workContractIds || (null !== $employeeContractId && in_array($employeeContractId, $workContractIds, true)); - return $natureMatches && $contractMatches; + // Exclut les employés dont aucune période de contrat n'intersecte la période imprimée + // (ex. un salarié parti en avril ne doit pas apparaître sur une impression de mai). + return $natureMatches && $contractMatches && $this->hasContractInRange($employee, $from, $to); })); } + /** + * Vrai si au moins une période de contrat de l'employé intersecte [$from, $to]. + * Une période sans date de fin (contrat en cours) est considérée ouverte jusqu'à l'infini. + * Aligné avec le filtre `hasContractInSelectedMonth` de la vue Calendrier. + */ + private function hasContractInRange(Employee $employee, DateTimeImmutable $from, DateTimeImmutable $to): bool + { + foreach ($employee->getContractPeriods() as $period) { + $start = $period->getStartDate(); + $end = $period->getEndDate(); + if ($start <= $to && (null === $end || $end >= $from)) { + return true; + } + } + + return false; + } + private function loadAbsences(DateTimeImmutable $from, DateTimeImmutable $to, array $employees): array { return $this->absenceRepository->findForPrint($from, $to, $employees); diff --git a/tests/State/AbsencePrintProviderTest.php b/tests/State/AbsencePrintProviderTest.php new file mode 100644 index 0000000..2665497 --- /dev/null +++ b/tests/State/AbsencePrintProviderTest.php @@ -0,0 +1,84 @@ +newInstanceWithoutConstructor(); + $employee = $this->buildEmployeeWithPeriod('2025-01-01', '2026-04-30'); + + // Imprime mai : l'employé parti le 30/04 ne doit pas être inclus. + self::assertFalse($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31')); + } + + public function testHasContractInRangeTrueWhenContractOverlapsRange(): void + { + $provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor(); + $employee = $this->buildEmployeeWithPeriod('2025-01-01', '2026-05-15'); + + // Contrat finissant le 15/05 → chevauche le mois de mai → inclus. + self::assertTrue($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31')); + } + + public function testHasContractInRangeTrueForOpenEndedContract(): void + { + $provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor(); + $employee = $this->buildEmployeeWithPeriod('2020-01-01', null); + + self::assertTrue($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31')); + } + + public function testHasContractInRangeFalseWhenContractStartsAfterRange(): void + { + $provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor(); + $employee = $this->buildEmployeeWithPeriod('2026-06-01', null); + + self::assertFalse($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31')); + } + + public function testHasContractInRangeFalseWhenNoPeriods(): void + { + $provider = new ReflectionClass(AbsencePrintProvider::class)->newInstanceWithoutConstructor(); + $employee = new Employee(); + + self::assertFalse($this->hasInRange($provider, $employee, '2026-05-01', '2026-05-31')); + } + + private function hasInRange(object $provider, Employee $employee, string $from, string $to): bool + { + return new ReflectionClass($provider::class) + ->getMethod('hasContractInRange') + ->invoke($provider, $employee, new DateTimeImmutable($from), new DateTimeImmutable($to)) + ; + } + + private function buildEmployeeWithPeriod(string $start, ?string $end): Employee + { + $employee = new Employee(); + $period = new EmployeeContractPeriod(); + $period->setEmployee($employee); + $period->setStartDate(new DateTimeImmutable($start)); + $period->setEndDate(null !== $end ? new DateTimeImmutable($end) : null); + $employee->getContractPeriods()->add($period); + + return $employee; + } +}