From da7f9a334f1ec6af124c7349f07850112984c446 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Sat, 1 Jul 2023 17:42:04 +0200
Subject: [PATCH] =?UTF-8?q?Export=20Apog=C3=A9e:=20setup=20via=20test=20un?=
 =?UTF-8?q?itaire,=20maquette=20de=20test.=20Corrige=20cas=202=20modules?=
 =?UTF-8?q?=20avec=20m=C3=AAme=20code.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/models/formsemestre.py                 |  5 ++
 app/scodoc/sco_apogee_csv.py               | 16 +++--
 app/scodoc/sco_apogee_reader.py            | 11 ++-
 pytest.ini                                 |  1 +
 tests/ressources/apogee/BUT-INFO-S2.txt    | 76 +++++++++++++++++++++
 tests/ressources/yaml/cursus_but_info.yaml | 79 ++++++++++++----------
 tests/unit/test_apogee_export.py           | 54 +++++++++++++++
 tests/unit/yaml_setup.py                   | 16 +++++
 8 files changed, 218 insertions(+), 40 deletions(-)
 create mode 100644 tests/ressources/apogee/BUT-INFO-S2.txt
 create mode 100644 tests/unit/test_apogee_export.py

diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index 64668fa16..6b76e2250 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -526,6 +526,11 @@ class FormSemestre(db.Model):
             return ""
         return ", ".join(sorted([etape.etape_apo for etape in self.etapes if etape]))
 
+    def add_etape(self, etape_apo: str):
+        "Ajoute une étape"
+        etape = FormSemestreEtape(formsemestre_id=self.id, etape_apo=etape_apo)
+        db.session.add(etape)
+
     def regroupements_coherents_etud(self) -> list[tuple[UniteEns, UniteEns]]:
         """Calcule la liste des regroupements cohérents d'UE impliquant ce
         formsemestre.
diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py
index b8addd5be..2d3b6af44 100644
--- a/app/scodoc/sco_apogee_csv.py
+++ b/app/scodoc/sco_apogee_csv.py
@@ -216,7 +216,12 @@ class ApoEtud(dict):
                             break
                 self.col_elts[code] = elt
                 if elt is None:
-                    self.new_cols[col_id] = self.cols[col_id]
+                    try:
+                        self.new_cols[col_id] = self.cols[col_id]
+                    except KeyError as exc:
+                        raise ScoFormatError(
+                            f"""Fichier Apogee invalide : ligne mal formatée ? <br>colonne <tt>{col_id}</tt> non déclarée ?"""
+                        ) from exc
                 else:
                     try:
                         self.new_cols[col_id] = sco_elts[code][
@@ -343,14 +348,17 @@ class ApoEtud(dict):
         module_code_found = False
         for modimpl in modimpls:
             module = modimpl["module"]
-            if module["code_apogee"] and code in {
-                x.strip() for x in module["code_apogee"].split(",")
-            }:
+            if (
+                res.modimpl_inscr_df[modimpl["moduleimpl_id"]][etudid]
+                and module["code_apogee"]
+                and code in {x.strip() for x in module["code_apogee"].split(",")}
+            ):
                 n = res.get_etud_mod_moy(modimpl["moduleimpl_id"], etudid)
                 if n != "NI" and self.export_res_modules:
                     return dict(N=self.fmt_note(n), B=20, J="", R="")
                 else:
                     module_code_found = True
+
         if module_code_found:
             return VOID_APO_RES
         #
diff --git a/app/scodoc/sco_apogee_reader.py b/app/scodoc/sco_apogee_reader.py
index 1e7984336..2a56d85d9 100644
--- a/app/scodoc/sco_apogee_reader.py
+++ b/app/scodoc/sco_apogee_reader.py
@@ -295,8 +295,15 @@ class ApoCSVReadWrite:
                     filename=self.get_filename(),
                 )
             cols = {}  # { col_id : value }
-            for i, field in enumerate(fields):
-                cols[self.col_ids[i]] = field
+            try:
+                for i, field in enumerate(fields):
+                    cols[self.col_ids[i]] = field
+            except IndexError as exc:
+                raise
+                raise ScoFormatError(
+                    f"Fichier Apogee incorrect (colonnes excédentaires ? (<tt>{i}/{field}</tt>))",
+                    filename=self.get_filename(),
+                ) from exc
             etud_tuples.append(
                 ApoEtudTuple(
                     nip=fields[0],  # id etudiant
diff --git a/pytest.ini b/pytest.ini
index e4d9d0bed..e92885fe6 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,6 +1,7 @@
 [pytest]
 markers =
     slow: marks tests as slow (deselect with '-m "not slow"')
+    apo
     but_gb
     but_gccd
     but_mlt
diff --git a/tests/ressources/apogee/BUT-INFO-S2.txt b/tests/ressources/apogee/BUT-INFO-S2.txt
new file mode 100644
index 000000000..1970f0fef
--- /dev/null
+++ b/tests/ressources/apogee/BUT-INFO-S2.txt
@@ -0,0 +1,76 @@
+XX-APO_TITRES-XX
+apoC_annee	2021/2022
+apoC_cod_dip	DIPTIS2
+apoC_Cod_Exp	2
+apoC_cod_vdi	17
+apoC_Fichier_Exp	export.txt
+apoC_lib_dip	BUT INFO TEST
+apoC_Titre1	Maquette pour tests unitaires sur un BUT Info S2
+apoC_Titre2	
+							
+XX-APO_TYP_RES-XX
+10	AB1	AB2	ABI	ABJ	ADM	AJ	AJRO	C1	DEF	DIF	
+18	AB1	AB2	ABI	ABJ	ADM	ADMC	ADMD	AJ	AJAC	AJAR	AJRO	ATT	B1	C1	COMP	DEF	DIF	NAR	
+45	ABI	ABJ	ADAC	ADM	ADMC	ADMD	AIR	AJ	AJAR	AJCP	AJRO	AJS	ATT	B1	B2	C1	COMP	CRED	DEF	DES	DETT	DIF	ENER	ENRA	EXC	INFA	INFO	INST	LC	MACS	N1	N2	NAR	NON	NSUI	NVAL	OUI	SUIV	SUPS	TELE	TOEF	TOIE	VAL	VALC	VALR	
+10	ABI	ABJ	ADMC	COMP	DEF	DIS	NVAL	VAL	VALC	VALR	
+AB1 : Ajourné en B2 mais admis en B1	AB2 : ADMIS en B1 mais ajourné en B2	ABI : Absence	ABJ : Absence justifiée	ADM : Admis	AJ : Ajourné	AJRO : Ajourné - Réorientation Obligatoire	C1 : Niveau C1	DEF : Défaillant	DIF : Décision différée	
+AB1 : Ajourné en B2 mais admis en B1	AB2 : ADMIS en B1 mais ajourné en B2	ABI : Absence	ABJ : Absence justifiée	ADM : Admis	ADMC : Admis avec compensation	ADMD : Admis (passage avec dette)	AJ : Ajourné	AJAC : Ajourné mais accès autorisé à étape sup.	AJAR : Ajourné et Admis A Redoubler	AJRO : Ajourné - Réorientation Obligatoire	ATT : En attente de décison	B1 : Niveau B1	C1 : Niveau C1	COMP : Compensé	DEF : Défaillant	DIF : Décision différée	NAR : Ajourné non admis à redoubler	
+ABI : Absence	ABJ : Absence justifiée	ADAC : Admis avant choix	ADM : Admis	ADMC : Admis avec compensation	ADMD : Admis (passage avec dette)	AIR : Ingénieur spécialité Informatique appr	AJ : Ajourné	AJAR : Ajourné et Admis A Redoubler	AJCP : Ajourné mais autorisé à compenser	AJRO : Ajourné - Réorientation Obligatoire	AJS : Ajourné (note éliminatoire)	ATT : En attente de décison	B1 : Niveau B1	B2 : Niveau B2	C1 : Niveau C1	COMP : Compensé	CRED : Eléments en crédits	DEF : Défaillant	DES : Désistement	DETT : Eléments en dettes	DIF : Décision différée	ENER : Ingénieur spécialité Energétique	ENRA : Ingénieur spécialité Energétique appr	EXC : Exclu	INFA : Ingénieur spécialité Informatique appr	INFO : Ingénieur spécialié Informatique	INST : Ingénieur spécialité Instrumentation	LC : Liste complémentaire	MACS : Ingénieur spécialité MACS	N1 : Compétences CLES	N2 : Niveau N2	NAR : Ajourné non admis à redoubler	NON : Non	NSUI : Non suivi(e)	NVAL : Non Validé(e)	OUI : Oui	SUIV : Suivi(e)	SUPS : Supérieur au seuil	TELE : Ingénieur spéciailté Télécommunications	TOEF : TOEFL	TOIE : TOEIC	VAL : Validé(e)	VALC : Validé(e) par compensation	VALR : Validé(e) Retrospectivement	
+ABI : Absence	ABJ : Absence justifiée	ADMC : Admis avec compensation	COMP : Compensé	DEF : Défaillant	DIS : Dispense examen	NVAL : Non Validé(e)	VAL : Validé(e)	VALC : Validé(e) par compensation	VALR : Validé(e) Retrospectivement	
+							
+XX-APO_COLONNES-XX
+apoL_a01_code	Type Objet	Code	Version	Année	Session	Admission/Admissibilité	Type Rés.			Etudiant	Numéro
+apoL_a02_nom											Nom
+apoL_a03_prenom											Prénom
+apoL_a04_naissance									Session	Admissibilité	Naissance
+APO_COL_VAL_DEB
+apoL_c0001	ELP	V1INFU21		2021	0	1	N	V1INFU21 - UE 2.1 Réaliser	0	1	Note
+apoL_c0002	ELP	V1INFU21		2021	0	1	B		0	1	Barème
+apoL_c0003	ELP	V1INFU21		2021	0	1	J		0	1	Pts Jury
+apoL_c0004	ELP	V1INFU21		2021	0	1	R		0	1	Résultat
+apoL_c0005	ELP	V1INFU22		2021	0	1	N	V1INFU22 - UE 2.2 Optimiser	0	1	Note
+apoL_c0006	ELP	V1INFU22		2021	0	1	B		0	1	Barème
+apoL_c0007	ELP	V1INFU22		2021	0	1	J		0	1	Pts Jury
+apoL_c0008	ELP	V1INFU22		2021	0	1	R		0	1	Résultat
+apoL_c0009	ELP	V1INFU23		2021	0	1	N	V1INFU23 - UE 2.3 Administrer	0	1	Note
+apoL_c0010	ELP	V1INFU23		2021	0	1	B		0	1	Barème
+apoL_c0011	ELP	V1INFU23		2021	0	1	J		0	1	Pts Jury
+apoL_c0012	ELP	V1INFU23		2021	0	1	R		0	1	Résultat
+apoL_c0013	ELP	V1INFU24		2021	0	1	N	V1INFU24 - UE 2.4 Gérer	0	1	Note
+apoL_c0014	ELP	V1INFU24		2021	0	1	B		0	1	Barème
+apoL_c0015	ELP	V1INFU24		2021	0	1	J		0	1	Pts Jury
+apoL_c0016	ELP	V1INFU24		2021	0	1	R		0	1	Résultat
+apoL_c0017	ELP	V1INFU25		2021	0	1	N	V1INFU25 - UE 2.5 Conduire	0	1	Note
+apoL_c0018	ELP	V1INFU25		2021	0	1	B		0	1	Barème
+apoL_c0019	ELP	V1INFU25		2021	0	1	J		0	1	Pts Jury
+apoL_c0020	ELP	V1INFU25		2021	0	1	R		0	1	Résultat
+apoL_c0021	ELP	V1INFU26		2021	0	1	N	V1INFU26 - UE 2.6 Travailler	0	1	Note
+apoL_c0022	ELP	V1INFU26		2021	0	1	B		0	1	Barème
+apoL_c0023	ELP	V1INFU26		2021	0	1	J		0	1	Pts Jury
+apoL_c0024	ELP	V1INFU26		2021	0	1	R		0	1	Résultat
+apoL_c0025	ELP	VINFR201		2021	0	1	N	VINFR201 - Développement orienté objets	0	1	Note
+apoL_c0026	ELP	VINFR201		2021	0	1	B		0	1	Barème
+apoL_c0027	ELP	VINFR207		2021	0	1	N	VINFR207 - Graphes	0	1	Note
+apoL_c0028	ELP	VINFR207		2021	0	1	B		0	1	Barème
+apoL_c0029	ELP	VINFPOR2		2021	0	1	N	VINFPOR2 - Portfolio	0	1	Note
+apoL_c0030	ELP	VINFPOR2		2021	0	1	B		0	1	Barème
+apoL_c0031	ELP	TIRW2		2021	0	1	N	TIRW2 - Semestre 2 BUT INFO 2	0	1	Note
+apoL_c0032	ELP	TIRW2		2021	0	1	B		0	1	Barème
+apoL_c0033	ELP	TIRW2		2021	0	1	J		0	1	Pts Jury
+apoL_c0034	ELP	TIRW2		2021	0	1	R		0	1	Résultat
+apoL_c0035	ELP	TIRO		2021	0	1	N	TIRO - Année BUT 1 RT	0	1	Note
+apoL_c0036	ELP	TIRO		2021	0	1	B		0	1	Barème
+apoL_c0037	VET	TI1	117	2021	0	1	N	TI1 - BUT INFO an1	0	1	Note
+apoL_c0038	VET	TI1	117	2021	0	1	B		0	1	Barème
+apoL_c0039	VET	TI1	117	2021	0	1	J		0	1	Pts Jury
+apoL_c0040	VET	TI1	117	2021	0	1	R		0	1	Résultat
+APO_COL_VAL_FIN
+apoL_c0041	APO_COL_VAL_FIN							  	
+							
+XX-APO_VALEURS-XX
+apoL_a01_code	apoL_a02_nom	apoL_a03_prenom	apoL_a04_naissance	apoL_c0001	apoL_c0002	apoL_c0003	apoL_c0004	apoL_c0005	apoL_c0006	apoL_c0007	apoL_c0008	apoL_c0009	apoL_c0010	apoL_c0011	apoL_c0012	apoL_c0013	apoL_c0014	apoL_c0015	apoL_c0016	apoL_c0017	apoL_c0018	apoL_c0019	apoL_c0020	apoL_c0021	apoL_c0022	apoL_c0023	apoL_c0024	apoL_c0025	apoL_c0026	apoL_c0027	apoL_c0028	apoL_c0029	apoL_c0030	apoL_c0031	apoL_c0032	apoL_c0033	apoL_c0034	apoL_c0035	apoL_c0036	apoL_c0037	apoL_c0038	apoL_c0039	apoL_c0040
+
+1001	ex_a1	Jean	10/01/2003																																								
+1002	ex_a2	Lucie	11/01/2003																																								
+1003	ex_b1	Hélène	11/01/2003																																								
+1004	ex_b2	Rose	11/01/2003																																								
diff --git a/tests/ressources/yaml/cursus_but_info.yaml b/tests/ressources/yaml/cursus_but_info.yaml
index f735e1e56..52ab37599 100644
--- a/tests/ressources/yaml/cursus_but_info.yaml
+++ b/tests/ressources/yaml/cursus_but_info.yaml
@@ -1,4 +1,4 @@
-# Tests unitaires 
+# Tests unitaires
 # Le BUT Info a 4 parcours qui partagent certains niveaux de compétences
 # mais à ces niveaux sont associés des UEs dont les coefficients des ressources
 # varient selon le parcours.
@@ -14,58 +14,58 @@ Formation:
   # nota: les associations UE/Niveaux sont déjà données dans ce fichier XML.
   ues:
     # S1
-    'UE11':
+    "UE11":
       annee: BUT1
-    'UE12':
+    "UE12":
       annee: BUT1
-    'UE13':
+    "UE13":
       annee: BUT1
-    'UE14':
+    "UE14":
       annee: BUT1
-    'UE15':
+    "UE15":
       annee: BUT1
-    'UE16':
+    "UE16":
       annee: BUT1
     # S2
-    'UE21':
+    "UE21":
       annee: BUT1
-    'UE22':
+    "UE22":
       annee: BUT1
-    'UE23':
+    "UE23":
       annee: BUT1
-    'UE24':
+    "UE24":
       annee: BUT1
-    'UE25':
+    "UE25":
       annee: BUT1
-    'UE26':
+    "UE26":
       annee: BUT1
     # S3
-    'UE31':
+    "UE31":
       annee: BUT2
-    'UE32':
+    "UE32":
       annee: BUT2
-    'UE33':
+    "UE33":
       annee: BUT2
-    'UE34':
+    "UE34":
       annee: BUT2
-    'UE35':
+    "UE35":
       annee: BUT2
-    'UE36':
+    "UE36":
       annee: BUT2
     # S4
-    'UE41-A': # UE pour le parcours A
+    "UE41-A": # UE pour le parcours A
       annee: BUT2
-    'UE41-B': # UE pour le parcours B (même contenu, coefs différents)
+    "UE41-B": # UE pour le parcours B (même contenu, coefs différents)
       annee: BUT2
-    'UE42':
+    "UE42":
       annee: BUT2
-    'UE43':
+    "UE43":
       annee: BUT2
-    'UE44':
+    "UE44":
       annee: BUT2
-    'UE45':
+    "UE45":
       annee: BUT2
-    'UE46':
+    "UE46":
       annee: BUT2
 
 FormSemestres:
@@ -74,37 +74,41 @@ FormSemestres:
     idx: 1
     date_debut: 2021-09-01
     date_fin: 2022-01-15
-    codes_parcours: ['A', 'B']
+    codes_parcours: ["A", "B"]
   S2:
     idx: 2
     date_debut: 2022-01-16
     date_fin: 2022-06-30
-    codes_parcours: ['A', 'B']
+    codes_parcours: ["A", "B"]
+    elt_sem_apo: TIRW2
+    elt_annee_apo: TIRO
+    etape_apo: TI1!117
   S3:
     idx: 3
     date_debut: 2022-09-01
     date_fin: 2023-01-15
-    codes_parcours: ['A', 'B']
+    codes_parcours: ["A", "B"]
   S4:
     idx: 4
     date_debut: 2023-01-16
     date_fin: 2023-06-30
-    codes_parcours: ['A', 'B']
+    codes_parcours: ["A", "B"]
   S5:
     idx: 5
     date_debut: 2023-09-01
     date_fin: 2024-01-15
-    codes_parcours: ['A', 'B']
+    codes_parcours: ["A", "B"]
   S6:
     idx: 6
     date_debut: 2024-01-16
     date_fin: 2024-06-30
-    codes_parcours: ['A', 'B']
+    codes_parcours: ["A", "B"]
 
 Etudiants:
   ex_a1: # cursus S1 -> S6, valide tout
     prenom: Jean
     civilite: M
+    code_nip: 1001
     formsemestres:
       # on ne note que le portfolio, qui affecte toutes les UEs
       S1:
@@ -115,6 +119,7 @@ Etudiants:
         parcours: A
         notes_modules:
           "P2": 12
+          "R2.04-A": 16
       S3:
         parcours: A
         notes_modules:
@@ -135,6 +140,7 @@ Etudiants:
   ex_a2: # cursus S1 -> S6, valide tout sauf S5
     prenom: Lucie
     civilite: F
+    code_nip: 1002
     formsemestres:
       # on ne note que le portfolio, qui affecte toutes les UEs
       S1:
@@ -145,6 +151,7 @@ Etudiants:
         parcours: A
         notes_modules:
           "P2": 12
+          "R2.04-A": 17
       S3:
         parcours: A
         notes_modules:
@@ -161,10 +168,11 @@ Etudiants:
         parcours: A
         notes_modules:
           "P6-A": 16
-  
+
   ex_b1: # cursus S1 -> S6, valide tout
     prenom: Hélène
     civilite: F
+    code_nip: 1003
     formsemestres:
       # on ne note que le portfolio, qui affecte toutes les UEs
       S1:
@@ -175,6 +183,7 @@ Etudiants:
         parcours: B
         notes_modules:
           "P2": 12
+          "R2.04-B": 18
       S3:
         parcours: B
         notes_modules:
@@ -191,10 +200,11 @@ Etudiants:
         parcours: B
         notes_modules:
           "P6-B": 16
-  
+
   ex_b2: # cursus S1 -> S6, valide tout sauf S6
     prenom: Rose
     civilite: F
+    code_nip: 1004
     formsemestres:
       # on ne note que le portfolio, qui affecte toutes les UEs
       S1:
@@ -205,6 +215,7 @@ Etudiants:
         parcours: B
         notes_modules:
           "P2": 12
+          "R2.04-B": 19
       S3:
         parcours: B
         notes_modules:
diff --git a/tests/unit/test_apogee_export.py b/tests/unit/test_apogee_export.py
new file mode 100644
index 000000000..4b84f0a86
--- /dev/null
+++ b/tests/unit/test_apogee_export.py
@@ -0,0 +1,54 @@
+##############################################################################
+# ScoDoc
+# Copyright (c) 1999 - 2023 Emmanuel Viennet.  All rights reserved.
+# See LICENSE
+##############################################################################
+
+""" Test export Apogéee
+
+Ces tests sont généralement lents (construction de la base),
+et donc marqués par `@pytest.mark.slow`.
+
+Certains sont aussi marqués par @pytest.mark.lemans ou @pytest.mark.lyon
+pour lancer certains tests spécifiques seulement.
+
+Exemple utilisation spécifique:
+# test sur "apo" seulement:
+pytest --pdb -m apo tests/unit/test_apogee_export.py
+
+Elements Apogée simulés:
+
+- UEs : TIU2x
+- Ressources: R2.xy : TIRxy  (VRETR201 -> TIR201)
+"""
+
+import pytest
+from tests.unit import yaml_setup, yaml_setup_but
+
+import app
+from app.but.jury_but_validation_auto import formsemestre_validation_auto_but
+from app.models import Formation, FormSemestre, UniteEns
+from config import TestConfig
+
+DEPT = TestConfig.DEPT_TEST
+
+
+@pytest.mark.skip  # Ce "test" est utilisé comme setup pour développer, pas comme test unitaire routinier
+@pytest.mark.slow
+@pytest.mark.apo
+def test_refcomp_niveaux_info(test_client):
+    """Test niveaux / parcours / UE pour un BUT INFO
+    avec parcours A et B, même compétences mais coefs différents
+    selon le parcours.
+    """
+    # WIP
+    # pour le moment juste le chargement de la formation, du ref. comp, et des UE du S4.
+    app.set_sco_dept(DEPT)
+    doc, formation, formsemestre_titres = yaml_setup.setup_from_yaml(
+        "tests/ressources/yaml/cursus_but_info.yaml"
+    )
+    for formsemestre_titre in formsemestre_titres:
+        formsemestre = yaml_setup.create_formsemestre_with_etuds(
+            doc, formation, formsemestre_titre
+        )
+    #
diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py
index bf4dc70e4..277728e47 100644
--- a/tests/unit/yaml_setup.py
+++ b/tests/unit/yaml_setup.py
@@ -99,6 +99,9 @@ def create_formsemestre(
     titre: str,
     date_debut: str,
     date_fin: str,
+    elt_sem_apo: str = None,
+    elt_annee_apo: str = None,
+    etape_apo: str = None,
 ) -> FormSemestre:
     "Création d'un formsemestre, avec ses modimpls et évaluations"
     assert formation.is_apc() or not parcours  # parcours seulement si APC
@@ -110,11 +113,15 @@ def create_formsemestre(
         semestre_id=semestre_id,
         date_debut=date_debut,
         date_fin=date_fin,
+        elt_sem_apo=elt_sem_apo,
+        elt_annee_apo=elt_annee_apo,
     )
     # set responsable (list)
     a_user = User.query.first()
     formsemestre.responsables = [a_user]
     db.session.add(formsemestre)
+    db.session.flush()
+    formsemestre.add_etape(etape_apo)
     # Ajoute tous les modules du semestre sans parcours OU avec l'un des parcours indiqués
     sem_parcours_ids = {p.id for p in parcours}
     modules = [
@@ -228,6 +235,10 @@ def setup_formsemestre(
         assert parcour is not None
         parcours.append(parcour)
 
+    elt_sem_apo = infos.get("elt_sem_apo")
+    elt_annee_apo = infos.get("elt_annee_apo")
+    etape_apo = infos.get("etape_apo")
+
     formsemestre = create_formsemestre(
         formation,
         parcours,
@@ -235,6 +246,9 @@ def setup_formsemestre(
         formsemestre_titre,
         infos["date_debut"],
         infos["date_fin"],
+        elt_sem_apo=elt_sem_apo,
+        elt_annee_apo=elt_annee_apo,
+        etape_apo=etape_apo,
     )
 
     db.session.flush()
@@ -257,6 +271,7 @@ def inscrit_les_etudiants(doc: dict, formsemestre_titre: str = ""):
         # Création des étudiants (sauf si déjà existants)
         prenom = infos.get("prenom", "prénom")
         civilite = infos.get("civilite", "X")
+        code_nip = infos.get("code_nip", None)
         etud = Identite.query.filter_by(
             nom=nom, prenom=prenom, civilite=civilite
         ).first()
@@ -266,6 +281,7 @@ def inscrit_les_etudiants(doc: dict, formsemestre_titre: str = ""):
                 nom=nom,
                 prenom=prenom,
                 civilite=civilite,
+                code_nip=code_nip,
             )
             db.session.add(etud)
             db.session.commit()
-- 
GitLab