125 lines
4.7 KiB
PHP
125 lines
4.7 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Service\Contracts;
|
||
|
||
use App\Entity\Contract;
|
||
use App\Entity\EmployeeContractPeriod;
|
||
use App\Enum\ContractNature;
|
||
use App\Enum\TrackingMode;
|
||
use DateTimeImmutable;
|
||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||
|
||
final class EmployeeContractPeriodValidator
|
||
{
|
||
public function assertPeriodDates(
|
||
DateTimeImmutable $startDate,
|
||
?DateTimeImmutable $endDate,
|
||
ContractNature $nature,
|
||
bool $allowCdiEndDate = false
|
||
): void {
|
||
if (null !== $endDate && $endDate < $startDate) {
|
||
throw new UnprocessableEntityHttpException('contractEndDate cannot be before contractStartDate.');
|
||
}
|
||
|
||
if ($nature->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<int, int> $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
|
||
));
|
||
}
|
||
}
|
||
}
|