From 066a24b302f626b1817af176e25c462917f7e3b3 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Thu, 29 Jun 2023 23:24:36 +0200
Subject: [PATCH] WIP: jury BUT: adaptations des tests unitaires, traite
 semestre par semestre

---
 app/but/jury_but.py                           |  13 +-
 .../ressources/yaml/cursus_but_geii_lyon.yaml |   2 +-
 tests/unit/test_but_jury.py                   |  90 ++++--------
 tests/unit/yaml_setup.py                      | 137 ++++++++++--------
 tests/unit/yaml_setup_but.py                  |   4 +-
 5 files changed, 123 insertions(+), 123 deletions(-)

diff --git a/app/but/jury_but.py b/app/but/jury_but.py
index bf39b7128..31367a98e 100644
--- a/app/but/jury_but.py
+++ b/app/but/jury_but.py
@@ -776,8 +776,10 @@ class DecisionsProposeesAnnee(DecisionsProposees):
         # UEs
         for dec_ue in self.decisions_ues.values():
             if (
-                not dec_ue.recorded
-            ) and dec_ue.formsemestre.annee_scolaire() == annee_scolaire:
+                dec_ue.formsemestre
+                and (not dec_ue.recorded)
+                and dec_ue.formsemestre.annee_scolaire() == annee_scolaire
+            ):
                 # rappel: le code par défaut est en tête
                 code = dec_ue.codes[0] if dec_ue.codes else None
                 if (not only_validantes) or code in sco_codes.CODES_UE_VALIDES_DE_DROIT:
@@ -1457,7 +1459,12 @@ class DecisionsProposeesUE(DecisionsProposees):
         ) or self.formsemestre.modalite == "EXT":
             self.codes.insert(0, sco_codes.ADM)
             self.explanation = f"Moyenne >= {sco_codes.CursusBUT.BARRE_MOY}/20"
-        elif self.rcue and self.rcue.est_compensable():
+        elif (
+            self.rcue
+            and self.rcue.est_compensable()
+            and self.ue_status
+            and not self.ue_status["is_capitalized"]
+        ):
             self.codes.insert(0, sco_codes.CMP)
             self.explanation = "compensable dans le RCUE"
         else:
diff --git a/tests/ressources/yaml/cursus_but_geii_lyon.yaml b/tests/ressources/yaml/cursus_but_geii_lyon.yaml
index c1457d1a5..7ae5ecaae 100644
--- a/tests/ressources/yaml/cursus_but_geii_lyon.yaml
+++ b/tests/ressources/yaml/cursus_but_geii_lyon.yaml
@@ -1090,7 +1090,7 @@ Etudiants:
           "S1.2": 12.0000
         attendu: # les codes jury que l'on doit vérifier
           deca:
-            passage_de_droit: False
+            passage_de_droit: True
             nb_competences: 2
             nb_rcue_annee: 0
             decisions_ues:
diff --git a/tests/unit/test_but_jury.py b/tests/unit/test_but_jury.py
index acb5c67da..0eb2458af 100644
--- a/tests/unit/test_but_jury.py
+++ b/tests/unit/test_but_jury.py
@@ -30,6 +30,26 @@ from config import TestConfig
 DEPT = TestConfig.DEPT_TEST
 
 
+def setup_and_test_jurys(yaml_filename: str):
+    "Charge YAML et lance test jury BUT"
+    app.set_sco_dept(DEPT)
+    # Construit la base de test GB une seule fois
+    # puis lance les tests de jury
+    doc, formation, formsemestre_titres = yaml_setup.setup_from_yaml(yaml_filename)
+
+    for formsemestre_titre in formsemestre_titres:
+        formsemestre = yaml_setup.create_formsemestre_with_etuds(
+            doc, formation, formsemestre_titre
+        )
+        # Vérifie les champs de DecisionsProposeesAnnee de ce semestre
+        yaml_setup_but.check_deca_fields(formsemestre)
+
+        # Saisie de toutes les décisions de jury "automatiques"
+        # et vérification des résultats attendus:
+        formsemestre_validation_auto_but(formsemestre, only_adm=False)
+        yaml_setup_but.but_test_jury(formsemestre, doc)
+
+
 @pytest.mark.slow
 @pytest.mark.but_gb
 def test_but_jury_GB(test_client):
@@ -40,76 +60,21 @@ def test_but_jury_GB(test_client):
     - vérification jury de S3
     - vérification jury de S1 avec redoublants et capitalisations
     """
-    app.set_sco_dept(DEPT)
-    # Construit la base de test GB une seule fois
-    # puis lance les tests de jury
-    doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_gb.yaml")
-
-    # Vérifie les deca de tous les semestres:
-    for formsemestre in FormSemestre.query:
-        yaml_setup_but.check_deca_fields(formsemestre)
-
-    # Saisie de toutes les décisions de jury
-    for formsemestre in FormSemestre.query.order_by(FormSemestre.semestre_id):
-        formsemestre_validation_auto_but(formsemestre, only_adm=False)
-
-    # Vérifie résultats attendus:
-    S1: FormSemestre = FormSemestre.query.filter_by(titre="S1_SEE").first()
-    yaml_setup_but.but_test_jury(S1, doc)
-    S2: FormSemestre = FormSemestre.query.filter_by(titre="S2_SEE").first()
-    yaml_setup_but.but_test_jury(S2, doc)
-    S3: FormSemestre = FormSemestre.query.filter_by(titre="S3").first()
-    yaml_setup_but.but_test_jury(S3, doc)
-    # _test_but_jury(S1_redoublant, doc)
+    setup_and_test_jurys("tests/ressources/yaml/cursus_but_gb.yaml")
 
 
 @pytest.mark.slow
 @pytest.mark.lemans
 def test_but_jury_GMP_lm(test_client):
     """Tests sur un cursus GMP fourni par Le Mans"""
-    app.set_sco_dept(DEPT)
-    # Construit la base de test GB une seule fois
-    # puis lance les tests de jury
-    doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_gmp_iutlm.yaml")
-
-    formsemestres = FormSemestre.query.order_by(
-        FormSemestre.date_debut, FormSemestre.semestre_id
-    ).all()
-
-    # Vérifie les deca de tous les semestres:
-    for formsemestre in formsemestres:
-        yaml_setup_but.check_deca_fields(formsemestre)
-
-    # Saisie de toutes les décisions de jury qui ne le seraient pas déjà
-    for formsemestre in formsemestres:
-        formsemestre_validation_auto_but(formsemestre, only_adm=False)
-
-    # Vérifie résultats attendus:
-    for formsemestre in formsemestres:
-        yaml_setup_but.but_test_jury(formsemestre, doc)
+    setup_and_test_jurys("tests/ressources/yaml/cursus_but_gmp_iutlm.yaml")
 
 
 @pytest.mark.slow
 @pytest.mark.lyon
 def test_but_jury_GEII_lyon(test_client):
     """Tests sur un cursus GEII fourni par Lyon"""
-    app.set_sco_dept(DEPT)
-    # Construit la base de test GB une seule fois
-    # puis lance les tests de jury
-    doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_geii_lyon.yaml")
-    formsemestres = FormSemestre.query.order_by(
-        FormSemestre.date_debut, FormSemestre.semestre_id
-    ).all()
-
-    # Vérifie les champs de DecisionsProposeesAnnee de tous les semestres:
-    for formsemestre in formsemestres:
-        yaml_setup_but.check_deca_fields(formsemestre)
-
-    # Saisie de toutes les décisions de jury "automatiques"
-    # et vérification des résultats attendus:
-    for formsemestre in formsemestres:
-        formsemestre_validation_auto_but(formsemestre, only_adm=False)
-        yaml_setup_but.but_test_jury(formsemestre, doc)
+    setup_and_test_jurys("tests/ressources/yaml/cursus_but_geii_lyon.yaml")
 
 
 @pytest.mark.slow
@@ -118,7 +83,14 @@ def test_but_jury_GCCD_CY(test_client):
     """Tests sur un cursus BUT GCCD de S1 à S6"""
     # WIP
     app.set_sco_dept(DEPT)
-    doc = yaml_setup.setup_from_yaml("tests/ressources/yaml/cursus_but_gccd_cy.yaml")
+    doc, formation, formsemestre_titres = yaml_setup.setup_from_yaml(
+        "tests/ressources/yaml/cursus_but_gccd_cy.yaml"
+    )
+    for formsemestre_titre in formsemestre_titres:
+        _ = yaml_setup.create_formsemestre_with_etuds(
+            doc, formation, formsemestre_titre
+        )
+
     formsemestres = FormSemestre.query.order_by(
         FormSemestre.date_debut, FormSemestre.semestre_id
     ).all()
diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py
index 35b016a2f..bf4dc70e4 100644
--- a/tests/unit/yaml_setup.py
+++ b/tests/unit/yaml_setup.py
@@ -17,7 +17,7 @@ setup_from_yaml()
         - import de la formation (le test utilise une seule formation)
     - associe_ues_et_parcours():
         - crée les associations formation <-> référentiel de compétence
-    - setup_formsemestres()
+    - setup_formsemestre()
         - crée les formsemestres décrits dans le YAML
         avec tous les modules du semestre ou des parcours si indiqués
         et une évaluation dans chaque moduleimpl.
@@ -166,19 +166,20 @@ def create_evaluations(formsemestre: FormSemestre, publish_incomplete=True):
             evaluation.set_ue_poids_dict(ue_coef_dict)
 
 
-def note_les_modules(doc: dict):
-    """Saisie les notes des étudiants
+def note_les_modules(doc: dict, formsemestre_titre: str = ""):
+    """Saisie les notes des étudiants pour ce formsemestre
     doc : données YAML
     """
     a_user = User.query.first()
+    formsemestre: FormSemestre = FormSemestre.query.filter_by(
+        titre=formsemestre_titre
+    ).first()
+    assert formsemestre is not None
     for nom, infos in doc["Etudiants"].items():
         etud: Identite = Identite.query.filter_by(nom=nom).first()
         assert etud is not None
-        for titre, sem_infos in infos["formsemestres"].items():
-            formsemestre: FormSemestre = FormSemestre.query.filter_by(
-                titre=titre
-            ).first()
-            assert formsemestre is not None
+        sem_infos = infos["formsemestres"].get(formsemestre_titre)
+        if sem_infos:
             for code_module, note in sem_infos.get("notes_modules", {}).items():
                 modimpl = (
                     formsemestre.modimpls.join(Module)
@@ -209,57 +210,69 @@ def note_les_modules(doc: dict):
                     )
 
 
-def setup_formsemestres(formation: Formation, doc: str):
-    """Création des formsemestres
-    Le cas échéant associés à leur(s) parcours.
+def setup_formsemestre(
+    formation: Formation, doc: str, formsemestre_titre: str = ""
+) -> FormSemestre:
+    """Création du formsemestre de titre indiqué.
+    Le cas échéant associé à son parcours.
     """
-    for titre, infos in doc["FormSemestres"].items():
-        codes_parcours = infos.get("codes_parcours", [])
-        assert formation.is_apc() or not codes_parcours  # parcours seulement en APC
-        parcours = []
-        for code_parcour in codes_parcours:
-            parcour = formation.referentiel_competence.parcours.filter_by(
-                code=code_parcour
-            ).first()
-            assert parcour is not None
-            parcours.append(parcour)
-
-        _ = create_formsemestre(
-            formation,
-            parcours,
-            infos["idx"],
-            titre,
-            infos["date_debut"],
-            infos["date_fin"],
-        )
+    infos = doc["FormSemestres"][formsemestre_titre]
+
+    codes_parcours = infos.get("codes_parcours", [])
+    assert formation.is_apc() or not codes_parcours  # parcours seulement en APC
+    parcours = []
+    for code_parcour in codes_parcours:
+        parcour = formation.referentiel_competence.parcours.filter_by(
+            code=code_parcour
+        ).first()
+        assert parcour is not None
+        parcours.append(parcour)
+
+    formsemestre = create_formsemestre(
+        formation,
+        parcours,
+        infos["idx"],
+        formsemestre_titre,
+        infos["date_debut"],
+        infos["date_fin"],
+    )
 
     db.session.flush()
-    assert FormSemestre.query.count() == len(doc["FormSemestres"])
+    return formsemestre
 
 
-def inscrit_les_etudiants(formation: Formation, doc: dict):
+def inscrit_les_etudiants(doc: dict, formsemestre_titre: str = ""):
     """Inscrit les étudiants dans chacun de leurs formsemestres"""
     etudiants = doc.get("Etudiants")
     if not etudiants:
         return
+    formsemestre: FormSemestre = FormSemestre.query.filter_by(
+        titre=formsemestre_titre
+    ).first()
+    assert formsemestre is not None
+    partition_parcours = formsemestre.partitions.filter_by(
+        partition_name=scu.PARTITION_PARCOURS
+    ).first()
     for nom, infos in etudiants.items():
-        etud = Identite.create_etud(
-            dept_id=g.scodoc_dept_id,
-            nom=nom,
-            prenom=infos.get("prenom", "prénom"),
-            civilite=infos.get("civilite", "X"),
-        )
-        db.session.add(etud)
-        db.session.commit()
-        # L'inscrire à ses formsemestres
-        for titre, sem_infos in infos["formsemestres"].items():
-            formsemestre: FormSemestre = FormSemestre.query.filter_by(
-                titre=titre
-            ).first()
-            assert formsemestre is not None
-            partition_parcours = formsemestre.partitions.filter_by(
-                partition_name=scu.PARTITION_PARCOURS
-            ).first()
+        # Création des étudiants (sauf si déjà existants)
+        prenom = infos.get("prenom", "prénom")
+        civilite = infos.get("civilite", "X")
+        etud = Identite.query.filter_by(
+            nom=nom, prenom=prenom, civilite=civilite
+        ).first()
+        if etud is None:
+            etud = Identite.create_etud(
+                dept_id=g.scodoc_dept_id,
+                nom=nom,
+                prenom=prenom,
+                civilite=civilite,
+            )
+            db.session.add(etud)
+            db.session.commit()
+
+        # L'inscrire au formsemestre
+        sem_infos = infos["formsemestres"].get(formsemestre_titre)
+        if sem_infos:
             if partition_parcours is not None and "parcours" in sem_infos:
                 group = partition_parcours.groups.filter_by(
                     group_name=sem_infos["parcours"]
@@ -278,9 +291,8 @@ def inscrit_les_etudiants(formation: Formation, doc: dict):
                 etat=scu.INSCRIT,
                 method="tests/unit/inscrit_les_etudiants",
             )
-    # Met à jour les inscriptions:
-    for formsemestre in formation.formsemestres:
-        formsemestre.update_inscriptions_parcours_from_groups()
+            # Met à jour les inscriptions:
+            formsemestre.update_inscriptions_parcours_from_groups()
 
 
 def etud_dispense_ues(
@@ -296,7 +308,7 @@ def etud_dispense_ues(
         db.session.add(disp)
 
 
-def setup_from_yaml(filename: str) -> dict:
+def setup_from_yaml(filename: str) -> tuple[dict, Formation, list[str]]:
     """Lit le fichier yaml et construit l'ensemble des objets"""
     with open(filename, encoding="utf-8") as f:
         doc = yaml.safe_load(f.read())
@@ -307,9 +319,20 @@ def setup_from_yaml(filename: str) -> dict:
     formation = setup_formation(doc["Formation"])
 
     yaml_setup_but.associe_ues_et_parcours(formation, doc["Formation"])
-    setup_formsemestres(formation, doc)
+
+    formsemestre_titres = list(doc["FormSemestres"].keys())
+    return doc, formation, formsemestre_titres
+
+
+def create_formsemestre_with_etuds(
+    doc: dict, formation: Formation, formsemestre_titre: str
+) -> FormSemestre:
+    """Création formsemestre de titre indiqué, puis inscrit ses étudianst et les note"""
+    formsemestre = setup_formsemestre(
+        formation, doc, formsemestre_titre=formsemestre_titre
+    )
     etudiants = doc.get("Etudiants")
     if etudiants:
-        inscrit_les_etudiants(formation, doc)
-        note_les_modules(doc)
-    return doc
+        inscrit_les_etudiants(doc, formsemestre_titre=formsemestre_titre)
+        note_les_modules(doc, formsemestre_titre=formsemestre_titre)
+    return formsemestre
diff --git a/tests/unit/yaml_setup_but.py b/tests/unit/yaml_setup_but.py
index 606fa14d1..8412ed2be 100644
--- a/tests/unit/yaml_setup_but.py
+++ b/tests/unit/yaml_setup_but.py
@@ -272,9 +272,7 @@ def check_deca_fields(formsemestre: FormSemestre, etud: Identite = None):
     etud = etud or formsemestre.etuds.first()
     assert etud  # il faut au moins un étudiant dans le semestre
     deca = DecisionsProposeesAnnee(etud, formsemestre)
-    assert deca.validation is None  # pas encore de validation enregistrée
     assert False is deca.recorded
-    assert deca.code_valide is None
     parcour = deca.parcour
     formation: Formation = formsemestre.formation
     ues = (
@@ -310,7 +308,7 @@ def check_deca_fields(formsemestre: FormSemestre, etud: Identite = None):
         if deca.formsemestre_impair
         else 0
     )
-    assert len(deca.decisions_ues) == nb_ues
+    assert nb_ues > 0
 
     assert len(deca.niveaux_competences) == len(ues)
     assert deca.nb_competences == len(ues)
-- 
GitLab