fix : le cron de bascule recalcule la vraie clôture au lieu du placeholder

LeaveRolloverCommand::resolveCarry se fiait au closing_days stocké quand
une ligne existait pour l'exercice précédent. Or closing_days n'est jamais
recalculé après création (toujours = opening, ou 0 sur un bootstrap), donc
le report propageait l'ouverture sans créditer l'acquisition de l'année.
Cas Aurore : bascule 2026->2027 aurait reporté 0 au lieu de 31.

resolveCarry calcule désormais toujours la clôture réelle via
computeDynamicClosingForYear (bootstrap-aware, intègre acquisition + samedis
+ fractionnés − pris), puis fige ce résultat dans closing_days de l'exercice
qui se termine. Vérifié sur données réelles : report 2027 d'Aurore = 31,00 j
/ 5,00 samedis (au lieu de 0).

Corrections manuelles préservées : le cron reste idempotent (ne réécrit pas
une ligne existante) et le bon levier de correction devient opening_days
(propagé par le recalcul), pas closing_days.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 11:46:17 +02:00
parent 9635cf15ff
commit ca8468c95d
2 changed files with 35 additions and 17 deletions
+12 -5
View File
@@ -69,7 +69,7 @@ Etat implementation:
- le calcul de synthese conges lit en priorite `opening_days/opening_saturdays` de cette table quand une ligne existe pour `(employee, rule_code, year)`
- si aucune ligne n'existe, le calcul reste base sur le report dynamique N-1
- **le report dynamique (`LeaveBalanceComputationService::computeDynamicClosingForYear`, qui alimente le solde d'ouverture de l'exercice suivant) ancre lui aussi sur cette table** : pour chaque exercice de sa boucle, si une ligne bootstrap existe il part de `opening_days/opening_saturdays` (et ajoute l'offset `taken_days/taken_saturdays`) au lieu de recalculer depuis l'embauche. Sans cet ancrage, la clôture d'un exercice consulté en avance (ex. exercice suivant) cumulerait une année pleine d'acquisition par exercice antérieur à la mise en service — aucune absence historique n'étant saisie (cas Aurore : 88 jours au lieu de 31).
- la commande `app:leave:rollover` calcule aussi le report dynamique N-1 si la ligne N-1 n'est pas encore persistée (pas de reset a 0 par defaut)
- la commande `app:leave:rollover` recalcule **toujours** le report via `computeDynamicClosingForYear(N-1)` (et ne se fie plus au `closing_days` stocké, qui n'est qu'un placeholder = `opening`), puis fige ce résultat dans le `closing_days` de l'exercice qui se termine ; voir § 6
### Definition des colonnes
@@ -121,12 +121,19 @@ Date d'effet:
- non forfait: au `1er juin`
Traitement par employe:
1. lire l'exercice precedent
2. determiner le report:
1. determiner le report de l'exercice precedent:
- si cloture `paidLeaveSettled=true` sur la periode precedente => report `0`
- sinon report = `closing` exercice precedent
- sinon report = **cloture reelle recalculee** via `computeDynamicClosingForYear(exercicePrecedent)` (acquisition + samedis + fractionnes pris, ancree sur l'`opening_days` bootstrap de chaque exercice). On **ne se fie PAS** au `closing_days` stocke : il n'est jamais recalcule apres creation (toujours egal a l'`opening`), donc s'y fier propagerait l'ouverture sans jamais crediter l'acquisition de l'annee (cas Aurore : report 0 au lieu de 31).
2. **figer** ce report dans `closing_days/closing_saturdays` de la ligne de l'exercice qui se termine (la colonne contient enfin un vrai solde de cloture, auditable).
3. creer la ligne du nouvel exercice avec ce report en `opening_*`
4. initialiser `accrued/taken/closing` pour le nouvel exercice
4. initialiser `accrued/taken/closing` pour le nouvel exercice (= `opening` a la creation)
### Correction manuelle d'un solde (RH / comptable)
Le verrouillage (`is_locked`) n'est pas utilise ; les corrections se font directement en BDD. Deux garde-fous rendent cela sur :
- **Idempotence** : le cron ne cree la ligne d'un exercice que si elle n'existe pas (les lignes existantes sont ignorees). Une ligne corrigee a la main n'est donc **jamais** ecrasee par un passage ulterieur du cron (meme avec `--force`).
- **Le bon levier est `opening_days`, pas `closing_days`** : `computeDynamicClosingForYear` part de l'`opening_days` de chaque exercice comme ancre. Corriger l'`opening_days` d'un exercice (ou la donnee de fond : absence, fractionne, paye) se propage automatiquement aux reports des exercices suivants. Editer un `closing_days` d'un exercice **pas encore bascule** est inutile (il sera recalcule a la bascule) ; une fois la ligne suivante creee, plus rien n'y touche.
## 7) Donnees a fournir au go-live