fix(heures) : garde backend anti-suppression sur grille périmée (delete explicite) #36

Merged
tristan merged 1 commits from fix/work-hour-bulk-upsert-explicit-delete into develop 2026-06-25 07:29:03 +00:00
Owner

Contexte (incident prod)

Le correctif #31 (dirty-tracking front) ne protège que les sessions chargeant le nouveau bundle. Un vieil onglet ouvert avant déploiement tourne encore sur l'ancien JS et envoie toute la grille périmée. Hier soir, un onglet ouvert le matin a supprimé ~10 lignes d'heures saisies dans la journée par d'autres utilisateurs (journal BDD à l'appui : 1 save = 2 updates + 8 deletes de lignes intactes).

Cause : le backend traitait toute entrée vide comme une suppression, sans aucune garde indépendante du client.

Correctif — suppression sur intention explicite (delete: true)

WorkHourBulkUpsertProcessor ne supprime une ligne existante sur entrée vide que si l'entrée porte delete: true. Sinon → no-op (ligne préservée). Aucune grille périmée, quel que soit le client (vieil onglet inclus), ne peut plus détruire une saisie concurrente. La création de ligne technique de validation reste limitée à null === $existing.

Le front (à jour) pose delete: true sur une ligne vidée volontairement (helper isEntryEmpty, appliqué après le filtre dirty-tracking) → suppression métier conservée. Flag optionnel ajouté au DTO front (WorkHourEntryPayload) et back (WorkHourBulkUpsert), défaut false.

Testabilité

Le processor dépend désormais des interfaces repo (EmployeeScopedRepositoryInterface / WorkHourReadRepositoryInterface, repos concrets final non mockables) → nouveau test unitaire WorkHourBulkUpsertProcessorTest (no-op sans flag / suppression avec flag / update normal).

Limite résiduelle (choix : suppression explicite, pas verrou optimiste)

L'édition explicite d'une ligne sur données périmées peut encore écraser une saisie concurrente sur cette même ligne. Seule la suppression est blindée.

Vérification

  • 267 tests PHPUnit OK (dont 3 nouveaux), via le pre-commit hook.
  • Front : revue de code (pas de harnais de tests front).

Doc

  • doc/hours-save-dirty-tracking.md, CLAUDE.md, doc in-app (documentation-content.ts).

🤖 Generated with Claude Code

## Contexte (incident prod) Le correctif #31 (dirty-tracking front) ne protège que les sessions chargeant le nouveau bundle. Un **vieil onglet** ouvert avant déploiement tourne encore sur l'ancien JS et envoie toute la grille périmée. Hier soir, un onglet ouvert le matin a **supprimé ~10 lignes d'heures** saisies dans la journée par d'autres utilisateurs (journal BDD à l'appui : 1 save = 2 updates + 8 deletes de lignes intactes). Cause : le backend traitait toute **entrée vide comme une suppression**, sans aucune garde indépendante du client. ## Correctif — suppression sur intention explicite (`delete: true`) `WorkHourBulkUpsertProcessor` ne supprime une ligne existante sur entrée vide **que si l'entrée porte `delete: true`**. Sinon → **no-op** (ligne préservée). Aucune grille périmée, quel que soit le client (vieil onglet inclus), ne peut plus détruire une saisie concurrente. La création de ligne technique de validation reste limitée à `null === $existing`. Le front (à jour) pose `delete: true` sur une ligne **vidée volontairement** (helper `isEntryEmpty`, appliqué après le filtre dirty-tracking) → suppression métier conservée. Flag optionnel ajouté au DTO front (`WorkHourEntryPayload`) et back (`WorkHourBulkUpsert`), défaut `false`. ## Testabilité Le processor dépend désormais des interfaces repo (`EmployeeScopedRepositoryInterface` / `WorkHourReadRepositoryInterface`, repos concrets `final` non mockables) → nouveau test unitaire `WorkHourBulkUpsertProcessorTest` (no-op sans flag / suppression avec flag / update normal). ## Limite résiduelle (choix : suppression explicite, pas verrou optimiste) L'**édition explicite** d'une ligne sur données périmées peut encore écraser une saisie concurrente sur cette même ligne. Seule la **suppression** est blindée. ## Vérification - **267 tests PHPUnit OK** (dont 3 nouveaux), via le pre-commit hook. - Front : revue de code (pas de harnais de tests front). ## Doc - `doc/hours-save-dirty-tracking.md`, `CLAUDE.md`, doc in-app (`documentation-content.ts`). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
tristan added 1 commit 2026-06-25 07:20:17 +00:00
## Problème
Le correctif #31 (dirty-tracking front) ne protège que les sessions qui
chargent le nouveau bundle. Un vieil onglet ouvert avant déploiement tourne
encore sur l'ancien JS et envoie toute la grille périmée → reproduit en prod :
un onglet ouvert le matin a supprimé ~10 lignes saisies dans la journée par
d'autres utilisateurs (entrée vide = suppression côté backend, sans garde).

## Correctif (suppression sur intention explicite)
`WorkHourBulkUpsertProcessor` ne supprime une ligne existante sur entrée vide
QUE si l'entrée porte `delete: true`. Sinon → no-op (ligne préservée). Aucune
grille périmée, quel que soit le client, ne peut plus détruire une saisie
concurrente. La création de ligne technique de validation reste limitée à
`null === $existing`.

Le front (à jour) pose `delete: true` sur une ligne vidée volontairement
(helper `isEntryEmpty`, après le filtre dirty-tracking) → suppression métier
conservée. Flag optionnel ajouté au DTO front (`WorkHourEntryPayload`) et back
(`WorkHourBulkUpsert`), défaut false.

## Testabilité
Le processor dépend désormais des interfaces repo (`EmployeeScopedRepositoryInterface`
/ `WorkHourReadRepositoryInterface`, repos concrets `final` non mockables) →
nouveau test unitaire `WorkHourBulkUpsertProcessorTest` (no-op sans flag /
suppression avec flag / update normal).

## Limite résiduelle (par choix : suppression explicite, pas verrou optimiste)
L'édition explicite d'une ligne sur données périmées peut encore écraser une
saisie concurrente sur cette même ligne. Seule la suppression est blindée.

## Vérification
- 267 tests PHPUnit OK (dont 3 nouveaux). Front : revue de code (pas de harnais).

## Doc
- doc/hours-save-dirty-tracking.md, CLAUDE.md, doc in-app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tristan merged commit d66288d061 into develop 2026-06-25 07:29:03 +00:00
tristan deleted branch fix/work-hour-bulk-upsert-explicit-delete 2026-06-25 07:29:04 +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#36