fix : correction calcule prorata congés avec un arrêt maladie long
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled

This commit is contained in:
2026-03-17 13:27:51 +01:00
parent 8563ddb08c
commit 9787231052
5 changed files with 245 additions and 14 deletions

View File

@@ -24,6 +24,7 @@ final readonly class LeaveBalanceComputationService
private const float STANDARD_SATURDAY_ACCRUAL_PER_MONTH = self::STANDARD_ANNUAL_SATURDAYS / 12.0;
private const float FOUR_HOUR_ANNUAL_DAYS = 10.0;
private const float FOUR_HOUR_ACCRUAL_PER_MONTH = 0.83;
private const float LONG_MALADIE_MONTHLY_ACCRUAL = 2.0;
public function __construct(
private AbsenceRepository $absenceRepository,
@@ -31,6 +32,7 @@ final readonly class LeaveBalanceComputationService
private EmployeeLeaveBalanceRepository $leaveBalanceRepository,
private PublicHolidayServiceInterface $publicHolidayService,
private SuspensionDaysCalculator $suspensionDaysCalculator,
private LongMaladieService $longMaladieService,
) {}
/**
@@ -83,19 +85,34 @@ final readonly class LeaveBalanceComputationService
$suspensions = $this->suspensionDaysCalculator->applyFirstMonthGrace(
$this->resolveSuspensionsForEmployeePeriod($employee, $from, $to)
);
$longMaladiePeriods = [];
$longMaladieReductionFactor = 1.0;
if (4 !== $employee->getContract()?->getWeeklyHours()) {
$longMaladiePeriods = $this->longMaladieService->findReducedRatePeriods($employee, $effectiveFrom, $to);
if ([] !== $longMaladiePeriods) {
$totalNormalAccrual = $this->resolveDaysAccrualPerMonth($employee) + $this->resolveSaturdayAccrualPerMonth($employee);
$longMaladieReductionFactor = self::LONG_MALADIE_MONTHLY_ACCRUAL / $totalNormalAccrual;
}
}
$generatedDays = $this->computeAccruedDays(
$this->resolveAnnualDays($employee),
$this->resolveDaysAccrualPerMonth($employee),
$effectiveFrom,
$to,
$suspensions
$suspensions,
$longMaladiePeriods,
$longMaladieReductionFactor
);
$generatedSaturdays = $this->computeAccruedDays(
$this->resolveAnnualSaturdays($employee),
$this->resolveSaturdayAccrualPerMonth($employee),
$effectiveFrom,
$to,
$suspensions
$suspensions,
$longMaladiePeriods,
$longMaladieReductionFactor
);
$absences = $this->absenceRepository->findByEmployeeAndOverlappingDateRange($employee, $effectiveFrom, $to);
@@ -267,12 +284,18 @@ final readonly class LeaveBalanceComputationService
: self::STANDARD_SATURDAY_ACCRUAL_PER_MONTH;
}
/**
* @param list<ContractSuspension> $suspensions
* @param list<array{start: DateTimeImmutable, end: DateTimeImmutable}> $longMaladiePeriods
*/
private function computeAccruedDays(
float $annualCap,
float $accrualPerMonth,
DateTimeImmutable $periodStart,
DateTimeImmutable $periodEnd,
array $suspensions = []
array $suspensions = [],
array $longMaladiePeriods = [],
float $longMaladieReductionFactor = 1.0
): float {
if ($accrualPerMonth <= 0.0 || $periodEnd < $periodStart) {
return 0.0;
@@ -281,7 +304,8 @@ final readonly class LeaveBalanceComputationService
$periodStart = $this->normalizeDate($periodStart);
$periodEnd = $this->normalizeDate($periodEnd);
$publicHolidays = [] !== $suspensions ? $this->buildPublicHolidayMap($periodStart, $periodEnd) : [];
$coveredMonths = 0.0;
$normalMonths = 0.0;
$reducedMonths = 0.0;
$cursor = $periodStart->modify('first day of this month')->setTime(0, 0);
while ($cursor <= $periodEnd) {
$monthStart = $cursor > $periodStart ? $cursor : $periodStart;
@@ -295,7 +319,7 @@ final readonly class LeaveBalanceComputationService
if ($suspendedDays > 0) {
$businessDays = $this->countBusinessDaysInRange($monthStart, $monthEnd, $publicHolidays);
$suspendedBusinessDays = $this->suspensionDaysCalculator->countSuspendedBusinessDays($monthStart, $monthEnd, $suspensions, $publicHolidays);
$coveredMonths += max(0, $businessDays - $suspendedBusinessDays) / 22.0;
$normalMonths += max(0, $businessDays - $suspendedBusinessDays) / 22.0;
$cursor = $cursor->modify('first day of next month');
continue;
@@ -304,12 +328,25 @@ final readonly class LeaveBalanceComputationService
$coveredDays = ((int) $monthEnd->diff($monthStart)->format('%a')) + 1;
$daysInMonth = (int) $cursor->format('t');
$coveredMonths += $coveredDays / $daysInMonth;
if ([] !== $longMaladiePeriods) {
$reducedDays = $this->longMaladieService->countReducedDaysInMonth($monthStart, $monthEnd, $longMaladiePeriods);
if ($reducedDays > 0) {
$normalDays = max(0, $coveredDays - $reducedDays);
$normalMonths += $normalDays / $daysInMonth;
$reducedMonths += min($coveredDays, $reducedDays) / $daysInMonth;
$cursor = $cursor->modify('first day of next month');
continue;
}
}
$normalMonths += $coveredDays / $daysInMonth;
$cursor = $cursor->modify('first day of next month');
}
return min($annualCap, $coveredMonths * $accrualPerMonth);
return min($annualCap, ($normalMonths + $reducedMonths * $longMaladieReductionFactor) * $accrualPerMonth);
}
private function parseYmdDate(string $value): ?DateTimeImmutable