persistProcessor->process($data, $operation, $uriVariables, $context); } $period = $data->getContractPeriod(); if (!$period instanceof EmployeeContractPeriod && null !== $data->getContractPeriodId()) { $period = $this->entityManager->find(EmployeeContractPeriod::class, $data->getContractPeriodId()); if ($period instanceof EmployeeContractPeriod) { $data->setContractPeriod($period); } } if (!$period instanceof EmployeeContractPeriod) { throw new UnprocessableEntityHttpException('contractPeriodId is required.'); } $this->validate($data, $period); $isNew = null === $data->getId(); $employee = $period->getEmployee(); $empName = $employee ? trim(($employee->getLastName() ?? '').' '.($employee->getFirstName() ?? '')) : ''; $start = $data->getStartDate()->format('d/m/Y'); $end = $data->getEndDate()?->format('d/m/Y') ?? 'indéfinie'; $result = $this->persistProcessor->process($data, $operation, $uriVariables, $context); $this->auditLogger->log( $employee, $isNew ? 'create' : 'update', 'contract_suspension', $data->getId(), sprintf('Suspension %s pour %s du %s au %s', $isNew ? 'créée' : 'modifiée', $empName, $start, $end), ['new' => ['start' => $start, 'end' => $end]], DateTimeImmutable::createFromInterface($data->getStartDate()), ); $this->entityManager->flush(); return $result; } private function validate(ContractSuspension $suspension, EmployeeContractPeriod $period): void { // Compare as Y-m-d strings to avoid timezone issues between Doctrine and API Platform DateTimeImmutable $startDate = $suspension->getStartDate()->format('Y-m-d'); $endDate = $suspension->getEndDate()?->format('Y-m-d'); $periodStart = $period->getStartDate()->format('Y-m-d'); $periodEnd = $period->getEndDate()?->format('Y-m-d'); if (null !== $periodEnd && $periodEnd < new DateTimeImmutable('today')->format('Y-m-d')) { throw new UnprocessableEntityHttpException('Impossible de suspendre une période de contrat clôturée.'); } if (null !== $endDate && $endDate < $startDate) { throw new UnprocessableEntityHttpException('La date de fin doit être postérieure à la date de début.'); } if ($startDate < $periodStart) { throw new UnprocessableEntityHttpException('La suspension ne peut pas commencer avant le début du contrat.'); } if (null !== $periodEnd) { if ($startDate > $periodEnd) { throw new UnprocessableEntityHttpException('La suspension ne peut pas commencer après la fin du contrat.'); } if (null !== $endDate && $endDate > $periodEnd) { throw new UnprocessableEntityHttpException('La suspension ne peut pas se terminer après la fin du contrat.'); } } $this->validateNoOverlap($suspension, $period); } private function validateNoOverlap(ContractSuspension $suspension, EmployeeContractPeriod $period): void { $start = $suspension->getStartDate()->format('Y-m-d'); $end = $suspension->getEndDate()?->format('Y-m-d'); foreach ($period->getSuspensions() as $existing) { if ($existing->getId() === $suspension->getId() && null !== $suspension->getId()) { continue; } $existingStart = $existing->getStartDate()->format('Y-m-d'); $existingEnd = $existing->getEndDate()?->format('Y-m-d'); if (null === $end && null === $existingEnd) { throw new UnprocessableEntityHttpException('Les suspensions ne peuvent pas se chevaucher.'); } if (null === $end) { if ($start <= $existingEnd) { throw new UnprocessableEntityHttpException('Les suspensions ne peuvent pas se chevaucher.'); } continue; } if (null === $existingEnd) { if ($existingStart <= $end) { throw new UnprocessableEntityHttpException('Les suspensions ne peuvent pas se chevaucher.'); } continue; } if ($start <= $existingEnd && $end >= $existingStart) { throw new UnprocessableEntityHttpException('Les suspensions ne peuvent pas se chevaucher.'); } } } }