[#SIRH] Récap salaire: congés N-1 forfait non affichés et comptés en présence
L'export récap salaire comptait tous les congés 'C' d'un forfait et ne créditait aucune présence sur les jours de congé. Or un congé imputé sur le stock N-1 ne doit pas s'afficher et doit compter comme jour de présence (règle déjà appliquée dans la fiche employé via EmployeeLeaveSummaryProvider). - Nouvelle méthode publique resolvePreviousYearTakenDays() (mutualise le budget N-1 avec la fiche: phase courante + recalcul jours payés). - SalaryRecapPrintProvider charge les congés depuis le 1er janvier et consomme le budget N-1 chronologiquement (splitForfaitCongesByN1): jours couverts N-1 retirés de l'affichage congés et ajoutés à la présence; au-delà = congés N. - Non-forfait / budget N-1 = 0: comportement inchangé. Vérifié end-to-end sur données prod (SARAZI mai: +1 présence, 4 congés affichés; LIOT/ODUNCU budget 0 après paiement N-1 -> congés affichés). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ use App\Repository\ObservationRepository;
|
||||
use App\Repository\WorkHourRepository;
|
||||
use App\Service\Contracts\EmployeeContractResolver;
|
||||
use App\Service\PublicHolidayServiceInterface;
|
||||
use App\Service\WorkHours\AbsenceSegmentsResolver;
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use Dompdf\Dompdf;
|
||||
@@ -42,6 +43,8 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
private ObservationRepository $observationRepository,
|
||||
private EmployeeContractResolver $contractResolver,
|
||||
private PublicHolidayServiceInterface $publicHolidayService,
|
||||
private EmployeeLeaveSummaryProvider $leaveSummaryProvider,
|
||||
private AbsenceSegmentsResolver $absenceSegmentsResolver,
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): Response
|
||||
@@ -65,6 +68,13 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
|
||||
$year = (int) $from->format('Y');
|
||||
$monthNumber = (int) $from->format('n');
|
||||
|
||||
// Congés depuis le début de l'exercice forfait (année civile) jusqu'à la fin du mois :
|
||||
// nécessaires pour consommer chronologiquement le budget N-1 d'un forfait (un congé
|
||||
// imputé N-1 ne doit ni s'afficher ni manquer en présence sur le récap).
|
||||
$yearStart = new DateTimeImmutable(sprintf('%d-01-01', $year));
|
||||
$ytdAbsences = $this->absenceRepository->findForPrint($yearStart, $to, $employees);
|
||||
$ytdAbsenceMap = $this->buildAbsenceMap($ytdAbsences);
|
||||
$rttPayments = $this->rttPaymentRepository->findByYearAndMonth($year, $monthNumber);
|
||||
|
||||
$bonuses = $this->bonusRepository->findByMonth($from, $to);
|
||||
@@ -83,7 +93,7 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
$mileageMap = $this->buildMileageMap($mileages);
|
||||
$observationMap = $this->buildObservationMap($observations);
|
||||
|
||||
$siteGroups = $this->aggregateBySite($employees, $days, $contractMap, $driverMap, $workHourMap, $absenceMap, $rttPaymentMap, $bonusMap, $mileageMap, $observationMap, $holidayMap);
|
||||
$siteGroups = $this->aggregateBySite($employees, $days, $contractMap, $driverMap, $workHourMap, $absenceMap, $rttPaymentMap, $bonusMap, $mileageMap, $observationMap, $holidayMap, $ytdAbsenceMap, $year, $from, $to);
|
||||
|
||||
$options = new Options();
|
||||
$options->set('isRemoteEnabled', true);
|
||||
@@ -264,6 +274,10 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
array $mileageMap,
|
||||
array $observationMap,
|
||||
array $holidayMap,
|
||||
array $ytdAbsenceMap,
|
||||
int $year,
|
||||
DateTimeImmutable $monthFrom,
|
||||
DateTimeImmutable $monthTo,
|
||||
): array {
|
||||
$siteGroups = [];
|
||||
|
||||
@@ -286,6 +300,10 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
$mileageMap[$employeeId] ?? 0.0,
|
||||
$observationMap[$employeeId] ?? '',
|
||||
$holidayMap,
|
||||
$ytdAbsenceMap[$employeeId] ?? [],
|
||||
$year,
|
||||
$monthFrom,
|
||||
$monthTo,
|
||||
);
|
||||
|
||||
if (!isset($siteGroups[$siteId])) {
|
||||
@@ -315,6 +333,10 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
float $mileageKm,
|
||||
string $observation,
|
||||
array $holidayMap,
|
||||
array $ytdAbsences,
|
||||
int $year,
|
||||
DateTimeImmutable $monthFrom,
|
||||
DateTimeImmutable $monthTo,
|
||||
): array {
|
||||
$contractName = null;
|
||||
$presenceDays = 0.0;
|
||||
@@ -415,7 +437,21 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
}
|
||||
}
|
||||
|
||||
$conges = $this->countAbsencesByCode($absences, ['C']);
|
||||
// Forfait : un congé imputé sur le stock N-1 ne doit pas s'afficher dans le récap
|
||||
// et doit compter comme jour de présence. On consomme le budget N-1 chronologiquement
|
||||
// sur tous les congés de l'exercice (année civile) jusqu'à la fin du mois imprimé.
|
||||
$n1Budget = $isForfait ? $this->leaveSummaryProvider->resolvePreviousYearTakenDays($employee, $year) : 0.0;
|
||||
if ($isForfait && $n1Budget > 0.0) {
|
||||
$ytdConges = array_values(array_filter(
|
||||
$ytdAbsences,
|
||||
static fn (Absence $a): bool => 'C' === $a->getType()?->getCode()
|
||||
));
|
||||
$split = $this->splitForfaitCongesByN1($ytdConges, $n1Budget, $monthFrom, $monthTo);
|
||||
$conges = ['count' => $split['count'], 'dates' => $split['dates']];
|
||||
$presenceDays += $split['n1PresenceDays'];
|
||||
} else {
|
||||
$conges = $this->countAbsencesByCode($absences, ['C']);
|
||||
}
|
||||
$maladie = $this->countAbsencesByCode($absences, ['M', 'AT']);
|
||||
|
||||
$nightHours = round($nightMinutesTotal / 60, 2);
|
||||
@@ -574,6 +610,73 @@ class SalaryRecapPrintProvider implements ProviderInterface
|
||||
return max(0, $end - $start);
|
||||
}
|
||||
|
||||
/**
|
||||
* Répartit les congés ('C') d'un forfait entre N-1 (budget consommé chronologiquement,
|
||||
* non affiché et compté en présence) et N (affiché en congé). Seuls les jours tombant
|
||||
* dans le mois imprimé alimentent le retour ; les congés des mois antérieurs ne servent
|
||||
* qu'à consommer le budget N-1.
|
||||
*
|
||||
* @param list<Absence> $ytdConges congés depuis le début d'exercice jusqu'à la fin du mois
|
||||
*
|
||||
* @return array{count: float, dates: string, n1PresenceDays: float}
|
||||
*/
|
||||
private function splitForfaitCongesByN1(
|
||||
array $ytdConges,
|
||||
float $n1Budget,
|
||||
DateTimeImmutable $monthFrom,
|
||||
DateTimeImmutable $monthTo
|
||||
): array {
|
||||
usort($ytdConges, static fn (Absence $a, Absence $b): int => $a->getStartDate() <=> $b->getStartDate());
|
||||
|
||||
$remaining = $n1Budget;
|
||||
$count = 0.0;
|
||||
$n1PresenceDays = 0.0;
|
||||
$dayKeys = [];
|
||||
|
||||
foreach ($ytdConges as $absence) {
|
||||
$start = DateTimeImmutable::createFromInterface($absence->getStartDate())->setTime(0, 0);
|
||||
$end = DateTimeImmutable::createFromInterface($absence->getEndDate())->setTime(0, 0);
|
||||
|
||||
for ($day = $start; $day <= $end; $day = $day->modify('+1 day')) {
|
||||
if ((int) $day->format('N') >= 6) {
|
||||
continue; // week-ends ignorés
|
||||
}
|
||||
[$am, $pm] = $this->absenceSegmentsResolver->resolveForDate($absence, $day->format('Y-m-d'));
|
||||
$amount = ($am ? 0.5 : 0.0) + ($pm ? 0.5 : 0.0);
|
||||
if ($amount <= 0.0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$covered = 0.0;
|
||||
if ($remaining > 0.0) {
|
||||
$covered = min($remaining, $amount);
|
||||
$remaining -= $covered;
|
||||
}
|
||||
$displayed = $amount - $covered;
|
||||
|
||||
// Seul le mois imprimé alimente le récap ; les mois antérieurs ne font que consommer.
|
||||
if ($day < $monthFrom || $day > $monthTo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$n1PresenceDays += $covered;
|
||||
if ($displayed > 0.0) {
|
||||
$count += $displayed;
|
||||
$dayKeys[] = $day->format('Y-m-d');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort($dayKeys);
|
||||
$dayKeys = array_unique($dayKeys);
|
||||
|
||||
return [
|
||||
'count' => $count,
|
||||
'dates' => implode(', ', $this->mergeDaysIntoPeriods($dayKeys)),
|
||||
'n1PresenceDays' => $n1PresenceDays,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<Absence> $absences
|
||||
* @param list<string> $codes
|
||||
|
||||
Reference in New Issue
Block a user