requiresEndDate() && null === $endDate) { throw new UnprocessableEntityHttpException('contractEndDate is required for CDD and INTERIM.'); } if (!$allowCdiEndDate && ContractNature::CDI === $nature && null !== $endDate) { throw new UnprocessableEntityHttpException('contractEndDate must be empty for CDI.'); } } public function assertCloseEndDateCanBeApplied( DateTimeImmutable $startDate, ?DateTimeImmutable $currentEndDate, DateTimeImmutable $requestedEndDate, ContractNature $nature ): void { $this->assertPeriodDates($startDate, $requestedEndDate, $nature, true); if (null !== $currentEndDate && $requestedEndDate > $currentEndDate) { throw new UnprocessableEntityHttpException('contractEndDate cannot be increased on current contract.'); } } public function assertNextStartDateCompatible( DateTimeImmutable $startDate, EmployeeContractPeriod $currentPeriod ): void { $currentEndDate = $currentPeriod->getEndDate(); if (null === $currentEndDate) { if ($startDate <= $currentPeriod->getStartDate()) { throw new UnprocessableEntityHttpException('contractStartDate must be after current contract start date.'); } return; } if ($startDate <= $currentEndDate) { throw new UnprocessableEntityHttpException('contractStartDate must be after current contract end date.'); } } /** * Validates the per-period work schedule (`workDaysHours`) against the contract. * * Mandatory for non-standard TIME contracts (weeklyHours ∉ {35, 39}, non-INTERIM, * non-Forfait). Forbidden on standard/forfait/interim contracts (ambiguity). * When provided, sum of minutes MUST equal weeklyHours × 60. * * @param null|array $workDaysHours */ public function assertWorkDaysHours(?Contract $contract, ContractNature $nature, ?array $workDaysHours): void { if (null === $contract) { return; } $trackingMode = $contract->getTrackingMode(); $weeklyHours = $contract->getWeeklyHours(); $isStandard = 35 === $weeklyHours || 39 === $weeklyHours; $isForfait = TrackingMode::PRESENCE->value === $trackingMode; $isInterim = ContractNature::INTERIM === $nature; if ($isForfait || $isInterim || $isStandard) { if (null !== $workDaysHours && [] !== $workDaysHours) { throw new UnprocessableEntityHttpException('workDaysHours must not be provided for Forfait, Interim or 35h/39h contracts.'); } return; } if (null === $workDaysHours || [] === $workDaysHours) { throw new UnprocessableEntityHttpException('workDaysHours is required for non-standard contracts.'); } $totalMinutes = 0; foreach ($workDaysHours as $isoDay => $minutes) { if (!is_int($isoDay) && !(is_string($isoDay) && ctype_digit($isoDay))) { throw new UnprocessableEntityHttpException('workDaysHours keys must be iso weekdays 1-5 (Mon-Fri) as integers.'); } $iso = (int) $isoDay; if ($iso < 1 || $iso > 5) { throw new UnprocessableEntityHttpException('workDaysHours keys must be iso weekdays 1-5 (Mon-Fri).'); } if (!is_int($minutes) || $minutes < 0) { throw new UnprocessableEntityHttpException('workDaysHours values must be non-negative integer minutes.'); } $totalMinutes += $minutes; } $expectedMinutes = ($weeklyHours ?? 0) * 60; if ($totalMinutes !== $expectedMinutes) { throw new UnprocessableEntityHttpException(sprintf( 'workDaysHours total must equal contract weekly hours: got %d min, expected %d min.', $totalMinutes, $expectedMinutes )); } } }