Un lycéen a une seule ligne dans parcours-lyceens, mise à jour progressivement à chaque étape
du questionnaire via partialUpdate(). C'est l'inverse du parcours éclaireur qui utilise plusieurs lignes.
Étapes du questionnaire (ordre réel)
Définies dans front/src/pages/publicspace/questionnaire/lyceen/form-lyceen.ts.
- Niveau (
niveau) — Seconde / Première / Terminale. Seconde sort du questionnaire (pas de suite). - Collège ou Seconde — écran d'information uniquement, aucune sauvegarde.
- Filière (
filiere) — Bac général / techno / pro. Visible à partir de Première. - Spés Première (
spes_premiere) — 3 spécialités. Uniquement Première + Bac Général. - Spés Terminale (
spes_terminale) — 2 spécialités. Uniquement Terminale + Bac Général. - Abandon de spé (
spe_abandonnee_premiere) — Terminale + Bac Général, sispes_terminaledéjà rempli. - Option Terminale (
options_terminale) — Terminale + Bac Général + une spé abandonnée. Maths expertes et Maths complémentaires sont mutuellement exclusives. - Domaines (
domains) — Première/Terminale, tous bacs. - Matières (
matieres) — ne garde que les matières notées. - Moyenne Première (
moyennePremiere) — uniquement Terminale Bac Pro. - Matières Terminale (
matieres_terminale) — uniquement Terminale Bac Général. - Environnement (
environnement) — regroupe 4 champs plats (lycée/fac/école/CFA) en un tableau. - Encadrement — boursier, budget, projet d'études sup, accompagnement souhaité.
- Lycée (dernière étape) — département, établissement.
Bac Pro et Bac Général se répartissent sur des champs quasi disjoints : le Bac Pro saute toutes les étapes spés/options de Terminale générale au profit de la moyenne de Première.
Fonctionnement technique du formulaire
Le wizard est le composant générique components/questionnaire/Questionnaire.vue (partagé avec le parcours éclaireur), piloté par le tableau questionnaire exporté par form-lyceen.ts. Le routing matche route.params.stepName sur step.name ; une étape disparaît de la navigation dès que toutes ses questions ont show(model) === false (filtre émergent, pas de saut explicite codé en dur).
partial-updateest fire-and-forget. Sur « Suivant » comme « Précédent »,onStepSave(→partialUpdate) part sans êtreawaité avant lerouter.push: une erreur réseau est juste loguée en console, l'utilisateur est déjà sur l'écran suivant sans le savoir.- Le POST final ne réutilise pas le modèle tel quel.
filterModel()(dansQuestionnaireLyceen.vue) ré-exécute leparseDataToSavede toutes les étapes sur le modèle complet pour reconstruire le payload envoyé àpostFormulaireLyceen, pas seulement celui de la dernière étape. loadData: truene recharge pas les réponses précédentes. Il sert uniquement à détecter un « rerun » (résultat déjà calculé) pour afficher l'icône de sortie vers/etudes— reprendre un parcours abandonné en cours ne pré-remplit aucun champ malgré le nom de la prop.- Validation 100 % client, étape par étape.
currentStepIsValidne vérifie que les questions visibles de l'étape courante (required+rules), pas le modèle entier ; c'est ce qui active/désactive le bouton « Suivant »/« Terminer ». Un champrequiredvide bloque le bouton mais n'affiche aucun message (le tooltip reste vide) — seule unerules()en échec alimente le message affiché, et dans ce casrules()est réexécutée une seconde fois pour construire ce message (pas de cache du résultat).
Pièges
partial-updatene crée jamais de ligne. Si le lycéen n'a pas encore deparcours-lyceens(compte tout neuf), l'appel intermédiaire ne fait rien et retourne silencieusement — seule la soumission finale (POST /parcours-lyceen/formulaire) crée la première ligne. Contrairement à ce que suggère « mise à jour progressive », un lycéen qui abandonne avant la fin n'a rien de sauvegardé.- Étape « Lycée » : noms de champs incohérents. Elle envoie
lycee/lyceeNom, alors que l'entité aetablissement/etablissementNom. Sans conséquence en usage normal (cette étape ne déclenche unpartial-updatequ'en cliquant « Précédent », pas en la validant), mais si ça arrive, l'établissement choisi n'est alors pas persisté en intermédiaire. abandon_terminale(front) →spe_abandonnee_premiere(colonne). Piège si on grep un seul des deux noms.moyenne(jsonb) n'est jamais rempli par le questionnaire — uniquement par la réponse de l'algorithme externe, à la soumission finale.- Champs obsolètes toujours présents :
boostAlternanceApprentissage,boostConnaisPersonne,boostPartirne sont plus collectés depuis l'INSP-662 mais restent dans l'entité (marqués obsolètes en commentaire). - Aucune validation serveur sur
/parcours-lyceen/partial-updateet/parcours-lyceen/formulaire. Les deux routes typent@Body() body: any(parcours-lyceen.controller.ts) : leValidationPipeglobal strict (whitelist/forbidNonWhitelisteddansmain.ts) ne s'applique jamais, Nest sautant la validation dès que le paramètre n'a pas de type DTO reflété. Le seul DTO existant (ParcoursLyceenUpdateDto) est branché sur une autre route (patch user), pas sur celles-ci. Colonnes entité toutesnullable, sans enum ni contrainte DB. La validation client documentée ci-dessus est donc la seule barrière ; un payload posté directement (hors formulaire) est accepté tel quel, et/formulaireavale même ses erreurs silencieusement (catch (error) {}vide).
Les structures des champs JSONB sont typées dans
parcours-lyceen.entity.ts.