Files
SIRH/docs/superpowers/specs/2026-06-09-rtt-custom-deficit-design.md
T
tristan f0387233e4
Auto Tag Develop / tag (push) Successful in 7s
[#SIRH-36] corriger calcule rtt contrat custom (#27)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [ ] Pas de régression
- [ ] TU/TI/TF rédigée
- [ ] TU/TI/TF OK
- [ ] CHANGELOG modifié

Reviewed-on: #27
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-11 08:36:57 +00:00

6.9 KiB
Raw Blame History

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.