From 63ea4f31f282fd548bf3b4830592e01b4d36d4ee Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Thu, 26 Jan 2023 10:49:04 -0300
Subject: [PATCH] Jurys BUT: modif. autorisations passage. Cosmetic css.

---
 app/but/jury_but.py                  | 139 +++++++++++++++------------
 app/but/jury_but_view.py             |  82 +++++++++-------
 app/scodoc/sco_moduleimpl_status.py  |  49 +++++-----
 app/static/css/jury_but.css          |  41 +++++++-
 app/static/css/scodoc.css            |  31 ++++--
 app/views/notes.py                   |  27 +++++-
 scodoc.py                            |   3 +-
 tests/unit/cursus_but_geii_lyon.yaml |  81 ++++++++++++++++
 tests/unit/test_but_jury.py          |   3 +-
 9 files changed, 312 insertions(+), 144 deletions(-)

diff --git a/app/but/jury_but.py b/app/but/jury_but.py
index 244989edd..af1eed4ff 100644
--- a/app/but/jury_but.py
+++ b/app/but/jury_but.py
@@ -630,19 +630,38 @@ class DecisionsProposeesAnnee(DecisionsProposees):
             d[dec_rcue.rcue.ue_2.id] = dec_rcue
         return d
 
-    def next_annee_semestre_id(self, code: str) -> int:
-        """L'indice du semestre dans lequel l'étudiant est autorisé à
-        poursuivre l'année suivante. None si aucun."""
-        if self.formsemestre_pair is None:
-            return None  # seulement sur année
-        if code == RED:
-            return self.formsemestre_pair.semestre_id - 1
-        elif (
-            code in sco_codes.BUT_CODES_PASSAGE
+    def next_semestre_ids(self, code: str) -> set[int]:
+        """Les indices des semestres dans lequels l'étudiant est autorisé
+        à poursuivre après le semestre courant.
+        """
+        ids = set()
+        # La poursuite d'études dans un semestre pair d’une même année
+        # est de droit pour tout étudiant:
+        if (self.formsemestre.semestre_id % 2) and sco_codes.ParcoursBUT.NB_SEM:
+            ids.add(self.formsemestre.semestre_id + 1)
+
+        # La poursuite d’études dans un semestre impair est possible si
+        # et seulement si l’étudiant a obtenu :
+        #  - la moyenne à plus de la moitié des regroupements cohérents d’UE ;
+        #  - et une moyenne égale ou supérieure à 8 sur 20 à chaque RCUE.
+        #
+        # La condition a paru trop stricte à de nombreux collègues.
+        # ScoDoc ne contraint donc pas à la respecter strictement.
+        # Si le code est dans BUT_CODES_PASSAGE (ADM, ADJ, PASD, PAS1NCI, ATJ),
+        # autorise à passer dans le semestre suivant
+        if (
+            self.jury_annuel
+            and code in sco_codes.BUT_CODES_PASSAGE
             and self.formsemestre_pair.semestre_id < sco_codes.ParcoursBUT.NB_SEM
         ):
-            return self.formsemestre_pair.semestre_id + 1
-        return None
+            ids.add(self.formsemestre.semestre_id + 1)
+
+        if code == RED:
+            ids.add(
+                self.formsemestre.semestre_id - (self.formsemestre.semestre_id + 1) % 2
+            )
+
+        return ids
 
     def record_form(self, form: dict):
         """Enregistre les codes de jury en base
@@ -704,47 +723,43 @@ class DecisionsProposeesAnnee(DecisionsProposees):
             raise ScoValueError(
                 f"code annee <tt>{html.escape(code)}</tt> invalide pour formsemestre {html.escape(self.formsemestre)}"
             )
-        if code == self.code_valide or (self.code_valide is not None and no_overwrite):
-            self.recorded = True
-            return False  # no change
-        if self.validation:
-            db.session.delete(self.validation)
-            db.session.commit()
-        if code is None:
-            self.validation = None
-        else:
-            self.validation = ApcValidationAnnee(
-                etudid=self.etud.id,
-                formsemestre=self.formsemestre_impair,
-                ordre=self.annee_but,
-                annee_scolaire=self.annee_scolaire(),
-                code=code,
-            )
-            db.session.add(self.validation)
-            db.session.commit()
-            log(f"Recording {self}: {code}")
-            Scolog.logdb(
-                method="jury_but",
-                etudid=self.etud.id,
-                msg=f"Validation année BUT{self.annee_but}: {code}",
-            )
+
+        if code != self.code_valide and (self.code_valide is None or not no_overwrite):
+            # Enregistrement du code annuel BUT
+            if self.validation:
+                db.session.delete(self.validation)
+                db.session.commit()
+            if code is None:
+                self.validation = None
+            else:
+                self.validation = ApcValidationAnnee(
+                    etudid=self.etud.id,
+                    formsemestre=self.formsemestre_impair,
+                    ordre=self.annee_but,
+                    annee_scolaire=self.annee_scolaire(),
+                    code=code,
+                )
+                db.session.add(self.validation)
+                db.session.commit()
+                log(f"Recording {self}: {code}")
+                Scolog.logdb(
+                    method="jury_but",
+                    etudid=self.etud.id,
+                    msg=f"Validation année BUT{self.annee_but}: {code}",
+                )
 
         # --- Autorisation d'inscription dans semestre suivant ?
-        if self.formsemestre_pair is not None:
-            if code is None:
-                ScolarAutorisationInscription.delete_autorisation_etud(
-                    etudid=self.etud.id,
-                    origin_formsemestre_id=self.formsemestre_pair.id,
-                )
-            else:
-                next_semestre_id = self.next_annee_semestre_id(code)
-                if next_semestre_id is not None:
-                    ScolarAutorisationInscription.autorise_etud(
-                        self.etud.id,
-                        self.formsemestre_pair.formation.formation_code,
-                        self.formsemestre_pair.id,
-                        next_semestre_id,
-                    )
+        ScolarAutorisationInscription.delete_autorisation_etud(
+            etudid=self.etud.id,
+            origin_formsemestre_id=self.formsemestre.id,
+        )
+        for next_semestre_id in self.next_semestre_ids(code):
+            ScolarAutorisationInscription.autorise_etud(
+                self.etud.id,
+                self.formsemestre.formation.formation_code,
+                self.formsemestre.id,
+                next_semestre_id,
+            )
 
         db.session.commit()
         self.recorded = True
@@ -872,18 +887,18 @@ class DecisionsProposeesAnnee(DecisionsProposees):
         self.invalidate_formsemestre_cache()
 
     def get_autorisations_passage(self) -> list[int]:
-        """Les liste des indices de semestres auxquels on est autorisé à
-        s'inscrire depuis cette année"""
-        formsemestre = self.formsemestre_pair or self.formsemestre_impair
-        if not formsemestre:
-            return []
-        return [
-            a.semestre_id
-            for a in ScolarAutorisationInscription.query.filter_by(
-                etudid=self.etud.id,
-                origin_formsemestre_id=formsemestre.id,
-            )
-        ]
+        """Liste des indices de semestres auxquels on est autorisé à
+        s'inscrire depuis le semestre courant.
+        """
+        return sorted(
+            [
+                a.semestre_id
+                for a in ScolarAutorisationInscription.query.filter_by(
+                    etudid=self.etud.id,
+                    origin_formsemestre_id=self.formsemestre.id,
+                )
+            ]
+        )
 
     def descr_niveaux_validation(self, line_sep: str = "\n") -> str:
         """Description textuelle des niveaux validés (enregistrés)
diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py
index acb6b695b..ac454195e 100644
--- a/app/but/jury_but_view.py
+++ b/app/but/jury_but_view.py
@@ -43,21 +43,20 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
     """
     H = []
 
-    H.append("""<div class="but_section_annee">""")
-    H.append(
-        f"""
-        <div>
-            <b>Décision de jury pour l'année :</b> {
-                _gen_but_select("code_annee", deca.codes, deca.code_valide,
-                    disabled=True, klass="manual")
-            }
-            <span>({deca.code_valide or 'non'} enregistrée)</span>
+    if deca.jury_annuel:
+        H.append(
+            f"""
+        <div class="but_section_annee">
+            <div>
+                <b>Décision de jury pour l'année :</b> {
+                    _gen_but_select("code_annee", deca.codes, deca.code_valide,
+                        disabled=True, klass="manual")
+                }
+                <span>({deca.code_valide or 'non'} enregistrée)</span>
+            </div>
         </div>
         """
-    )
-    div_explanation = f"""<div class="but_explanation">{deca.explanation}</div>"""
-
-    H.append("""</div>""")
+        )
 
     formsemestre_1 = deca.formsemestre_impair
     formsemestre_2 = deca.formsemestre_pair
@@ -74,7 +73,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
     <div class="titre_niveaux">
         <b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b>
     </div>
-    {div_explanation}
+    <div class="but_explanation">{deca.explanation}</div>
     <div class="but_annee">
     <div class="titre"></div>
     <div class="titre">{"S" +str(formsemestre_1.semestre_id)
@@ -285,7 +284,7 @@ def jury_but_semestriel(
     read_only: bool,
     navigation_div: str = "",
 ) -> str:
-    """Formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)"""
+    """Page: formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel)."""
     res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
     parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res)
     inscription_etat = etud.inscription_etat(formsemestre.id)
@@ -374,20 +373,20 @@ def jury_but_semestriel(
         f"""
         <div class="jury_but">
         <div>
-        <div class="bull_head">
-        <div>
-            <div class="titre_parcours">Jury BUT S{formsemestre.id}
-            - Parcours {(parcour.libelle if parcour else False) or "non spécifié"}
+            <div class="bull_head">
+                <div>
+                    <div class="titre_parcours">Jury BUT S{formsemestre.id}
+                    - Parcours {(parcour.libelle if parcour else False) or "non spécifié"}
+                    </div>
+                    <div class="nom_etud">{etud.nomprenom}</div>
+                </div>
+                <div class="bull_photo"><a href="{
+                    url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
+                    }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
+                </div>
             </div>
-            <div class="nom_etud">{etud.nomprenom}</div>
-        </div>
-        <div class="bull_photo"><a href="{
-            url_for("scolar.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
-            }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
-        </div>
-        </div>
-        <h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
-        {warning}
+            <h3>Jury sur un semestre BUT isolé (ne concerne que les UEs)</h3>
+            {warning}
         </div>
 
         <form method="post" id="jury_but">
@@ -450,24 +449,35 @@ def jury_but_semestriel(
             )
         H.append("</div>")  # but_annee
 
+    div_autorisations_passage = (
+        f"""
+        <div class="but_autorisations_passage">
+            <span>Autorisé à passer en&nbsp;:</span>
+            { ", ".join( ["S" + str(a.semestre_id or '') for a in autorisations_passage ] )}
+        </div>
+    """
+        if autorisations_passage
+        else """<div class="but_autorisations_passage but_explanation">pas d'autorisations de passage enregistrées.</div>"""
+    )
+    H.append(div_autorisations_passage)
+
     if read_only:
         H.append(
             """<div class="but_explanation">
             Vous n'avez pas la permission de modifier ces décisions.
-            Les champs entourés en vert sont enregistrés.</div>"""
+            Les champs entourés en vert sont enregistrés.
+            </div>
+            """
         )
     else:
         if formsemestre.semestre_id < formsemestre.formation.get_parcours().NB_SEM:
             H.append(
                 f"""
                 <div class="but_settings">
-                <input type="checkbox" name="autorisation_passage" value="1" {
-                    "checked" if est_autorise_a_passer else ""}>
-                <em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
-                {("(autorisations enregistrées: " + ' '.join(
-                    'S' + str(a.semestre_id or '') for a in autorisations_passage) + ")"
-                ) if autorisations_passage else ""}
-                </input>
+                    <input type="checkbox" name="autorisation_passage" value="1" {
+                        "checked" if est_autorise_a_passer else ""}>
+                        <em>autoriser à passer dans le semestre S{formsemestre.semestre_id+1}</em>
+                    </input>
                 </div>
                 """
             )
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index d0fdd05b3..9b92a7dfe 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -544,7 +544,9 @@ def _ligne_evaluation(
     if not first_eval:
         H.append("""<tr><td colspan="8">&nbsp;</td></tr>""")
         tr_class_1 += " mievr_spaced"
-    H.append(f"""<tr class="{tr_class_1}"><td class="mievr_tit" colspan="8">""")
+    H.append(
+        f"""<tr class="{tr_class_1} mievr_tit"><td class="mievr_tit" colspan="8">"""
+    )
     coef = evaluation.coefficient
     if is_apc:
         if not evaluation.get_ue_poids_dict():
@@ -588,7 +590,9 @@ def _ligne_evaluation(
         )
     #
     H.append(
-        f"""<div class="evaluation_order">
+        f"""
+        </td>
+        <td class="evaluation_order">
         <span class="evalindex" title="Indice dans les vecteurs (formules)">{
             eval_index:2}</span>
         <span class="eval_arrows_chld">
@@ -612,20 +616,6 @@ def _ligne_evaluation(
     else:
         H.append(arrow_none)
 
-    H.append(
-        f"""</span></span></td>
-        </div>
-        </tr>
-        <tr class="{tr_class}">
-        <th class="moduleimpl_evaluations" colspan="2">&nbsp;</th>
-        <th class="moduleimpl_evaluations">Durée</th>
-        <th class="moduleimpl_evaluations">Coef.</th>
-        <th class="moduleimpl_evaluations">Notes</th>
-        <th class="moduleimpl_evaluations">Abs</th>
-        <th class="moduleimpl_evaluations">N</th>
-        <th class="moduleimpl_evaluations">Moyenne """
-    )
-
     if etat["evalcomplete"]:
         etat_txt = """(prise en compte)"""
         etat_descr = "notes utilisées dans les moyennes"
@@ -648,9 +638,19 @@ def _ligne_evaluation(
             }" title="{etat_descr}">{etat_txt}</a>"""
 
     H.append(
-        f"""{etat_txt}</th>
-    </tr>
-    <tr class="{tr_class}"><td class="mievr">"""
+        f"""</span></span></td>
+        </tr>
+        <tr class="{tr_class}">
+            <th class="moduleimpl_evaluations" colspan="2">&nbsp;</th>
+            <th class="moduleimpl_evaluations">Durée</th>
+            <th class="moduleimpl_evaluations">Coef.</th>
+            <th class="moduleimpl_evaluations">Notes</th>
+            <th class="moduleimpl_evaluations">Abs</th>
+            <th class="moduleimpl_evaluations">N</th>
+            <th class="moduleimpl_evaluations" colspan="2">Moyenne {etat_txt}</th>
+        </tr>
+        <tr class="{tr_class}">
+            <td class="mievr">"""
     )
     if can_edit_evals:
         H.append(
@@ -726,7 +726,7 @@ def _ligne_evaluation(
         <td class="rightcell mievr_nbnotes">{etat["nb_notes"]} / {etat["nb_inscrits"]}</td>
         <td class="rightcell mievr_coef">{etat["nb_abs"]}</td>
         <td class="rightcell mievr_coef">{etat["nb_neutre"]}</td>
-        <td class="rightcell">"""
+        <td class="rightcell" colspan="2">"""
         % etat
     )
     if etat["moy"]:
@@ -750,11 +750,11 @@ def _ligne_evaluation(
         H.append(f"""<tr class="{tr_class}"><td></td>""")
         if modimpl.module.is_apc():
             H.append(
-                f"""<td colspan="7" class="eval_poids">{
+                f"""<td colspan="8" class="eval_poids">{
                     evaluation.get_ue_poids_str()}</td>"""
             )
         else:
-            H.append('<td colspan="7"></td>')
+            H.append('<td colspan="8"></td>')
         H.append("""</tr>""")
     else:  # il y a deja des notes saisies
         gr_moyennes = etat["gr_moyennes"]
@@ -773,7 +773,10 @@ def _ligne_evaluation(
                 name = "Tous"  # tous
             else:
                 name = f"""Groupe {gr_moyenne["group_name"]}"""
-            H.append(f"""<td colspan="2" class="mievr_grtit">{name} &nbsp;</td><td>""")
+            H.append(
+                f"""<td colspan="2" class="mievr_grtit">{name} &nbsp;</td>
+                <td colspan="2">"""
+            )
             if gr_moyenne["gr_nb_notes"] > 0:
                 H.append(
                     f"""{gr_moyenne["gr_moy"]}&nbsp; (<a href="{
diff --git a/app/static/css/jury_but.css b/app/static/css/jury_but.css
index 4f2538d8d..a0fd87bdf 100644
--- a/app/static/css/jury_but.css
+++ b/app/static/css/jury_but.css
@@ -1,5 +1,10 @@
 /* Saisie décision de jury BUT */
 
+:root {
+    --color-recorded: rgb(3, 157, 3);
+    --color-explanation: blueviolet;
+}
+
 .jury_but {
     font-family: Verdana, Geneva, Tahoma, sans-serif;
 }
@@ -18,6 +23,19 @@
     margin-top: 0px;
 }
 
+form#jury_but {
+    margin: 0px 16px 16px 16px;
+    background-color: rgb(253, 253, 231);
+    border: 2px solid rgb(4, 4, 118);
+    border-radius: 4px;
+    padding-top: 8px;
+    padding-left: 16px;
+    padding-right: 16px;
+    padding-bottom: 16px;
+    min-width: var(--sco-content-min-width);
+    max-width: var(--sco-content-max-width);
+}
+
 .but_annee {
     margin-left: 32px;
     display: inline-grid;
@@ -112,11 +130,10 @@ div.but_settings {
 }
 
 .but_explanation {
-    color: blueviolet;
+    color: var(--color-explanation);
     font-style: italic;
     padding-top: 4px;
     padding-bottom: 12px;
-    ;
 }
 
 select:disabled {
@@ -130,7 +147,7 @@ select:invalid {
 
 select.but_code option.recorded,
 div.but_code recorded {
-    color: rgb(3, 157, 3);
+    color: var(--color-recorded);
     font-weight: bold;
 }
 
@@ -143,14 +160,14 @@ div.but_code {
 
 div.but_niveau_ue.recorded,
 div.but_niveau_rcue.recorded {
-    border-color: rgb(0, 169, 0);
+    border-color: var(--color-recorded);
     border-width: 3px;
 }
 
 div.but_niveau_ue.recorded_different,
 div.but_niveau_rcue.recorded_different {
     box-shadow: 0 0 0 3px red;
-    outline: dashed 3px rgb(0, 169, 0);
+    outline: dashed 3px var(--color-recorded);
 }
 
 div.but_niveau_ue.annee_prec {
@@ -174,6 +191,8 @@ div.but_buttons span {
 
 div.but_doc_codes {
     margin: 16px;
+    min-width: var(--sco-content-min-width);
+    max-width: var(--sco-content-max-width);
     background-color: rgb(227, 254, 254);
     font-size: 75%;
     border: 2px solid rgb(4, 4, 118);
@@ -227,4 +246,16 @@ div.but_doc table tr td.amue {
 
 .but_niveau_rcue .scoplement {
     font-weight: normal;
+}
+
+.but_autorisations_passage {
+    margin-top: 12px;
+    margin-left: 32px;
+    font-weight: bold;
+    color: var(--color-recorded);
+}
+
+.but_autorisations_passage.but_explanation {
+    font-weight: normal;
+    color: var(--color-explanation);
 }
\ No newline at end of file
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 254defd54..e66360983 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -1,7 +1,11 @@
-/* # -*- mode: css -*- 
-           ScoDoc, (c) Emmanuel Viennet 1998 - 2021
+/*   ScoDoc, (c) Emmanuel Viennet 1998 - 2023
  */
 
+:root {
+  --sco-content-min-width: 600px;
+  --sco-content-max-width: 1024px;
+}
+
 html,
 body {
   margin: 0;
@@ -1728,6 +1732,8 @@ ul.ue_inscr_list li.etud {
 div.moduleimpl_tableaubord {
   padding: 7px;
   border: 2px solid gray;
+  min-width: var(--sco-content-min-width);
+  max-width: var(--sco-content-max-width);
 }
 
 div.moduleimpl_type_sae {
@@ -1855,11 +1861,19 @@ span.mievr_rattr {
   padding: 1px 3px 1px 3px;
 }
 
-tr.mievr td.mievr_tit {
+tr.mievr_tit td.mievr_tit {
   font-weight: bold;
-  background-color: #cccccc;
   border-top-left-radius: 8px;
+}
+
+tr.mievr.mievr_tit td {
+  background-color: #e1e1e1;
+}
+
+tr.mievr_tit td:last-child {
   border-top-right-radius: 8px;
+  text-align: right;
+  padding-right: 8px;
 }
 
 tr.mievr td {
@@ -1922,11 +1936,6 @@ span.eval_warning_coef {
   background-color: rgb(255, 225, 0);
 }
 
-div.evaluation_order {
-  position: absolute;
-  right: 1em;
-}
-
 span.evalindex {
   font-weight: normal;
   font-size: 80%;
@@ -2580,11 +2589,13 @@ div.bull_head {
   display: grid;
   justify-content: space-between;
   grid-template-columns: auto auto;
+  min-width: 600px;
+  max-width: 1072px;
 }
 
 div.bull_photo {
   display: inline-block;
-  margin-right: 10px;
+  margin-right: 8px;
 }
 
 span.bulletin_menubar_but {
diff --git a/app/views/notes.py b/app/views/notes.py
index d32a7fee2..ce4b14725 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -2499,11 +2499,26 @@ def formsemestre_validation_but(
 
     H.append(jury_but_view.show_etud(deca, read_only=read_only))
 
+    autorisations_idx = deca.get_autorisations_passage()
+    div_autorisations_passage = (
+        f"""
+        <div class="but_autorisations_passage">
+            <span>Autorisé à passer en&nbsp;:</span>
+            { ", ".join( ["S" + str(i) for i in autorisations_idx ] )}
+        </div>
+    """
+        if autorisations_idx
+        else """<div class="but_autorisations_passage but_explanation">pas d'autorisations de passage enregistrées.</div>"""
+    )
+    H.append(div_autorisations_passage)
+
     if read_only:
         H.append(
-            """<div class="but_explanation">
+            """
+            <div class="but_explanation">
             Vous n'avez pas la permission de modifier ces décisions.
-            Les champs entourés en vert sont enregistrés.</div>"""
+            Les champs entourés en vert sont enregistrés.
+            </div>"""
         )
     else:
         erase_span = f"""<a href="{
@@ -2513,9 +2528,11 @@ def formsemestre_validation_but(
         H.append(
             f"""<div class="but_settings">
             <input type="checkbox" onchange="enable_manual_codes(this)">
-            <em>permettre la saisie manuelles des codes d'année et de niveaux.
-            Dans ce cas, il vous revient de vous assurer de la cohérence entre
-            vos codes d'UE/RCUE/Année !</em>
+                <em>permettre la saisie manuelles des codes
+                {"d'année et " if deca.jury_annuel else ""}
+                de niveaux.
+                Dans ce cas, assurez-vous de la cohérence entre les codes d'UE/RCUE/Année !
+                </em>
             </input>
             </div>
 
diff --git a/scodoc.py b/scodoc.py
index 25ca43156..12556c2c2 100755
--- a/scodoc.py
+++ b/scodoc.py
@@ -32,7 +32,7 @@ from app.models import GroupDescr
 from app.models import Identite
 from app.models import ModuleImpl, ModuleImplInscription
 from app.models import Partition
-from app.models import ScolarFormSemestreValidation
+from app.models import ScolarAutorisationInscription, ScolarFormSemestreValidation
 from app.models.but_refcomp import (
     ApcCompetence,
     ApcNiveau,
@@ -101,6 +101,7 @@ def make_shell_context():
         "ResultatsSemestreBUT": ResultatsSemestreBUT,
         "Role": Role,
         "scolar": scolar,
+        "ScolarAutorisationInscription": ScolarAutorisationInscription,
         "ScolarFormSemestreValidation": ScolarFormSemestreValidation,
         "ScolarNews": models.ScolarNews,
         "scu": scu,
diff --git a/tests/unit/cursus_but_geii_lyon.yaml b/tests/unit/cursus_but_geii_lyon.yaml
index 7a38f3bb3..8d64c976a 100644
--- a/tests/unit/cursus_but_geii_lyon.yaml
+++ b/tests/unit/cursus_but_geii_lyon.yaml
@@ -1022,3 +1022,84 @@ Etudiants:
                 moy_ue: 9.5000
                 moy_ue_with_cap: 12.7600
           decision_annee: AJ
+  geii1000:
+    prenom: etugeii1000
+    civilite: M
+    formsemestres:
+      S1:
+        notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
+          "S1.1": 12.0000
+          "S1.2": 9.0000
+        attendu: # les codes jury que l'on doit vérifier
+          deca:
+            passage_de_droit: False
+            nb_competences: 2
+            nb_rcue_annee: 0
+            decisions_ues:
+              "UE11":
+                codes: [ "ADM", "..." ]
+                code_valide: ADM
+                moy_ue: 12.0000
+              "UE12":
+                codes: [ "AJ", "..." ]
+                code_valide: AJ
+                decision_jury: AJ
+                moy_ue: 9.0000
+      S2:
+        notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
+          "S2.1": 12.0000
+          "S2.2": 10.50 # capitalise mais ne compense pas
+        attendu: # les codes jury que l'on doit vérifier
+          deca:
+            passage_de_droit: False
+            nb_competences: 2
+            nb_rcue_annee: 2
+            valide_moitie_rcue: False
+            codes: [ "RED", "..." ]
+            decisions_ues:
+              "UE21":
+                codes: [ "ADM", "..." ]
+                code_valide: ADM
+                moy_ue: 12.0000
+              "UE22":
+                code_valide: ADM
+                moy_ue: 10.5
+            decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
+              "UE11":
+                code_valide: ADM
+                decision_jury: ADM
+                rcue:
+                  moy_rcue: 12.0000
+                  est_compensable: False
+              "UE12":
+                code_valide: AJ
+                decision_jury: AJ
+                rcue:
+                  moy_rcue: 9.75
+                  est_compensable: False
+          decision_annee: RED
+      S1-red:
+        notes_modules: # on joue avec les SAE seulement car elles sont "diagonales"
+          "S1.1": 5.0000
+          "S1.2": 12.0000
+        attendu: # les codes jury que l'on doit vérifier
+          deca:
+            passage_de_droit: False
+            nb_competences: 2
+            nb_rcue_annee: 0
+            decisions_ues:
+              "UE11":
+                code_valide: AJ
+                moy_ue: 5.  # LA MOYENNE COURANTE
+                moy_ue_with_cap: 12.0000
+              "UE12":
+                code_valide: ADM
+                decision_jury: ADM
+                moy_ue: 12.0000
+            # RCUE inter-annuel
+            decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1)
+              "UE12":
+                code_valide: ADM
+                rcue:
+                  moy_rcue: 11.25
+                  est_compensable: False
diff --git a/tests/unit/test_but_jury.py b/tests/unit/test_but_jury.py
index 5ee80a684..fec4976c6 100644
--- a/tests/unit/test_but_jury.py
+++ b/tests/unit/test_but_jury.py
@@ -33,7 +33,7 @@ DEPT = TestConfig.DEPT_TEST
 @pytest.mark.but_gb
 def test_but_jury_GB(test_client):
     """Tests sur un cursus GB
-    - construction des semestres et de leurs étudianst à partir du yaml
+    - construction des semestres et de leurs étudiants à partir du yaml
     - vérification jury de S1
     - vérification jury de S2
     - vérification jury de S3
@@ -96,7 +96,6 @@ def test_but_jury_GEII_lyon(test_client):
     # Construit la base de test GB une seule fois
     # puis lance les tests de jury
     doc = yaml_setup.setup_from_yaml("tests/unit/cursus_but_geii_lyon.yaml")
-
     formsemestres = FormSemestre.query.order_by(
         FormSemestre.date_debut, FormSemestre.semestre_id
     ).all()