Files
SIRH/doc/rtt-tab.md
T
2026-06-11 10:26:48 +02:00

91 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Onglet "RTT" — fiche employé
## Vue d'ensemble
L'onglet **RTT** de la fiche employé (`frontend/components/employees/RttTab.vue`) affiche un tableau hebdomadaire détaillé des heures supplémentaires accumulées et payées sur un exercice :
- bandeau de navigation par mois (chevrons gauche/droite) ;
- table semaine par semaine : Heure / Base 25% / 25% / Total 25% / Base 50% / 50% / Total 50% / Total / Cumul ;
- ligne Report (carry N-1 ou cumul mois précédents) ;
- ligne Total mois, ligne Payé, ligne Reste ;
- bouton « + Payer les RTT » dans le bandeau ;
- sélecteur d'exercice en pied de tableau.
L'onglet est **masqué pour les contrats FORFAIT** (filtre `showRttTab` dans `useEmployeeDetailPage`). Les FORFAIT n'accumulent pas de RTT.
## Période affichée
Toujours **Juin (Y-1) → Mai (Y)**. Le champ `EmployeeRttSummary.year` correspond à `Y` (année de fin d'exercice) ; ex. `year=2026` = `01/06/2025 → 31/05/2026`.
## Règle de calcul — contrats CUSTOM (4h, 25h…)
Pour un contrat CUSTOM, la récupération est **plate** (1h sup = 1h récup, sans bonus 25 %/50 %).
Depuis 2026-06, une semaine **travaillée sous les heures contractuelles** produit un **déficit
signé** dans la colonne « Heure » qui **réduit le « Total » et le « Cumul »** (1h manquante =
-1h). Les colonnes Base/25 %/50 % restent à **0** (pas de tranches pour ces contrats). Le cumul
peut devenir négatif ; il est reporté à l'exercice suivant.
Techniquement : `WeekRecoveryDetail::isFlatRecovery` marque ces semaines ;
`EmployeeRttSummaryProvider::applyDeficitCascade` les exclut du drainage des tranches 25/50.
#### Jour de solidarité (CUSTOM < 35h)
Sur la semaine du Lundi de Pentecôte, un contrat CUSTOM < 35h porte un déficit
forfaitaire de `7/35 × heuresHebdo` (12 min/h hebdo, ex. 4h → 0h48) dans les colonnes
Heure / Total / Cumul (25 %/50 % restent à 0). Le montant est fixe et inconditionnel :
il ne dépend pas des heures saisies ni du RTT que la RH pose ce jour-là. Les contrats
35h/39h ne sont pas concernés ici (leur RTT posé draine le cumul normalement).
## Sélecteur d'année
Position : sous la table, à l'intérieur de la zone scrollable, à gauche.
Plage proposée :
- du plus récent (= exercice courant) au plus ancien. Contrairement à l'onglet Congés, le RTT **ne propose pas** l'exercice suivant (consulter un exercice RTT à venir n'a pas de sens) ; pour une phase clôturée, la borne haute reste l'exercice de fin de phase ;
- **double plancher** : `max(floor_historique_contrat, floor_data_start_date)`
- **floor_historique_contrat** : dérivé de `employee.contractHistory[].startDate` — premier exercice où l'employé avait un contrat ouvert
- **floor_data_start_date** : exercice contenant `RTT_START_DATE` (env, ex. `2026-02-23` → exercice 2026)
- la valeur est exposée par l'API `GET /employees/{id}/rtt-summary` via le champ `rttStartDate` (déjà existant — mais peuplé uniquement quand la date tombe dans l'exercice retourné, donc le composable utilise la première réponse pour borner la plage).
- format unique : `Juin 2025 → Mai 2026`, `Juin 2024 → Mai 2025`
Comportement :
- changer d'exercice recharge `getEmployeeRttSummary?year=YYYY` (le backend valide 20002100) ;
- la table redéploie les semaines de l'exercice sélectionné, navigation par mois conservée.
## Verrouillage des éditions sur exercices passés
Quand `selectedYear !== currentYear` (consultation d'un exercice antérieur), le bouton **+ Payer les RTT** est désactivé — **sauf sur l'exercice immédiatement précédent** (`selectedYear === currentYear - 1`), où le paiement rétroactif est autorisé (Option B).
La consultation des exercices plus anciens reste possible, l'édition non.
### Paiement rétroactif sur l'exercice précédent (Option B)
Un paiement enregistré sur l'exercice N-1 modifie sa clôture, donc le **report d'ouverture de l'exercice courant N**. Pour éviter tout décalage / double comptage :
- garde back `EmployeeRttPaymentProcessor::assertYearAllowedForPayment` : accepte courant, **N-1**, ou dernier exercice d'une phase clôturée ;
- après enregistrement, le processor **recalcule automatiquement** la ligne `employee_rtt_balances` de l'exercice courant via `RttClosingBalanceService::computeClosingBalance(N-1)`, dans une **transaction** (le `flush` du paiement le rend visible au recalcul) ;
- si le report de l'exercice courant est **verrouillé** (`is_locked`), le paiement est **refusé** (`assertReportNotLocked`) : la RH doit déverrouiller d'abord ;
- portée volontairement limitée à N-1 (chaîne de recalcul = 1 étape). Si la ligne courante n'existe pas encore, l'affichage reste correct grâce au fallback de `EmployeeRttSummaryProvider::resolveCarry` (calcul dynamique de la clôture N-1).
## Sélecteur de phase de contrat
L'onglet RTT est visible quand la **phase de contrat sélectionnée** n'est pas FORFAIT (et non pas le contrat courant). Concrètement, sur un employé passé en FORFAIT après une période 39h :
- En vue `FORFAIT` (défaut), l'onglet RTT est masqué.
- En vue `39h` (phase passée sélectionnée via le picker `Vue contrat`), l'onglet RTT redevient visible avec les exercices Juin→Mai bornés à la phase.
Le bouton `+ Payer les RTT` est activé uniquement sur le **dernier exercice de la phase passée** (l'exercice contenant `phase.endDate`). Les exercices antérieurs sont en lecture seule.
Cf. `doc/contract-phase-view.md` pour les détails complets.
## Implémentation
- Composable : `frontend/composables/useEmployeeRtt.ts`
- État : `selectedRttYear`, computed `currentRttYear`, `availableRttYears`
- API : `setSelectedRttYear(year)`, `loadRttData()`, `resetLoaded()`
- `resetLoaded()` (appelé au changement d'employé) remet `selectedRttYear = null`.
- Composant : `frontend/components/employees/RttTab.vue`
- Props : `selectedYear`, `availableYears`, `currentYear`
- Event : `update-selected-year`
- Renommage `currentYear` (computed local de l'année du mois affiché) → `displayedMonthYear` pour éviter la collision avec la nouvelle prop.
- Page : `frontend/pages/employees/[id].vue`
- Backend : aucun changement — `EmployeeRttSummaryProvider` accepte déjà `?year=YYYY` (validation 20002100) et expose `rttStartDate`.