From 4de891579cc19cb1a880a65c7ca9369d5f648fe0 Mon Sep 17 00:00:00 2001 From: tristan Date: Fri, 20 Mar 2026 11:53:24 +0100 Subject: [PATCH] =?UTF-8?q?feat=20:=20ajout=20des=20cong=C3=A9s=20bonus=20?= =?UTF-8?q?pour=20les=20forfaits=20si=20ils=20travaillent=20un=20weekend?= =?UTF-8?q?=20ou=20f=C3=A9ri=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 1 + doc/functional-rules.md | 1 + src/Repository/WorkHourRepository.php | 51 ++++++++++++++++++++++ src/State/EmployeeLeaveSummaryProvider.php | 13 +++++- 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4dd81ec..5c1184c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,6 +46,7 @@ - CUSTOM contracts (weeklyHours ≠ 35 and ≠ 39, not INTERIM/FORFAIT): reference = actual contractual hours, no 25%/50% bonuses (1h overtime = 1h recovery), deficit doesn't impact balance - INTERIM: no overtime bonuses, no recovery time - Driver contracts: no overtime calculation +- FORFAIT weekend/holiday bonus: each weekend or public holiday day worked gives bonus leave (full day if morning+afternoon, 0.5 if only one). Added to acquired days, no cap. PRESENCE mode only. ## Frais (MileageAllowance) - Onglet "Frais" (anciennement "Frais Kms") sur la fiche employé diff --git a/doc/functional-rules.md b/doc/functional-rules.md index 56b7e32..9c5cf1b 100644 --- a/doc/functional-rules.md +++ b/doc/functional-rules.md @@ -231,6 +231,7 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer. - en cas de suspension en cours de mois, l'acquisition est proratisée en jours ouvrés (lun-ven hors fériés) travaillés / 22 - contrat `FORFAIT`: - base annuelle: `jours ouvrés de l'exercice (lundi-vendredi, hors jours fériés métropole) - 218` + - bonus weekend/férié: chaque jour travaillé un weekend ou jour férié donne 1 jour de congé supplémentaire (journée ≥ 5h = 1.0 jour, demi-journée > 0h et < 5h = 0.5 jour), sans plafond - prorata: en cas de démarrage/fin de contrat en cours d'année civile, le calcul ne couvre que l'intervalle actif du contrat dans l'année - reste à prendre: `acquis - absences` (toutes absences, demi-journées incluses) - pas de samedi (`0`) diff --git a/src/Repository/WorkHourRepository.php b/src/Repository/WorkHourRepository.php index 68f7c7e..eede627 100644 --- a/src/Repository/WorkHourRepository.php +++ b/src/Repository/WorkHourRepository.php @@ -191,6 +191,57 @@ final class WorkHourRepository extends ServiceEntityRepository implements WorkHo return $result; } + /** + * Count weekend and public holiday worked days for forfait bonus leave (PRESENCE mode only). + * Morning + afternoon = 1.0 day, one only = 0.5 day. + * + * @param list $publicHolidayDates Y-m-d formatted weekday public holiday dates + */ + public function countWeekendAndHolidayWorkedDays(Employee $employee, DateTimeImmutable $from, DateTimeImmutable $to, array $publicHolidayDates = []): float + { + $targetDates = []; + + // Collect weekend dates in range + for ($cursor = $from; $cursor <= $to; $cursor = $cursor->modify('+1 day')) { + if ((int) $cursor->format('N') >= 6) { + $targetDates[] = $cursor; + } + } + + // Add weekday public holidays + foreach ($publicHolidayDates as $date) { + $targetDates[] = new DateTimeImmutable($date); + } + + if ([] === $targetDates) { + return 0.0; + } + + $dateStrings = array_map(static fn (DateTimeImmutable $d): string => $d->format('Y-m-d'), $targetDates); + + /** @var list $rows */ + $rows = $this->createQueryBuilder('w') + ->andWhere('w.employee = :employee') + ->andWhere('w.workDate IN (:dates)') + ->andWhere('w.isPresentMorning = true OR w.isPresentAfternoon = true') + ->setParameter('employee', $employee) + ->setParameter('dates', $dateStrings) + ->getQuery() + ->getResult() + ; + + $total = 0.0; + foreach ($rows as $row) { + if ($row->isPresentMorning() && $row->isPresentAfternoon()) { + $total += 1.0; + } else { + $total += 0.5; + } + } + + return $total; + } + /** * Return the set of Y-m-d dates where the employee has worked hours on the given dates. * diff --git a/src/State/EmployeeLeaveSummaryProvider.php b/src/State/EmployeeLeaveSummaryProvider.php index 35857f3..6223902 100644 --- a/src/State/EmployeeLeaveSummaryProvider.php +++ b/src/State/EmployeeLeaveSummaryProvider.php @@ -535,10 +535,21 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface $type = $employee->getContract()?->getType(); if (ContractType::FORFAIT === $type) { $businessDaysInPeriod = $this->countBusinessDays($from, $to); + $publicHolidays = $this->buildPublicHolidayMap($from, $to); + $weekdayHolidays = array_filter( + array_keys($publicHolidays), + static fn (string $date): bool => (int) new DateTimeImmutable($date)->format('N') <= 5 + ); + $bonusDays = $this->workHourRepository->countWeekendAndHolidayWorkedDays( + $employee, + $from, + $to, + array_values($weekdayHolidays) + ); return [ 'ruleCode' => LeaveRuleCode::FORFAIT_218->value, - 'acquiredDays' => (float) max(0, $businessDaysInPeriod - self::FORFAIT_TARGET_WORKED_DAYS), + 'acquiredDays' => (float) max(0, $businessDaysInPeriod - self::FORFAIT_TARGET_WORKED_DAYS) + $bonusDays, 'acquiredSaturdays' => 0.0, 'accrualPerMonth' => 0.0, 'saturdayAccrualPerMonth' => 0.0,