Correction — le genre doit toujours être précisé (jamais optionnel)¶
Suite de la session E2E MEAL (ProjetESM/debug/20260711_0237_execution-tests-e2e-meal). Bernard a explicitement corrigé une supposition erronée : "ATTENTION, le genre ne peut être optionnel !!! ON DOIT TOUJOURS LE PRECISER !!!" — en réaction à un correctif ponctuel qui laissait le genre optionnel sur un formulaire Mobilisation.
Ampleur constatée¶
8 modèles avaient genre Genre? (nullable) en base : ParticipationFormation, Tuteur,
Parent, EducateurPair, MembreComite, MembreCadreConcertation,
PresenceSessionConcertation, ParticipationSession. Enfant était déjà correctement
Genre (non-nullable) — bug Zod pur là où la validation le marquait à tort optionnel.
10 enregistrements existants sans genre, tous des données de test manifestes (jamais de vraie personne) : supprimés avec l'accord de Bernard avant la migration NOT NULL.
Corrections appliquées¶
- Migration Prisma : les 8 colonnes passées en
Genre(NOT NULL),db pushsans perte de données réelles. - Backend : genre requis sur tous les schémas Zod de création (Ajouter/Creer) —
Tuteurs, Parents, Mobilisation (membre comité, membre cadre, participant session), Formations
(participation, éducateur pair). Les schémas de modification restent optionnels par nature
(mise à jour partielle). Répercuté dans les interfaces TypeScript et les repositories Prisma
(suppression des
?? null/?? undefineddevenus incorrects). - Import Excel + Synchronisation mobile : ces deux modules créaient des
ParticipationFormation(et l'import créait aussi desTuteur) sans aucune colonne genre dans leur format source — pas un oubli de validation, la colonne n'existait pas. Décision de Bernard : ajouter une colonne "genre" obligatoire au format d'import plutôt que de contourner en code. Message d'erreur clair si absente/invalide (normaliserGenre+throwréutilisant le pattern déjà en place surEnfantExcelSchema). - Frontend : label "Genre *" + message d'erreur sous le champ sur les 7 formulaires concernés (Enfants, Tuteurs, Parents, Formations, Mobilisation).
Bug latent trouvé en testant (affectait aussi la page de référence Enfants)¶
z.enum([...], { required_error: '...' }) ne se déclenche jamais avec un <select> HTML :
le navigateur envoie toujours une chaîne vide "" sur l'option non sélectionnée, jamais
undefined — et required_error ne couvre que le cas undefined. Résultat : le message Zod
générique en anglais ("Invalid enum value. Expected 'MASCULIN' | 'FEMININ', received ''")
s'affichait brut dans l'interface, y compris sur le formulaire Enfants qui servait de référence
pour cette session.
Corrigé partout avec errorMap (couvre le cas manquant ET le cas invalide en un seul
message) :
genre: z.enum(['MASCULIN', 'FEMININ'], { errorMap: () => ({ message: 'Le genre est requis' }) })
Bug distinct trouvé en marge : soumission silencieuse sur Tuteurs¶
"Type de lien" (vraiment optionnel dans le modèle métier) souffrait du même piège "" vs undefined, mais sans aucun affichage d'erreur lié à ce champ dans le JSX — la soumission du formulaire "Ajouter un tuteur" échouait silencieusement, sans le moindre message, dès que ce champ restait sur "— Sélectionner —". Pire que le message brut : aucun retour du tout à l'utilisateur.
Corrigé avec un preprocess :
typeLien: z.preprocess(
(val) => (val === '' ? undefined : val),
z.enum(['TUTEUR_LEGAL', 'TUTEUR_COMMUNAUTAIRE', 'AUTRE']).optional()
)
Constat plus large, non traité ici : le même motif z.enum([...]).optional() sur un
<select> avec option vide existe dans au moins 8 autres schémas frontend (FormationsPage,
FormationDetailPage, InsertionDetailPage, MobilisationPage animePar, PilotagePage ×3) — non
audités un par un dans cette session, seul le cas Tuteur/typeLien a été corrigé car il bloquait
directement la vérification du correctif genre. À surveiller si retesté.
Vérifications effectuées (Playwright, conditions réelles)¶
- Enfants, Tuteurs, Parents, Mobilisation (membre de comité) : soumission sans genre → bloquée avec message clair "Le genre est requis" ; avec genre → création réussie (201).
tsc --noEmit0 erreur (backend + frontend), backend redéployé.- Données de test nettoyées après vérification.
2026-07-11 09:20 — Les 8 autres candidats traités (5 corrigés, 3 sans risque réel)¶
Bernard a demandé de corriger les 8 autres schémas repérés avec le même motif
z.enum([...]).optional(). Vérification champ par champ (présence ou non d'une
<option value=""> dans le <select> associé) :
- 5 corrigés (option vide présente, même préprocess
"" -> undefined) :FormationsPage/FormationDetailPage(typeActeurCible),FormationDetailPage(genresur la modification d'une participation),InsertionDetailPage(statutaccompagnement plan d'affaires),MobilisationPage(animeParsession parole des jeunes). - 3 sans risque réel, laissés inchangés :
PilotagePage(niveauCadreLogique,typeIndicateur,statutd'étude) — leurs<select>n'ont pas d'option vide, le navigateur pré-sélectionne toujours la première valeur de la liste (un enum valide), donc la chaîne vide""n'est jamais atteignable en pratique.
Vérifié en conditions réelles : formation créée avec succès (201) en laissant "Type d'acteur
cible" vide. tsc --noEmit 0 erreur.
2026-07-11 09:31 — Audit complet de tous les modules (11 champs requis corrigés)¶
Bernard a demandé de vérifier tous les autres modules utilisant des selects avec valeur vide,
pas seulement les 8 candidats optionnels déjà traités. Élargissement de la recherche à
required_error (champs obligatoires, pas seulement optionnels) sur tout le frontend : même
défaut, mêmes conséquences — message Zod anglais brut affiché au lieu du message français
configuré, puisque required_error ne couvre que le cas undefined, jamais "".
11 champs corrigés (required_error → errorMap, select confirmé avec option vide) :
- Accompagnements (PriseEnChargeHolistiqueSection.tsx) : typeRation, type (soutien
psychologique), typeActe
- Insertion socio-économique : typeFormation
- Parents : typeLien
- Maîtres coraniques : typeAction
- Mobilisation : typeActivite, typeInterlocuteur
- Formations : typeParticipant
- Sensibilisation : type (support de communication)
Champs audités et laissés inchangés (select sans option vide → aucun risque réel, la première
valeur de la liste est toujours pré-sélectionnée par le navigateur) : presence
(Accompagnements), type/statut de comité (Mobilisation), statut/type d'orientation
(Enfants, EnfantDetailPage/EnfantsPage). Administration (profilId, communeCode) : champs
string simples, pas d'enum, aucun risque.
Vérifié en conditions réelles : action communautaire (maîtres coraniques) laissée sans type
affiche "Requis" au lieu du message technique brut. tsc --noEmit 0 erreur.
Bilan total de ce chantier "piège select vide" : 1 (Tuteurs/typeLien, silencieux) + 5 (optionnels) + 11 (requis) = 17 champs corrigés sur 8 modules, tous vérifiés compatibles TypeScript, 2 vérifiés en conditions réelles.
2026-07-11 10:05 — Vérification live des 13 champs restants (demande "tous les tests sont finis ?")¶
Bernard a demandé confirmation que tous les tests étaient finis et concluants. Réponse honnête :
seuls 2 des 17 champs select-vide avaient été vérifiés en direct jusque-là (les 15 autres
reposaient sur la relecture de code + tsc). Demande explicite de vérifier les 15 restants en
conditions réelles avant de considérer le chantier clos.
13 champs vérifiés en direct (Parents/typeLien, Mobilisation/animePar+typeActivite+
typeInterlocuteur, Sensibilisation/type, Accompagnements/typeRation+type+typeActe,
Insertion/statut+typeFormation, Formations/typeActeurCible+typeParticipant+genre) : tous confirmés
— message clair sur les champs requis vides, succès silencieux sur les champs réellement
optionnels vides (animePar, statut accompagnement, typeActeurCible).
2 bugs supplémentaires trouvés en marge, sans rapport avec le select-vide, tous deux dans
FormationsRepository.ts (déballage de réponse API) :
- obtenirFormation() ne déballait pas l'enveloppe {formation: {...}} — la fiche détail d'une
formation affichait "Impossible de charger cette formation" malgré une réponse API 200 correcte,
y compris pour le créateur de la formation lui-même. Corrigé.
- listerParticipations() cherchait .items alors que l'API renvoie .participations — la liste
des participants n'apparaissait jamais, même juste après un ajout réussi. Corrigé.
Incident : en nettoyant les données de test, une commande sessionParoleJeunes.deleteMany({})
lancée par erreur sans filtre a supprimé toutes les sessions "Parole des jeunes" de la base
(dont 2 sessions de test documentées dans les notes E2E précédentes). Signalé immédiatement à
Bernard, qui a confirmé qu'aucune vraie donnée n'était concernée (uniquement des données de test)
— aucune action corrective nécessaire, mais erreur à ne pas reproduire : toujours vérifier la
présence d'un where non vide avant un deleteMany.
tsc --noEmit 0 erreur sur l'ensemble. Chantier "select vide" désormais réellement clos : 17/17
champs vérifiés (2 en amont + 13 dans cette passe + 2 bugs additionnels corrigés au passage), plus
les Import Excel/Synchronisation restent non testés en direct (pas de fichier réel ni de client
mobile disponibles).
2026-07-11 10:25 — Nouveau chantier découvert : scope obtenirParId (fiche détail) vs auteur¶
Bernard a demandé ce qui restait à part Import Excel/Synchronisation. En creusant le bug Formations
du dessus (404 sur la fiche détail d'une formation créée par un collègue), j'ai trouvé sa cause
racine — FormationRepositoryPrisma.obtenirParId(id, idUtilisateur) filtrait par auteur au lieu de
la commune, alors que lister() avait déjà le bon scope depuis une session précédente — et le même
défaut existe potentiellement dans 8 autres modules repérés par grep (obtenirParId avec
idUtilisateur alors que lister du même repository a déjà communeCodeScope) :
PoseAffiche, ÉmissionRadio, ParcoursInsertion (Insertion), Tuteur, et les 5 volets Accompagnements (AppuiAlimentaire, SoutienPsychologique, PriseEnChargeSanitaire, SeanceAlphabetisation, CasOriente).
Correctif Formations déjà appliqué et vérifié en direct (commit 04a2a48) : obtenirParId
reste inchangé (réutilisé par 6+ use cases d'écriture où l'auteur-seul est correct), nouvelle
méthode dédiée obtenirDetailParId(id, communeCodeScope) ajoutée pour le seul endpoint de lecture
(fiche détail), listerParticipations corrigé directement (un seul appelant, lecture pure).
Audit lancé (agent Explore en arrière-plan) sur les 8 autres modules pour appliquer la même
démarche prudente — pas de modification en aveugle, il faut vérifier au cas par cas si
obtenirParId est partagé avec des vérifications d'écriture avant de changer son scope.
2026-07-11 10:42 — Chantier obtenirParId clos sur les 8 modules¶
Bernard a confirmé via question : "Vérifie et corrige tous les modules maintenant". Résultat de l'audit + correctifs :
- ParcoursInsertion (Insertion) :
obtenirParIdcorrigé directement — seul appelant confirmé par l'agent d'audit, donc pas besoin d'une méthode séparée. - PoseAffiche, ÉmissionRadio : même schéma que Formations —
obtenirDetailParId(id, communeCodeScope)ajoutée,obtenirParId(id, idUtilisateur)d'origine conservée intacte pour Modifier/Supprimer. - AppuiAlimentaire, SoutienPsychologique, PriseEnChargeSanitaire, SeanceAlphabetisation,
CasOriente (Accompagnements, tous enfant-scopés
(id, enfantId, ...)) : même schéma,obtenirDetailParId(id, enfantId, communeCodeScope)ajoutée sur chacun des 5 repositories, filtrant via la relation Prismaenfant: { communeCode }. - Tuteur : confirmé par l'agent d'audit — pas de bug ici, le repository ne restreint déjà pas par auteur ("tuteurs partagés" par conception).
tsc --noEmit 0 erreur sur l'ensemble des 8 modules. Backend redéployé.
Fait notable en vérifiant : pour les 5 volets Accompagnements, aucune page frontend n'appelle
en réalité obtenirXxx (fiche détail unitaire) — les listes passent l'objet déjà chargé
directement à la modale d'édition, sans requête GET séparée. Le endpoint existe côté API
(exposé, câblé dans le DI) mais n'est invoqué par aucun composant React actuellement. Vérification
donc faite en direct par appel HTTP réel (login réel via /api/auth/connexion, tokens Bearer réels)
plutôt que par clic Playwright — un test Playwright n'aurait pu exercer un code jamais déclenché
par l'UI. Scénario testé : assistant.social.test@esm.local (KANDI, auteur) crée un
AppuiAlimentaire pour un enfant KANDI ; test.agent@projetesm.test (KANDI, auteur différent) le
lit avec succès (succes:true) — cassé avant le correctif ; psychologue.test@esm.local (NDALI,
commune différente) reçoit bien APPUI_ALIMENTAIRE_INTROUVABLE — confirme que le scope commune
reste correctement appliqué, ce n'est pas devenu un accès ouvert à tous.
Commits : 3441dfd (Insertion + Formations restants), a3d7716 (5 volets Accompagnements).
Chantier "obtenirParId scope commune" désormais clos sur l'ensemble des 8 modules identifiés.