From 04f90afc585a9027aa1321209909a33380bcdc70 Mon Sep 17 00:00:00 2001 From: tristan Date: Mon, 16 Mar 2026 18:17:58 +0100 Subject: [PATCH] =?UTF-8?q?feat=20:=20ajout=20de=20la=20r=C3=A8gle=20de=20?= =?UTF-8?q?d=C3=A9compte=20des=20RTT=20et=20correction=20du=20r=C3=A9cap?= =?UTF-8?q?=20cong=C3=A9s=20et=20RTT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 4 + config/services.yaml | 4 + doc/functional-rules.md | 15 +- frontend/components/employees/RttTab.vue | 164 +++++++++++++----- .../Rtt/RttRecoveryComputationService.php | 19 +- src/State/EmployeeLeaveSummaryProvider.php | 2 +- src/State/EmployeeRttSummaryProvider.php | 36 +++- src/State/LeaveRecapPrintProvider.php | 89 +++------- templates/leave-recap/print.html.twig | 8 +- 9 files changed, 218 insertions(+), 123 deletions(-) diff --git a/.env b/.env index 211ce90..e6ac054 100644 --- a/.env +++ b/.env @@ -36,6 +36,10 @@ DEFAULT_URI=http://localhost DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" ###< doctrine/doctrine-bundle ### +###> app ### +RTT_START_DATE=2026-02-23 +###< app ### + ###> nelmio/cors-bundle ### CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' ###< nelmio/cors-bundle ### diff --git a/config/services.yaml b/config/services.yaml index 2e913f9..2ffc895 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -26,6 +26,10 @@ services: arguments: $holidayUrl: '%env(HOLIDAY_URL)%' + App\Service\Rtt\RttRecoveryComputationService: + arguments: + $rttStartDate: '%env(RTT_START_DATE)%' + App\Repository\Contract\AbsenceReadRepositoryInterface: '@App\Repository\AbsenceRepository' App\Repository\Contract\EmployeeContractPeriodReadRepositoryInterface: '@App\Repository\EmployeeContractPeriodRepository' App\Repository\Contract\EmployeeScopedRepositoryInterface: '@App\Repository\EmployeeRepository' diff --git a/doc/functional-rules.md b/doc/functional-rules.md index 90b511b..0bb6dec 100644 --- a/doc/functional-rules.md +++ b/doc/functional-rules.md @@ -116,6 +116,12 @@ Documents complementaires: - contrats >= 39h: de 39h à 43h - Tranche 50%: - au-delà de 43h +- Date de début RTT (`RTT_START_DATE` dans `.env`): + - les semaines dont la fin est antérieure à cette date sont ignorées dans le calcul de récupération + - permet d'éviter les déficits fictifs avant la mise en service du logiciel +- Semaine en déficit (heures travaillées < heures contrat): + - le déficit est déduit du cumul RTT : d'abord des heures à 50%, puis des heures à 25% + - si aucun solde 50% ni 25%, les heures à 25% deviennent négatives - Nature `INTERIM`: - pas de bonus 25% - pas de bonus 50% @@ -278,6 +284,10 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer. - `rate`: taux de majoration, valeurs `25` ou `50` - les heures payées sont soustraites du disponible RTT (`availableMinutes -= totalPaidMinutes`) - affichage: 2 lignes par mois dans le tableau (25% et 50%) + - colonnes Total 25% et Total 50%: somme base + bonus de chaque tranche + - ligne Report N-1 (carry rollover): affichée en juin uniquement si carry > 0 + - ligne Report mois précédent: solde cumulé (carry N-1 + semaines antérieures − paiements antérieurs), affichée à partir de juillet (masquée si nul) + - Reste = Report cumulé + Total du mois − Payé du mois (balance courante en fin de mois) - affichage: - le compteur global RTT est affiché en **heures** (format `Xh00`) @@ -295,8 +305,9 @@ Tous les filtres checkbox sont cochés par défaut à l'ouverture du drawer. |---------|---------| | Nom | lastName + firstName | | Contrat | Contract.name | -| CP Acquis (N-1) | Report de l'exercice précédent (acquiredDays du computeYearSummary) | -| Samedi acquis | Report N-1 samedis. Forfait: `-` | +| CP N-1 restant | CDI/CDD: acquis N-1 − pris sur N-1. Forfait: report N-1 restant | +| Samedi restant | CDI/CDD: samedis acquis N-1 − pris. Forfait: `-` | +| CP N | Forfait: jours acquis année civile. Non-forfait: en cours d'acquisition | | RTT | Minutes disponibles (report N-1 + acquis N - payés). Format `X h Y m`. Forfait et INTERIM: `-` | ## 11) Récapitulatif Salaire (PDF mensuel) diff --git a/frontend/components/employees/RttTab.vue b/frontend/components/employees/RttTab.vue index f8d7d14..a3a7b2f 100644 --- a/frontend/components/employees/RttTab.vue +++ b/frontend/components/employees/RttTab.vue @@ -40,36 +40,55 @@ - - - - - - + + + + + + + + - + + - + + - - + + - + + - + + + + + + + + + + + + + + + {{ formatMinutes(week.base25Minutes) }}0 h - + - + - + + - + + @@ -121,9 +150,11 @@ - + + - + + @@ -131,10 +162,12 @@ - - - - + + + + + + @@ -290,44 +323,91 @@ const paddedWeeks = computed((): (EmployeeRttWeekSummary | null)[] => { return padded }) -// --- Report row --- +// --- Carry row (RTT rollover from previous year, June only) --- -const reportMonth = computed(() => { +const carryMonth = computed(() => { if (!props.summary) return 6 - const carryMonth = props.summary.carryMonth - // Report appears in the month AFTER carryMonth (wrapping 12 -> 1) - return carryMonth >= 12 ? 1 : carryMonth + 1 + const cm = props.summary.carryMonth + return cm >= 12 ? 1 : cm + 1 }) -const showReportRow = computed(() => { +const showCarryRow = computed(() => { return ( - currentMonth.value === reportMonth.value && + currentMonth.value === carryMonth.value && (props.summary?.carryFromPreviousYearMinutes ?? 0) > 0 ) }) -// --- Totals --- +// --- Month report row (cumulated balance from previous months) --- + +// Months of the exercise in order, starting from the carry month +const exerciseMonths = computed((): number[] => { + const start = carryMonth.value + const startIdx = orderedMonths.indexOf(start as (typeof orderedMonths)[number]) + if (startIdx === -1) return [...orderedMonths] + return [...orderedMonths.slice(startIdx), ...orderedMonths.slice(0, startIdx)] +}) + +const monthReport = computed(() => { + if (!props.summary) return { base25: 0, bonus25: 0, total25: 0, base50: 0, bonus50: 0, total50: 0, total: 0 } + + const cm = currentMonth.value + const cmIdx = exerciseMonths.value.indexOf(cm) + const previousMonths = exerciseMonths.value.slice(0, cmIdx) + + // Start from carry (included in the cumulation) + let base25 = props.summary.carryBase25Minutes + let bonus25 = props.summary.carryBonus25Minutes + let base50 = props.summary.carryBase50Minutes + let bonus50 = props.summary.carryBonus50Minutes + let total = props.summary.carryFromPreviousYearMinutes + + // Add weeks from previous months + for (const w of props.summary.weeks) { + if (previousMonths.includes(w.month)) { + base25 += w.base25Minutes + bonus25 += w.bonus25Minutes + base50 += w.base50Minutes + bonus50 += w.bonus50Minutes + total += w.totalMinutes + } + } + + // Subtract payments from previous months + for (const p of props.summary.monthPayments) { + if (previousMonths.includes(p.month)) { + base25 -= p.paidBase25Minutes + bonus25 -= p.paidBonus25Minutes + base50 -= p.paidBase50Minutes + bonus50 -= p.paidBonus50Minutes + total -= (p.paidBase25Minutes + p.paidBonus25Minutes + p.paidBase50Minutes + p.paidBonus50Minutes) + } + } + + return { base25, bonus25, total25: base25 + bonus25, base50, bonus50, total50: base50 + bonus50, total } +}) + +const showMonthReportRow = computed(() => { + // Not on the carry month — carry row handles that + if (currentMonth.value === carryMonth.value) return false + const r = monthReport.value + return r.total !== 0 +}) + +// --- Totals (current month weeks only) --- const totals = computed(() => { const weeks = weeksForCurrentMonth.value - const base = { + return { overtime: weeks.reduce((s, w) => s + w.overtimeMinutes, 0), base25: weeks.reduce((s, w) => s + w.base25Minutes, 0), bonus25: weeks.reduce((s, w) => s + w.bonus25Minutes, 0), + total25: weeks.reduce((s, w) => s + w.base25Minutes + w.bonus25Minutes, 0), base50: weeks.reduce((s, w) => s + w.base50Minutes, 0), bonus50: weeks.reduce((s, w) => s + w.bonus50Minutes, 0), + total50: weeks.reduce((s, w) => s + w.base50Minutes + w.bonus50Minutes, 0), total: weeks.reduce((s, w) => s + w.totalMinutes, 0), } - - if (showReportRow.value && props.summary) { - base.base25 += props.summary.carryBase25Minutes - base.bonus25 += props.summary.carryBonus25Minutes - base.base50 += props.summary.carryBase50Minutes - base.bonus50 += props.summary.carryBonus50Minutes - base.total += props.summary.carryFromPreviousYearMinutes - } - - return base }) const currentPayment = computed(() => { @@ -342,7 +422,7 @@ const paidTotal = computed(() => { }) const resteTotal = computed(() => { - return totals.value.total + paidTotal.value + return monthReport.value.total + totals.value.total + paidTotal.value }) // --- Format --- diff --git a/src/Service/Rtt/RttRecoveryComputationService.php b/src/Service/Rtt/RttRecoveryComputationService.php index 772cacc..3883ef1 100644 --- a/src/Service/Rtt/RttRecoveryComputationService.php +++ b/src/Service/Rtt/RttRecoveryComputationService.php @@ -21,13 +21,18 @@ use DateTimeImmutable; final readonly class RttRecoveryComputationService { + private ?DateTimeImmutable $rttStartDate; + public function __construct( private WorkHourRepository $workHourRepository, private AbsenceRepository $absenceRepository, private AbsenceSegmentsResolver $absenceSegmentsResolver, private WorkedHoursCreditPolicy $workedHoursCreditPolicy, private EmployeeContractResolver $contractResolver, - ) {} + string $rttStartDate = '', + ) { + $this->rttStartDate = '' !== $rttStartDate ? new DateTimeImmutable($rttStartDate) : null; + } /** * @return array{DateTimeImmutable, DateTimeImmutable} @@ -71,7 +76,7 @@ final readonly class RttRecoveryComputationService return $weeks; } - public function computeTotalRecoveryForExercise(Employee $employee, int $exerciseYear): WeekRecoveryDetail + public function computeTotalRecoveryForExercise(Employee $employee, int $exerciseYear, ?DateTimeImmutable $limitDate = null): WeekRecoveryDetail { [$from, $to] = $this->resolveExerciseBounds($exerciseYear); $weeks = $this->buildWeeksForExercise($from, $to); @@ -85,7 +90,7 @@ final readonly class RttRecoveryComputationService $weeks ); - $byWeek = $this->computeRecoveryByWeek($employee, $weekRanges, $from, $to, null); + $byWeek = $this->computeRecoveryByWeek($employee, $weekRanges, $from, $to, $limitDate); $total = new WeekRecoveryDetail(); foreach ($byWeek as $detail) { @@ -172,6 +177,12 @@ final readonly class RttRecoveryComputationService continue; } + if ($this->rttStartDate instanceof DateTimeImmutable && $effectiveEnd < $this->rttStartDate) { + $results[$weekKey] = new WeekRecoveryDetail(); + + continue; + } + $weekDays = []; for ($cursor = $effectiveStart; $cursor <= $effectiveEnd; $cursor = $cursor->modify('+1 day')) { $weekDays[] = $cursor->format('Y-m-d'); @@ -203,7 +214,7 @@ final readonly class RttRecoveryComputationService $overtime25StartMinutes = $this->computeWeeklyOvertime25StartMinutes($weekDays, $employeeContractsByDate); $weeklyOvertimeTotalMinutes = $isWeekPresenceTracking ? 0 - : max(0, $weeklyTotalMinutes - $overtimeReferenceMinutes); + : $weeklyTotalMinutes - $overtimeReferenceMinutes; $base25 = ($isWeekPresenceTracking || $disableOvertimeBonuses) ? 0 : max(0, min($weeklyTotalMinutes, 43 * 60) - $overtime25StartMinutes); $bonus25 = ($isWeekPresenceTracking || $disableOvertimeBonuses) ? 0 : (int) round($base25 * 0.25); diff --git a/src/State/EmployeeLeaveSummaryProvider.php b/src/State/EmployeeLeaveSummaryProvider.php index 7f54afb..fd30908 100644 --- a/src/State/EmployeeLeaveSummaryProvider.php +++ b/src/State/EmployeeLeaveSummaryProvider.php @@ -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; } diff --git a/src/State/EmployeeRttSummaryProvider.php b/src/State/EmployeeRttSummaryProvider.php index 3deda78..ecc9731 100644 --- a/src/State/EmployeeRttSummaryProvider.php +++ b/src/State/EmployeeRttSummaryProvider.php @@ -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 = []; diff --git a/src/State/LeaveRecapPrintProvider.php b/src/State/LeaveRecapPrintProvider.php index 9326563..05c2edc 100644 --- a/src/State/LeaveRecapPrintProvider.php +++ b/src/State/LeaveRecapPrintProvider.php @@ -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) { diff --git a/templates/leave-recap/print.html.twig b/templates/leave-recap/print.html.twig index e37ee42..1880bd9 100644 --- a/templates/leave-recap/print.html.twig +++ b/templates/leave-recap/print.html.twig @@ -86,9 +86,9 @@ - + + - @@ -105,9 +105,9 @@ - - + +
Semaine Heure Base25%25%Total 25% Base50%50%Total 50% Total
Report - {{ formatMinutes(summary!.carryBase25Minutes) }}{{ formatMinutes(summary!.carryBonus25Minutes) }}{{ formatMinutes(summary!.carryBonus25Minutes) }}{{ formatMinutes(summary!.carryBase25Minutes + summary!.carryBonus25Minutes) }} {{ formatMinutes(summary!.carryBase50Minutes) }}{{ formatMinutes(summary!.carryBonus50Minutes) }}{{ formatMinutes(summary!.carryBonus50Minutes) }}{{ formatMinutes(summary!.carryBase50Minutes + summary!.carryBonus50Minutes) }} {{ formatMinutes(summary!.carryFromPreviousYearMinutes) }}
Report-{{ formatMinutes(monthReport.base25) }}{{ formatMinutes(monthReport.bonus25) }}{{ formatMinutes(monthReport.total25) }}{{ formatMinutes(monthReport.base50) }}{{ formatMinutes(monthReport.bonus50) }}{{ formatMinutes(monthReport.total50) }}{{ formatMinutes(monthReport.total) }}
+ {{ formatMinutes(week.bonus25Minutes) }} 0 h + {{ formatMinutes(week.base25Minutes + week.bonus25Minutes) }} + 0 h + {{ formatMinutes(week.base50Minutes) }} 0 h + {{ formatMinutes(week.bonus50Minutes) }} 0 h + {{ formatMinutes(week.base50Minutes + week.bonus50Minutes) }} + 0 h + {{ formatMinutes(week.totalMinutes) }} 0 h @@ -110,9 +137,11 @@ Total {{ formatMinutes(totals.overtime) }} {{ formatMinutes(totals.base25) }}{{ formatMinutes(totals.bonus25) }}{{ formatMinutes(totals.bonus25) }}{{ formatMinutes(totals.total25) }} {{ formatMinutes(totals.base50) }}{{ formatMinutes(totals.bonus50) }}{{ formatMinutes(totals.bonus50) }}{{ formatMinutes(totals.total50) }} {{ formatMinutes(totals.total) }}
Payé - {{ currentPayment ? formatMinutes(-currentPayment.paidBase25Minutes) : '0 h' }}{{ currentPayment ? formatMinutes(-currentPayment.paidBonus25Minutes) : '0 h' }}{{ currentPayment ? formatMinutes(-currentPayment.paidBonus25Minutes) : '0 h' }}{{ currentPayment ? formatMinutes(-(currentPayment.paidBase25Minutes + currentPayment.paidBonus25Minutes)) : '0 h' }} {{ currentPayment ? formatMinutes(-currentPayment.paidBase50Minutes) : '0 h' }}{{ currentPayment ? formatMinutes(-currentPayment.paidBonus50Minutes) : '0 h' }}{{ currentPayment ? formatMinutes(-currentPayment.paidBonus50Minutes) : '0 h' }}{{ currentPayment ? formatMinutes(-(currentPayment.paidBase50Minutes + currentPayment.paidBonus50Minutes)) : '0 h' }} {{ formatMinutes(paidTotal) }}
Reste -{{ formatMinutes(totals.base25 - (currentPayment?.paidBase25Minutes ?? 0)) }}{{ formatMinutes(totals.bonus25 - (currentPayment?.paidBonus25Minutes ?? 0)) }}{{ formatMinutes(totals.base50 - (currentPayment?.paidBase50Minutes ?? 0)) }}{{ formatMinutes(totals.bonus50 - (currentPayment?.paidBonus50Minutes ?? 0)) }}{{ formatMinutes(monthReport.base25 + totals.base25 - (currentPayment?.paidBase25Minutes ?? 0)) }}{{ formatMinutes(monthReport.bonus25 + totals.bonus25 - (currentPayment?.paidBonus25Minutes ?? 0)) }}{{ formatMinutes(monthReport.total25 + totals.total25 - (currentPayment?.paidBase25Minutes ?? 0) - (currentPayment?.paidBonus25Minutes ?? 0)) }}{{ formatMinutes(monthReport.base50 + totals.base50 - (currentPayment?.paidBase50Minutes ?? 0)) }}{{ formatMinutes(monthReport.bonus50 + totals.bonus50 - (currentPayment?.paidBonus50Minutes ?? 0)) }}{{ formatMinutes(monthReport.total50 + totals.total50 - (currentPayment?.paidBase50Minutes ?? 0) - (currentPayment?.paidBonus50Minutes ?? 0)) }} {{ formatMinutes(resteTotal) }}
Nom ContratCP Acquis
(N-1)
CP N-1
restant
Samedi
restant
CP
N
Samedi
acquis
RTT Observations
{{ row.lastName }} {{ row.firstName }} {{ row.contractName ?? '' }}{{ row.acquiredDays }}{{ row.cpN }}{{ row.cpN1Remaining }} {{ row.acquiredSaturdays }}{{ row.cpN }} {{ row.rtt }}