Aller au contenu

ADR-0029 — Une photo RH en erreur se corrige OU se remplace : un choix explicite, jamais un doublon

Statut 🟢 Accepté — 2026-06-22 ; implémenté (backend demandes.py : garde durcie + endpoint remplacer + helper _ingerer_photo ; frontend MesDemandes.tsx : choix explicite + modales ; route proxy ; 207 backend passed, tsc + next build verts)
Portée Parcours client de téléversement d'une photo RH (/demandes). Comportement backend (garde anti-doublon, remplacement) + UX frontend (choix explicite, modales d'upload).
Décision Une seule photo par date et par demande, quel que soit son statut. Une photo en erreur (a_corriger) se résout par deux voies explicites : corriger cellule par cellule (in-app) ou remplacer le fichier entier (même date, la tentative cassée est jetée). Le re-téléversement « ajout » est réservé aux autres dates (trajectoires). Les uploads (ajout, remplacement) passent en modale.
Réf. ADR-0017 — Mobilité groupe (multi-photos = trajectoires) ; D-ING-5 (correction in-app cellule par cellule) ; ADR-0027 — Modales = tâche focalisée ; DESIGN.md (modale focalisée)

🎯 À retenir

On peut téléverser plusieurs photos RH datées sur une étude (suivi de trajectoire, ADR-0017). Mais ce chemin servait deux intentions opposées : enrichir à une autre date vs réparer une photo cassée. Quand un client re-téléverse un fichier propre à la même date qu'une photo en erreur, l'ancien code ajoutait une seconde photo au lieu de remplacer : deux photos à la même date, et un fil d'erreur qui ne part jamais. Ça ressemble à un bug. On tranche : une photo par date, et sur une photo en erreur le client choisit explicitement entre corriger (in-app) et remplacer (fichier entier). Plus aucun doublon de date silencieux.

Contexte

Vécu en test (persona client). Une photo a_corriger (6 anomalies bloquantes) était affichée avec son panneau de correction in-app. Le client a re-téléversé un fichier propre, mais sous un nom différent et à la même date. Effets, tous expliqués par le code :

  • Garde anti-doublon trop laxiste (demandes.py) : elle ne bloquait que sur une photo valide de même date. Une photo a_corriger laissait passer un nouvel upload.
  • L'upload ne remplaçait pas : il créait un second DocumentRH. Le commentaire du code disait pourtant « remplacer une tentative cassée est légitime » : l'intention et l'implémentation divergeaient.
  • Résultat : photo sale a_corriger + photo propre valide, même date, le fil d'erreur de la sale toujours là. Le client ne pouvait pas « s'en débarrasser ».

Racine : un seul chemin d'upload pour deux intentions (réparer vs enrichir une autre date). L'ambiguïté produit le doublon.

Décision

  • D-PHOTO-1 — Une photo par date, quel que soit le statut. La garde anti-doublon de l'upload (uploader_document_rh) bloque désormais si n'importe quelle photo existe à cette date : valide → 422 (« choisissez une autre date ») ; a_corriger → 409 (« en attente de correction : corrigez-la ou remplacez son fichier »). Plus de doublon de date.
  • D-PHOTO-2 — Remplacement explicite d'une tentative cassée. Nouvel endpoint POST …/documents/{id}/remplacer (réservé à a_corriger) : il valide le nouveau fichier d'abord (structurel → 422, l'ancien reste intact), puis jette la tentative cassée (ligne DocumentRH + fichier brut ; elle n'a pas de Dossier). Le nouveau hérite de la date de l'ancien. Une photo valide ne se remplace pas (409 : sa trace est gardée). Cœur d'ingestion factorisé (_ingerer_photo), partagé avec l'upload.
  • D-PHOTO-3 — Choix explicite côté client. Sur une photo en erreur, deux actions nettes : « Corriger ici » (déplie le panneau cellule par cellule, D-ING-5) et « Remplacer le fichier » (modale d'upload, date héritée). Le panneau de correction ne s'affiche plus d'office : le client tranche. L'« + Ajouter une photo » (autre date) est un chemin séparé, en modale.
  • D-PHOTO-4 — Les uploads passent en modale. Ajout et remplacement ouvrent la modale partagée (lib/Modal.tsx, zone de dépôt) — tâche focalisée hors du flux de la liste (cohérent ADR-0027 / DESIGN.md).

Conséquences

  • Positif. Plus de doublon de date ni de fil d'erreur « collant » ; l'intention du client est explicite (corriger vs remplacer vs enrichir) ; le multi-photos pour les trajectoires (autres dates) reste pleinement ouvert. Test backend dédié (test_remplacer_photo_a_corriger) couvre garde 409, remplacement, date héritée, refus de remplacer une valide.
  • Pas de migration. Aucune colonne de modèle ajoutée (remplacement = delete + create) ; _COLONNES_AJOUTEES non concerné.
  • Limite assumée. Le remplacement jette la tentative cassée (pas d'historique des fichiers rejetés). Acceptable : une photo a_corriger n'a ni Dossier ni valeur d'audit ; les corrections cellule par cellule, elles, restent journalisées (D-ING-5).

Déclencheur de réexamen (pré-enregistré)

  • Besoin de tracer les fichiers rejetés (si un audit réclame l'historique des tentatives cassées) → archiver le brut de la tentative au lieu de le supprimer, avec un statut remplace.