docs(rtt) : add design spec and implementation plan for custom deficit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# Déficit RTT pris en compte pour les contrats CUSTOM (4h, etc.)
|
||||
|
||||
Date : 2026-06-09
|
||||
Branche : `feature/SIRH-36-corriger-le-calcule-des-rtt-des-contrat-4h`
|
||||
|
||||
## Contexte & problème
|
||||
|
||||
Les salariées avec un contrat 4h (type CUSTOM : `weeklyHours` ≠ 35/39, non INTERIM/FORFAIT,
|
||||
mode TIME) voient, dans l'onglet RTT, des semaines travaillées **en dessous** de leurs heures
|
||||
contractuelles afficher un déficit dans la colonne « Heure » (ex. Ewa S23 : −2h) **sans aucun
|
||||
effet** : « Total » = 0 et « Cumul » = 0.
|
||||
|
||||
Cause : `RttRecoveryComputationService::computeRecoveryByWeek` écrête le total hebdo des CUSTOM
|
||||
avec `totalMinutes = max(0, $weeklyOvertimeTotalMinutes)`. Le déficit est donc supprimé. C'est
|
||||
le comportement métier documenté jusqu'ici (« CUSTOM : le déficit n'impacte pas le solde »).
|
||||
|
||||
Décision métier (validée avec le client) : **le déficit doit être pris en compte et réduire le
|
||||
cumul**, comme pour les 35h/39h, avec un affichage propre dans l'onglet RTT.
|
||||
|
||||
## Décisions validées
|
||||
|
||||
1. **Le cumul peut devenir négatif** (identique aux 35h/39h, comportement déjà assumé dans le
|
||||
code — cf. `RttClosingBalanceService::fold` ligne 98 « leftover may push the balance
|
||||
negative, as on screen »). Le négatif est reporté à l'exercice suivant.
|
||||
2. **Déficit visible** en colonnes « Heure » + « Total » + « Cumul ». Les colonnes 25%/50%
|
||||
restent à **0** pour un contrat CUSTOM (un 4h n'a pas de bonus, donc pas de tranches).
|
||||
|
||||
## Principe technique
|
||||
|
||||
Retirer l'écrêtage `max(0, …)` pour les semaines CUSTOM : le déficit (négatif) circule dans
|
||||
`WeekRecoveryDetail::totalMinutes` et réduit le cumul. La seule spécificité CUSTOM reste :
|
||||
récupération 1h = 1h, sans bonus 25/50.
|
||||
|
||||
### Le point délicat : ne pas drainer les tranches 25/50
|
||||
|
||||
Pour un 35h/39h, une semaine déficitaire **draine** les tranches 25/50 accumulées via la cascade
|
||||
de `EmployeeRttSummaryProvider` (lignes 149-174) et de `RttClosingBalanceService::fold` (lignes
|
||||
92-99), ce qui affiche des valeurs négatives en « Total 25% / 50% ».
|
||||
|
||||
Pour un CUSTOM, la récup n'est jamais bucketisée (elle vit uniquement dans `totalMinutes`). La
|
||||
cascade ne doit donc **pas** s'appliquer aux semaines CUSTOM, sinon le déficit apparaîtrait en
|
||||
négatif dans « Total 25% » (affichage sale, et incohérent avec les récups positives qui, elles,
|
||||
n'y figurent pas).
|
||||
|
||||
Solution : un drapeau **`isFlatRecovery`** (récupération plate 1:1, sans tranches) porté par la
|
||||
semaine, qui désactive la cascade dans le provider.
|
||||
|
||||
## Changements
|
||||
|
||||
### Backend
|
||||
|
||||
1. **`src/Dto/Rtt/WeekRecoveryDetail.php`** : ajout `public bool $isFlatRecovery = false`.
|
||||
2. **`src/Dto/Rtt/EmployeeRttWeekSummary.php`** : ajout `public bool $isFlatRecovery = false`.
|
||||
3. **`src/Service/Rtt/RttRecoveryComputationService.php`**
|
||||
(`computeRecoveryByWeek`, branche `$isCustomContract`) :
|
||||
- `totalMinutes = $weeklyOvertimeTotalMinutes` (signé — plus de `max(0, …)`).
|
||||
- `isFlatRecovery: true` sur le `WeekRecoveryDetail` retourné.
|
||||
- Les buckets `base25/bonus25/base50/bonus50` restent à 0 (inchangé).
|
||||
- Cas non-CUSTOM et PRESENCE/INTERIM inchangés (`isFlatRecovery` reste `false`).
|
||||
4. **`src/State/EmployeeRttSummaryProvider.php`**
|
||||
- `buildWeekSummaries` : propager `isFlatRecovery` du `WeekRecoveryDetail` vers
|
||||
l'`EmployeeRttWeekSummary` — dans le cas mono-mois **et** dans le cas semaine à cheval sur
|
||||
deux mois (les deux instances héritent du drapeau).
|
||||
- Cascade déficit (ligne 150) : condition devient
|
||||
`if ($week->totalMinutes >= 0 || $week->isFlatRecovery)`. Pour une semaine CUSTOM
|
||||
déficitaire, on ne draine pas : les buckets restent 0, `cumulativeBalanceMinutes`
|
||||
(déjà basé sur `totalMinutes`, ligne 197) intègre le déficit.
|
||||
- Dans la reconstruction de la branche `else` (semaine déficitaire normale), conserver
|
||||
explicitement `isFlatRecovery: $week->isFlatRecovery` (toujours `false` à ce point, mais
|
||||
explicite pour la clarté).
|
||||
5. **`src/Command/DumpVerificationSnapshotCommand.php`** : ce command duplique la cascade du
|
||||
provider (lignes ~695-716). Mettre à jour la condition de cascade pour respecter
|
||||
`isFlatRecovery`, et propager le drapeau dans sa reconstruction des week summaries, afin que
|
||||
les snapshots before/after restent fidèles à l'app.
|
||||
|
||||
### Aucun changement
|
||||
|
||||
- **`src/Service/Rtt/RttClosingBalanceService.php`** : `fold` gère déjà `totalMinutes` négatif
|
||||
(branche déficit lignes 92-99) et le remainder CUSTOM (lignes 83-87). Le report N+1 intègre
|
||||
donc automatiquement le déficit. Pas de modification.
|
||||
- **`frontend/components/employees/RttTab.vue`** : aucun. Les sous-colonnes Base/25%/50% sont
|
||||
déjà écrêtées à 0 sur les semaines déficitaires (`totalMinutes >= 0 ? … : 0`). Avec buckets =
|
||||
0 côté back, « Total 25%/50% » = 0, et « Heure »/« Total »/« Cumul » affichent le déficit.
|
||||
- **Pas de migration** : aucun changement de schéma.
|
||||
|
||||
## Effets de bord (assumés, cohérents)
|
||||
|
||||
- **Récap congés** (`LeaveRecapRowBuilder::…` via `computeTotalRecoveryForExercise`) : la valeur
|
||||
RTT reflètera aussi les déficits et peut devenir négative. Cohérent avec la décision.
|
||||
- **Rollover / report** : la clôture d'exercice (`computeClosingBalance`) intègre désormais les
|
||||
déficits. Les lignes `employee_rtt_balances` déjà stockées (calculées avec l'ancienne logique,
|
||||
déficit = 0) doivent être rafraîchies après déploiement :
|
||||
`php bin/console app:rtt:rollover --force --recompute` (ne touche pas les lignes
|
||||
`is_locked`). Ex. Ewa : clôture 2026 passe de 0 à −2h, donc report d'ouverture 2027 = −2h.
|
||||
- **Carry / Report row** : pour un CUSTOM, le report reste stocké/affiché dans la tranche
|
||||
`base25` (convention pré-existante du `fold`, remainder parking) — comportement inchangé, hors
|
||||
périmètre.
|
||||
|
||||
## Tests (TDD)
|
||||
|
||||
- **`tests/Service/Rtt/RttClosingBalanceServiceTest.php`** : nouveau cas — un `WeekRecoveryDetail`
|
||||
CUSTOM **déficitaire** (`totalMinutes` négatif) diminue bien la clôture (somme = report +
|
||||
Σ semaines − payés, négatif inclus).
|
||||
- **`tests/State/EmployeeRttSummaryProviderTest.php`** : semaine CUSTOM déficitaire
|
||||
(`isFlatRecovery = true`, `totalMinutes < 0`) → buckets 25/50 restent 0, `cumulativeBalance`
|
||||
réduit du déficit (pas de drainage des tranches). Vérifier aussi qu'une semaine 35h/39h
|
||||
déficitaire continue de drainer (non-régression).
|
||||
- **`tests/Service/Rtt/RttRecoveryComputationServiceTest.php`** : si réalisable en intégration —
|
||||
contrat CUSTOM, semaine travaillée sous les heures → `totalMinutes` négatif et
|
||||
`isFlatRecovery = true`.
|
||||
|
||||
## Documentation (obligatoire, même intervention)
|
||||
|
||||
- `doc/rtt-rollover.md` et/ou `doc/rtt-tab.md` : mettre à jour la règle CUSTOM (déficit
|
||||
désormais compté).
|
||||
- `frontend/data/documentation-content.ts` : section RTT — déficit des contrats CUSTOM.
|
||||
- `CLAUDE.md` : section « Overtime Rules » — corriger « deficit doesn't impact balance » pour les
|
||||
CUSTOM ; documenter `isFlatRecovery`.
|
||||
Reference in New Issue
Block a user