Files
SIRH/doc/rtt-rollover.md
T
tristan ac8a36eb4f
Auto Tag Develop / tag (push) Successful in 7s
[#SIRH-34] fix RTT bascule ne fonctionne pas (#22)
La bascule app:rtt:rollover ne reprenait que les RTT acquis de l'exercice qui
se terminait : le report d'ouverture déjà présent était perdu et les paiements
n'étaient pas déduits. Le nouveau report reprend le solde de clôture =
report d'ouverture(N-1) + acquis(N-1) − RTT payés(N-1), soit le "Disponible"
affiché par EmployeeRttSummaryProvider.

- nouveau RttClosingBalanceService (fold pur testé : invariant somme tranches =
  disponible, cascade déficit 50% avant 25%, récup CUSTOM non perdue)
- RttRolloverCommand branché dessus + option --recompute (écrase les lignes
  existantes non verrouillées, pour reprise d'une bascule erronée)
- test date-sensible EmployeeRttSummaryProviderTest rendu robuste
- docs: doc/rtt-rollover.md, CLAUDE.md, documentation-content.ts

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

| 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: #22
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-06-08 08:56:19 +00:00

7.0 KiB
Raw Blame History

Rollover RTT - Regles et Mise en Production

Document de reference pour expliquer le fonctionnement metier du report RTT N-1 et preparer le lancement en production.

1) Objectif

Permettre le report des heures supplementaires (RTT) d'un exercice a l'autre et fiabiliser les soldes.

Principe:

  • le solde d'ouverture est stocke par exercice
  • au changement d'exercice, on ouvre la nouvelle periode avec un "solde d'ouverture" (report N-1)
  • au go-live, les soldes d'ouverture sont importes manuellement (CSV ou insertion SQL)

2) Exercice metier

  • exercice RTT: du 1er juin au 31 mai
  • year = annee de fin d'exercice (ex: 2026 = 01/06/2025 -> 31/05/2026)
  • employes eligibles: tous sauf INTERIM et suivi PRESENCE

3) Logique de compteurs

  • report N-1:
    • correspond au solde d'ouverture (opening_minutes)
    • source prioritaire: table employee_rtt_balances
    • fallback: calcul dynamique de la somme des minutes de recuperation de l'exercice precedent
  • acquis N:
    • somme des minutes de recuperation hebdomadaires de l'exercice en cours
    • calcul: HS totales + bonus 25% + bonus 50% par semaine
  • disponible:
    • report N-1 + acquis N
  • affichage du compteur global: en jours (1 jour = 7h = 420 minutes)

4) Attribution mensuelle des semaines

  • une semaine ISO qui chevauche deux mois est affichee dans les deux mois, avec les valeurs reparties proportionnellement aux minutes travaillees de chaque portion
  • le calcul des heures supplementaires reste hebdomadaire (seuils 35h/39h/43h appliques sur la semaine entiere), seul l'affichage est scinde
  • exemple: S14 lundi-mardi en mars, mercredi-dimanche en avril → la S14 apparait en mars (part lun-mar) et en avril (part mer-dim)

5) Table cible

Table employee_rtt_balances (une ligne par employe et exercice):

  • employee_id
  • year
  • opening_minutes
  • is_locked
  • created_at, updated_at

Contrainte unique:

  • (employee_id, year)

Etat implementation:

  • la table est creee
  • le calcul de synthese RTT lit en priorite opening_minutes de cette table quand une ligne existe pour (employee, year)
  • si aucune ligne n'existe, le calcul dynamique sur l'exercice N-1 est effectue

Definition des colonnes

  • employee_id:
    • identifiant employe (FK vers employees)
    • une ligne de solde par employe / exercice
  • year:
    • annee d'exercice (annee de fin)
    • 2026 = 01/06/2025 -> 31/05/2026
  • opening_minutes:
    • report N-1 en minutes (solde d'ouverture)
    • correspond a la somme des minutes de recuperation de l'exercice precedent
  • is_locked:
    • false sur exercice ouvert (recalcul possible)
    • true apres validation RH (exercice fige)
  • created_at, updated_at:
    • trace technique creation / mise a jour

6) Rollover automatique

Commande quotidienne (cron) idempotente.

  • commande Symfony: php bin/console app:rtt:rollover
  • comportement date metier:
    • le 01/06: calcule et persiste le report pour chaque employe eligible
    • les autres jours: sortie sans action
  • option manuelle: --force pour executer hors date metier (reprise/correction)
  • option manuelle: --recompute pour recalculer et ecraser les lignes existantes au lieu de les sauter (reprise apres correction). Les lignes verrouillees (is_locked = true, validees RH) ne sont jamais ecrasees.

Date d'effet:

  • au 1er juin (meme date que le rollover conges non forfait)

Traitement par employe:

  1. verifier l'eligibilite (ni INTERIM, ni suivi PRESENCE)
  2. en mode normal: si une ligne existe deja pour (employee, targetYear), la sauter (idempotence). En mode --recompute: la recalculer, sauf si elle est verrouillee.
  3. calculer le solde de cloture de l'exercice N-1 (= disponible affiche en fin d'exercice) : report d'ouverture N-1 + acquis N-1 RTT payes N-1
    • le report d'ouverture N-1 vient de la ligne employee_rtt_balances de l'exercice N-1 (import go-live ou rollover precedent) ; a defaut, calcul dynamique des acquis de N-2.
    • l'acquis N-1 = somme des minutes de recuperation hebdomadaires de l'exercice N-1.
    • les RTT payes N-1 (employee_rtt_payments) sont deduits.
  4. creer (ou mettre a jour) la ligne du nouvel exercice avec ce solde, reparti sur les 4 tranches opening_base25/bonus25/base50/bonus50.

Regle clef : le report d'un exercice a l'autre reprend exactement le disponible affiche sur l'onglet RTT (cf. EmployeeRttSummaryProvider). Le report deja present au debut de l'exercice precedent n'est jamais perdu, et les heures deja payees ne sont pas re-creditees. Service mutualise : App\Service\Rtt\RttClosingBalanceService.

Bug historique corrige : la version initiale ne reportait que acquis N-1 (ni report d'ouverture, ni deduction des paiements), ce qui faisait disparaitre le solde de depart. Pour corriger des lignes deja creees a tort, relancer avec --force --recompute.

7) Donnees a fournir au go-live

La RH doit fournir les soldes RTT a reporter.

Colonnes minimales:

  • employee_id (id interne)
  • year
  • opening_minutes (total en minutes)

Format recommande:

  • CSV UTF-8
  • separateur ;

Exemple:

employee_id;year;opening_minutes
42;2026;1260
17;2026;840

Equivalent en insertion SQL directe:

INSERT INTO employee_rtt_balances (employee_id, year, opening_minutes, is_locked, created_at, updated_at)
VALUES
  (42, 2026, 1260, false, NOW(), NOW()),
  (17, 2026, 840, false, NOW(), NOW());

Conversion rapide: 1260 minutes = 21h00 = 3.00 jours (1 jour = 420 min = 7h)

8) Checklist mise en prod

  1. Executer la migration (employee_rtt_balances)
  2. Importer les soldes d'ouverture N-1 (CSV ou SQL)
  3. Verifier 3 cas metier:
    • CDI 39h avec heures supp sur l'exercice precedent
    • CDI 35h sans heures supp (report = 0)
    • INTERIM (doit etre ignore, pas de ligne creee)
  4. Activer le cron de rollover
  5. Geler (is_locked) les exercices historicises valides

Exemple cron (tous les jours a 02:15, juste apres le rollover conges): Dev

15 2 * * * cd /var/www/html && php bin/console app:rtt:rollover --no-interaction 2>&1

Prod

10 2 * * * cd /var/www/sirh && php bin/console app:rtt:rollover --no-interaction 2>&1

Explication de la ligne cron:

  • 15 2 * * *: tous les jours a 02:15
  • php bin/console app:rtt:rollover --no-interaction: execute le rollover sans confirmation
    • hors 01/06, la commande sort en no-op (normal)
  • >> var/log/rtt-rollover.log 2>&1: log sortie standard et erreurs

Execution manuelle forcee:

php bin/console app:rtt:rollover --force --no-interaction

Exemple de verification rapide:

tail -n 50 /var/www/html/var/log/rtt-rollover.log

9) Points de vigilance

  • Ne jamais modifier opening_minutes apres validation RH sans procedure explicite
  • Garder une trace de toute correction manuelle (auteur, date, motif)
  • Le calcul dynamique N-1 (fallback) parcourt toutes les heures de l'exercice precedent: preferer l'import explicite pour les exercices historiques
  • La commande de rollover est idempotente: si une ligne existe deja, l'employe est ignore (pas d'ecrasement)