feat : ajout de la règle de décompte des RTT et correction du récap congés et RTT
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled

This commit is contained in:
2026-03-16 18:17:58 +01:00
parent e022cfac98
commit 04f90afc58
9 changed files with 218 additions and 123 deletions

View File

@@ -128,7 +128,7 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
*/
public function computeYearSummary(Employee $employee, int $targetYear): ?array
{
$firstYear = $this->resolveFirstComputationYear($employee);
$firstYear = max($this->resolveFirstComputationYear($employee), $targetYear - 1);
if ($targetYear < $firstYear) {
$targetYear = $firstYear;
}

View File

@@ -72,9 +72,12 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
$weeks
);
$limitDate = null;
if ($year > $currentExerciseYear) {
$limitDate = $periodFrom->modify('-1 day');
} else {
// Exclude the current (incomplete) week: limit to last Sunday
$isoDay = (int) $today->format('N'); // 1=Monday .. 7=Sunday
$limitDate = 7 === $isoDay ? $today : $today->modify('last sunday');
}
$currentByWeekStart = $this->rttRecoveryService->computeRecoveryByWeek($employee, $weekRanges, $periodFrom, $periodTo, $limitDate);
@@ -110,6 +113,37 @@ final readonly class EmployeeRttSummaryProvider implements ProviderInterface
$weekRanges
);
// Post-process: distribute deficit weeks across cumulative balance (50% first, then 25%)
$cumulative50 = $carry->base50Minutes + $carry->bonus50Minutes;
$cumulative25 = $carry->base25Minutes + $carry->bonus25Minutes;
foreach ($summary->weeks as $i => $week) {
if ($week->totalMinutes >= 0) {
$cumulative50 += $week->base50Minutes + $week->bonus50Minutes;
$cumulative25 += $week->base25Minutes + $week->bonus25Minutes;
} else {
$deficit = -$week->totalMinutes;
$from50 = min($deficit, max(0, $cumulative50));
$from25 = $deficit - $from50;
$cumulative50 -= $from50;
$cumulative25 -= $from25;
$summary->weeks[$i] = new EmployeeRttWeekSummary(
month: $week->month,
weekNumber: $week->weekNumber,
weekStart: $week->weekStart,
weekEnd: $week->weekEnd,
overtimeMinutes: $week->overtimeMinutes,
base25Minutes: $from25 > 0 ? -$from25 : 0,
bonus25Minutes: 0,
base50Minutes: $from50 > 0 ? -$from50 : 0,
bonus50Minutes: 0,
totalMinutes: $week->totalMinutes,
);
}
}
$payments = $this->rttPaymentRepository->findByEmployeeAndYear($employee, $year);
$monthBuckets = [];

View File

@@ -9,13 +9,10 @@ use ApiPlatform\State\ProviderInterface;
use App\Entity\Employee;
use App\Enum\ContractNature;
use App\Enum\ContractType;
use App\Enum\LeaveRuleCode;
use App\Enum\TrackingMode;
use App\Repository\EmployeeLeaveBalanceRepository;
use App\Repository\EmployeeRepository;
use App\Repository\EmployeeRttBalanceRepository;
use App\Repository\EmployeeRttPaymentRepository;
use App\Service\PublicHolidayServiceInterface;
use App\Service\Rtt\RttRecoveryComputationService;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
@@ -30,8 +27,7 @@ class LeaveRecapPrintProvider implements ProviderInterface
public function __construct(
private Environment $twig,
private EmployeeRepository $employeeRepository,
private EmployeeLeaveBalanceRepository $leaveBalanceRepository,
private PublicHolidayServiceInterface $publicHolidayService,
private EmployeeLeaveSummaryProvider $leaveSummaryProvider,
private RttRecoveryComputationService $rttRecoveryService,
private EmployeeRttBalanceRepository $rttBalanceRepository,
private EmployeeRttPaymentRepository $rttPaymentRepository,
@@ -95,26 +91,24 @@ class LeaveRecapPrintProvider implements ProviderInterface
$nature = ContractNature::tryFrom($employee->getCurrentContractNature());
$isInterim = ContractNature::INTERIM === $nature;
$acquiredDays = 0.0;
$cpN1Remaining = 0.0;
$cpN = '-';
$acquiredSaturdays = '-';
$rtt = '-';
if (!$isInterim) {
$leaveYear = $this->resolveLeaveYear($employee, $today);
$ruleCode = $isForfait ? LeaveRuleCode::FORFAIT_218 : LeaveRuleCode::CDI_CDD_NON_FORFAIT;
$balance = $this->leaveBalanceRepository->findOneByEmployeeRuleAndYear($employee, $ruleCode, $leaveYear);
$leaveYear = $this->leaveSummaryProvider->resolveLeaveYearForToday($employee);
$yearSummary = $this->leaveSummaryProvider->computeYearSummary($employee, $leaveYear);
if (null !== $balance) {
$acquiredDays = $balance->getOpeningDays();
$acquiredSaturdays = $isForfait ? '-' : (string) $balance->getOpeningSaturdays();
}
if ($isForfait) {
try {
$cpN = (string) $this->computeForfaitAcquiredDays($employee, $today);
} catch (Throwable) {
$cpN = '-';
if (null !== $yearSummary) {
if ($isForfait) {
$cpN1Remaining = $yearSummary['previousYearRemainingDays'];
$cpN = (string) $yearSummary['acquiredDays'];
$acquiredSaturdays = '-';
} else {
$cpN1Remaining = $yearSummary['remainingDays'];
$cpN = (string) $yearSummary['accruingDays'];
$acquiredSaturdays = (string) $yearSummary['remainingSaturdays'];
}
}
@@ -131,31 +125,23 @@ class LeaveRecapPrintProvider implements ProviderInterface
'lastName' => $employee->getLastName(),
'firstName' => $employee->getFirstName(),
'contractName' => $contractName,
'acquiredDays' => $acquiredDays,
'cpN1Remaining' => $cpN1Remaining,
'cpN' => $cpN,
'acquiredSaturdays' => $acquiredSaturdays,
'rtt' => $rtt,
];
}
private function resolveLeaveYear(Employee $employee, DateTimeImmutable $today): int
{
if (ContractType::FORFAIT === $employee->getContract()?->getType()) {
return (int) $today->format('Y');
}
$month = (int) $today->format('n');
$year = (int) $today->format('Y');
return $month >= 6 ? $year + 1 : $year;
}
private function computeAvailableRttMinutes(Employee $employee, DateTimeImmutable $today): int
{
$month = (int) $today->format('n');
$year = (int) $today->format('Y');
$exerciseYear = $month >= 6 ? $year + 1 : $year;
// Exclude incomplete current week: limit to last Sunday
$isoDay = (int) $today->format('N');
$limitDate = 7 === $isoDay ? $today : $today->modify('last sunday');
// Carry from previous exercise
$carry = 0;
$balance = $this->rttBalanceRepository->findOneByEmployeeAndYear($employee, $exerciseYear);
@@ -166,8 +152,8 @@ class LeaveRecapPrintProvider implements ProviderInterface
$carry = $previousTotal->totalMinutes;
}
// Current exercise
$current = $this->rttRecoveryService->computeTotalRecoveryForExercise($employee, $exerciseYear);
// Current exercise (limited to completed weeks)
$current = $this->rttRecoveryService->computeTotalRecoveryForExercise($employee, $exerciseYear, $limitDate);
// Paid RTT
$paid = 0;
@@ -179,41 +165,6 @@ class LeaveRecapPrintProvider implements ProviderInterface
return $carry + $current->totalMinutes - $paid;
}
private function computeForfaitAcquiredDays(Employee $employee, DateTimeImmutable $today): float
{
$year = (int) $today->format('Y');
$from = new DateTimeImmutable(sprintf('%d-01-01', $year));
$to = new DateTimeImmutable(sprintf('%d-12-31', $year));
$contractStartRaw = $employee->getCurrentContractStartDate();
if (null !== $contractStartRaw && '' !== trim($contractStartRaw)) {
$contractStart = DateTimeImmutable::createFromFormat('!Y-m-d', trim($contractStartRaw));
if ($contractStart instanceof DateTimeImmutable && $contractStart > $from) {
$from = $contractStart;
}
}
$contractEndRaw = $employee->getCurrentContractEndDate();
if (null !== $contractEndRaw && '' !== trim($contractEndRaw)) {
$contractEnd = DateTimeImmutable::createFromFormat('!Y-m-d', trim($contractEndRaw));
if ($contractEnd instanceof DateTimeImmutable && $contractEnd < $to) {
$to = $contractEnd;
}
}
$holidays = $this->publicHolidayService->getHolidaysDayByYears('metropole', (string) $year);
$businessDays = 0;
for ($cursor = $from; $cursor <= $to; $cursor = $cursor->modify('+1 day')) {
$weekDay = (int) $cursor->format('N');
if ($weekDay <= 5 && !isset($holidays[$cursor->format('Y-m-d')])) {
++$businessDays;
}
}
return (float) max(0, $businessDays - 218);
}
private function formatMinutes(int $minutes): string
{
if (0 === $minutes) {