refactor(exercise) : extract ExerciseYearResolver to dedup year formula

Pull the "date -> leave/RTT exercise year" formula out of
EmployeeRttPaymentProcessor, EmployeeRttSummaryProvider and
EmployeeLeaveSummaryProvider into a single
App\Service\Exercise\ExerciseYearResolver. Forfait flag is parameterised
so the leave (calendar year) and RTT (Juin N-1 -> Mai N) variants share
the same implementation. Pure refactor, no behavioural change.
This commit is contained in:
2026-05-19 11:33:06 +02:00
parent 613ac02e1d
commit 8f355e05ad
8 changed files with 109 additions and 47 deletions

View File

@@ -22,6 +22,7 @@ use App\Repository\EmployeeRepository;
use App\Repository\WorkHourRepository;
use App\Security\EmployeeScopeService;
use App\Service\Contracts\EmployeeContractPhaseResolver;
use App\Service\Exercise\ExerciseYearResolver;
use App\Service\Leave\LeaveBalanceComputationService;
use App\Service\Leave\LongMaladieService;
use App\Service\Leave\SuspensionDaysCalculator;
@@ -63,6 +64,7 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
private SuspensionDaysCalculator $suspensionDaysCalculator,
private WorkHourRepository $workHourRepository,
private EmployeeContractPhaseResolver $phaseResolver,
private ExerciseYearResolver $exerciseYearResolver,
string $dataStartDate = '',
) {
$this->dataStartDate = '' !== $dataStartDate ? $dataStartDate : null;
@@ -480,9 +482,9 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
private function clampYearToPhase(int $year, ContractPhase $phase, bool $isForfait): int
{
$firstYear = $this->exerciseYearForDate($phase->startDate, $isForfait);
$firstYear = $this->exerciseYearResolver->forDate($phase->startDate, $isForfait);
$lastYear = $phase->endDate instanceof DateTimeImmutable
? $this->exerciseYearForDate($phase->endDate, $isForfait)
? $this->exerciseYearResolver->forDate($phase->endDate, $isForfait)
: null;
if ($year < $firstYear) {
@@ -495,22 +497,6 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
return $year;
}
/**
* Map a date to the leave exercise year it belongs to.
* - Forfait: exercise = calendar year.
* - Non-forfait: exercise N runs from June (N-1) to May (N); dates in June-December
* map to N+1, January-May map to N.
*/
private function exerciseYearForDate(DateTimeImmutable $date, bool $isForfait): int
{
$year = (int) $date->format('Y');
if ($isForfait) {
return $year;
}
return (int) $date->format('n') >= 6 ? $year + 1 : $year;
}
private function resolveTargetPhase(Employee $employee): ContractPhase
{
$raw = $this->requestStack->getCurrentRequest()?->query->get('phaseId');
@@ -1041,7 +1027,7 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
: $this->resolveCurrentLeaveYear(new DateTimeImmutable('today'));
// Do not go before the exercice containing $phase->startDate.
$phaseFirstYear = $this->exerciseYearForDate($phase->startDate, $isForfait);
$phaseFirstYear = $this->exerciseYearResolver->forDate($phase->startDate, $isForfait);
$history = $employee->getContractHistory();
if ([] === $history) {
@@ -1066,7 +1052,7 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
return max($phaseFirstYear, $candidate);
}
$firstYear = $this->exerciseYearForDate($oldestStartDate, $isForfait);
$firstYear = $this->exerciseYearResolver->forDate($oldestStartDate, $isForfait);
$oldestBalanceYear = $this->leaveBalanceRepository->findEarliestYearForEmployee($employee);
if (null !== $oldestBalanceYear && $oldestBalanceYear < $firstYear) {