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 photovalidede même date. Une photoa_corrigerlaissait 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 proprevalide, 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 (ligneDocumentRH+ fichier brut ; elle n'a pas deDossier). 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_AJOUTEESnon concerné. - Limite assumée. Le remplacement jette la tentative cassée (pas d'historique des
fichiers rejetés). Acceptable : une photo
a_corrigern'a niDossierni 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.