From d2122aa9c0e552c43216b7f608fe243ba4148a5c Mon Sep 17 00:00:00 2001 From: tristan Date: Thu, 11 Jun 2026 16:57:51 +0200 Subject: [PATCH] =?UTF-8?q?feat(overtime-contingent)=20:=20calculateur=20p?= =?UTF-8?q?ur=20heures=20supp=20pay=C3=A9es=20par=20ann=C3=A9e=20civile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- .../OvertimePaidContingentCalculator.php | 54 ++++++++++++ .../OvertimePaidContingentCalculatorTest.php | 88 +++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/Service/WorkHours/OvertimePaidContingentCalculator.php create mode 100644 tests/Service/WorkHours/OvertimePaidContingentCalculatorTest.php diff --git a/src/Service/WorkHours/OvertimePaidContingentCalculator.php b/src/Service/WorkHours/OvertimePaidContingentCalculator.php new file mode 100644 index 0000000..c521786 --- /dev/null +++ b/src/Service/WorkHours/OvertimePaidContingentCalculator.php @@ -0,0 +1,54 @@ + Mai N + mois) + * en agrégats par ANNEE CIVILE (Janv-Déc). Heures payées = base25 + base50, + * hors majoration (bonus). Plafond : 350 h chauffeur, 220 h autres. + */ +final readonly class OvertimePaidContingentCalculator +{ + public const int CAP_HOURS_DRIVER = 350; + public const int CAP_HOURS_DEFAULT = 220; + + /** + * @param iterable $payments paiements d'un employé + * (typiquement exercices civilYear et civilYear+1) + * + * @return array clé 1..12 -> minutes base payées (base25+base50) + */ + public function monthlyBaseMinutes(iterable $payments, int $civilYear): array + { + $months = array_fill(1, 12, 0); + + foreach ($payments as $payment) { + $month = $payment->getMonth(); + $paymentCivilYear = $month >= 6 ? $payment->getYear() - 1 : $payment->getYear(); + if ($paymentCivilYear !== $civilYear) { + continue; + } + + $months[$month] += $payment->getBase25Minutes() + $payment->getBase50Minutes(); + } + + return $months; + } + + /** + * @param iterable $payments + */ + public function totalBaseMinutes(iterable $payments, int $civilYear): int + { + return array_sum($this->monthlyBaseMinutes($payments, $civilYear)); + } + + public function capHours(bool $isDriver): int + { + return $isDriver ? self::CAP_HOURS_DRIVER : self::CAP_HOURS_DEFAULT; + } +} diff --git a/tests/Service/WorkHours/OvertimePaidContingentCalculatorTest.php b/tests/Service/WorkHours/OvertimePaidContingentCalculatorTest.php new file mode 100644 index 0000000..3a5019d --- /dev/null +++ b/tests/Service/WorkHours/OvertimePaidContingentCalculatorTest.php @@ -0,0 +1,88 @@ += 6 -> civil 2025). + // Mars 2026 stocké en exercice 2026 (mois 3 < 6 -> civil 2026). + // Septembre 2026 stocké en exercice 2027 (mois 9 >= 6 -> civil 2026). + // March 2026 payment has a large bonus (999 min) that must be excluded. + $payments = [ + $this->payment(2026, 9, 120, 0), // civil 2025 -> exclu de 2026 + $this->payment(2026, 3, 60, 30, 999), // civil 2026 -> mois 3, bonus ignoré + $this->payment(2027, 9, 100, 20), // civil 2026 -> mois 9 + ]; + + $months = $calc->monthlyBaseMinutes($payments, 2026); + + self::assertSame(90, $months[3]); // 60 + 30 (bonus 999 excluded) + self::assertSame(120, $months[9]); // 100 + 20 + self::assertSame(0, $months[1]); + self::assertSame(0, $months[8]); + self::assertSame(210, $calc->totalBaseMinutes($payments, 2026)); // bonus ignoré + } + + public function testMonth5BelongsToExerciseYearAndMonth6ToPreviousCalendarYear(): void + { + $calc = new OvertimePaidContingentCalculator(); + + $payments = [ + $this->payment(2026, 5, 50, 0), // mai -> civil 2026 + $this->payment(2026, 6, 70, 0), // juin -> civil 2025 + ]; + + self::assertSame(50, $calc->totalBaseMinutes($payments, 2026)); + self::assertSame(70, $calc->totalBaseMinutes($payments, 2025)); + } + + public function testCapHours(): void + { + $calc = new OvertimePaidContingentCalculator(); + + self::assertSame(350, $calc->capHours(true)); + self::assertSame(220, $calc->capHours(false)); + } + + public function testEmptyPaymentsYieldsZeros(): void + { + $calc = new OvertimePaidContingentCalculator(); + $months = $calc->monthlyBaseMinutes([], 2026); + + self::assertSame(0, $months[1]); + self::assertSame(0, $months[12]); + self::assertSame(0, array_sum($months)); + self::assertSame(0, $calc->totalBaseMinutes([], 2026)); + } + + private function payment( + int $exerciseYear, + int $month, + int $base25, + int $base50, + int $bonus25 = 0, + int $bonus50 = 0, + ): EmployeeRttPayment { + return new EmployeeRttPayment() + ->setYear($exerciseYear) + ->setMonth($month) + ->setBase25Minutes($base25) + ->setBase50Minutes($base50) + ->setBonus25Minutes($bonus25) + ->setBonus50Minutes($bonus50) + ; + } +}