# 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`.