fix(heures) : n'enregistrer que les lignes modifiées (anti-écrasement concurrent) #31

Merged
tristan merged 1 commits from feature/SIRH-42-fix-l-ajout-des-heures into develop 2026-06-24 07:16:43 +00:00
Owner

Problème

Sur l'écran Heures / Heures Conducteurs, l'enregistrement envoyait au bulk-upsert une entrée pour tous les employés visibles non verrouillés, à partir de l'état en mémoire de la grille. Le backend (WorkHourBulkUpsertProcessor) traitant une entrée vide comme une suppression, un admin avec une grille périmée pouvait supprimer une ligne saisie entre-temps par un autre utilisateur.

Scénario reproduit

  1. Un admin ouvre l'écran ; la ligne d'un salarié ROLE_SELF est vide.
  2. Ce salarié saisit ses heures dans sa propre session → ligne créée, non validée (donc non verrouillée).
  3. L'admin, sur sa grille périmée, enregistre d'autres employés.
  4. Le payload contient une entrée vide pour le salarié → le backend supprime sa ligne. Perte de données.

Correctif (suivi des lignes modifiées)

hydrateRows capture un instantané loadedRows de l'état chargé depuis le serveur. handleSave ne transmet plus que les lignes dont l'état courant diffère de l'instantané.

  • Ligne intouchée → jamais envoyée → jamais supprimée
  • Ligne vidée volontairement → envoyée vide → supprimée (métier conservé)
  • Ligne remplie/modifiée → envoyée → créée/mise à jour

Symétrique dans useHoursPage.ts et useDriverHoursPage.ts.

Limite connue

Pas de verrou optimiste backend : l'édition explicite d'une ligne sur données périmées peut toujours écraser une saisie concurrente sur cette même ligne (hors périmètre).

Doc

  • doc/hours-save-dirty-tracking.md (nouveau)
  • Note CLAUDE.md (section Validation Rules)

Vérification

  • Pre-commit hook : 236 tests PHPUnit OK.
  • Pas de harnais de tests frontend (revue de code uniquement).

🤖 Generated with Claude Code

## Problème Sur l'écran **Heures** / **Heures Conducteurs**, l'enregistrement envoyait au bulk-upsert une entrée pour **tous** les employés visibles non verrouillés, à partir de l'état en mémoire de la grille. Le backend (`WorkHourBulkUpsertProcessor`) traitant une **entrée vide comme une suppression**, un admin avec une grille **périmée** pouvait supprimer une ligne saisie entre-temps par un autre utilisateur. ### Scénario reproduit 1. Un admin ouvre l'écran ; la ligne d'un salarié `ROLE_SELF` est vide. 2. Ce salarié saisit ses heures dans sa propre session → ligne créée, **non validée** (donc non verrouillée). 3. L'admin, sur sa grille périmée, enregistre d'autres employés. 4. Le payload contient une entrée **vide** pour le salarié → le backend supprime sa ligne. **Perte de données.** ## Correctif (suivi des lignes modifiées) `hydrateRows` capture un instantané `loadedRows` de l'état chargé depuis le serveur. `handleSave` ne transmet plus que les lignes **dont l'état courant diffère de l'instantané**. - Ligne **intouchée** → jamais envoyée → jamais supprimée ✅ - Ligne **vidée volontairement** → envoyée vide → supprimée (métier conservé) - Ligne **remplie/modifiée** → envoyée → créée/mise à jour Symétrique dans `useHoursPage.ts` et `useDriverHoursPage.ts`. ## Limite connue Pas de verrou optimiste backend : l'édition **explicite** d'une ligne sur données périmées peut toujours écraser une saisie concurrente sur cette même ligne (hors périmètre). ## Doc - `doc/hours-save-dirty-tracking.md` (nouveau) - Note `CLAUDE.md` (section *Validation Rules*) ## Vérification - Pre-commit hook : **236 tests PHPUnit OK**. - Pas de harnais de tests frontend (revue de code uniquement). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tristan added 1 commit 2026-06-24 07:07:17 +00:00
L'écran Heures / Heures Conducteurs envoyait au bulk-upsert une entrée pour
tous les employés visibles non verrouillés, à partir de l'état en mémoire.
Le backend traitant une entrée vide comme une suppression, un admin avec une
grille périmée pouvait supprimer une ligne saisie entre-temps par un autre
utilisateur (ex. ROLE_SELF non encore validé, donc non verrouillé).

handleSave capture désormais un instantané des lignes chargées (loadedRows,
dans hydrateRows) et ne transmet que les lignes dont l'état courant en diffère.
Une ligne intouchée n'est jamais envoyée → jamais supprimée.

Symétrique dans useHoursPage.ts et useDriverHoursPage.ts.
Doc : doc/hours-save-dirty-tracking.md + note CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tristan merged commit 8e59e9fd6a into develop 2026-06-24 07:16:43 +00:00
tristan deleted branch feature/SIRH-42-fix-l-ajout-des-heures 2026-06-24 07:16:44 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: MALIO-DEV/SIRH#31