From fa0417f0b1cb136b12de281052b80bc11fe0bb66 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Sat, 23 Mar 2024 13:23:26 +0100
Subject: [PATCH] Jury BUT: affiche la liste des modules avec note en ATTente

---
 app/but/jury_but.py           | 25 +++++++++++++++++++++----
 app/static/css/jury_but.css   |  7 +++++++
 app/templates/scolar/index.j2 |  8 +++++++-
 app/views/notes.py            | 34 +++++++++++++++++++++-------------
 4 files changed, 56 insertions(+), 18 deletions(-)

diff --git a/app/but/jury_but.py b/app/but/jury_but.py
index fe670d70e..87bbe4721 100644
--- a/app/but/jury_but.py
+++ b/app/but/jury_but.py
@@ -77,7 +77,7 @@ from app.models.but_refcomp import (
     ApcNiveau,
     ApcParcours,
 )
-from app.models import Evaluation, Scolog, ScolarAutorisationInscription
+from app.models import Evaluation, ModuleImpl, Scolog, ScolarAutorisationInscription
 from app.models.but_validations import (
     ApcValidationAnnee,
     ApcValidationRCUE,
@@ -796,16 +796,33 @@ class DecisionsProposeesAnnee(DecisionsProposees):
         if self.formsemestre_pair is not None:
             sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre_pair.id)
 
-    def has_notes_en_attente(self) -> bool:
-        "Vrai si l'étudiant a au moins une note en attente dans le semestre origine de ce deca"
-        res = (
+    def _get_current_res(self) -> ResultatsSemestreBUT:
+        "Les res. du semestre d'origine du deca"
+        return (
             self.res_pair
             if self.formsemestre_pair
             and (self.formsemestre.id == self.formsemestre_pair.id)
             else self.res_impair
         )
+
+    def has_notes_en_attente(self) -> bool:
+        "Vrai si l'étudiant a au moins une note en attente dans le semestre origine de ce deca"
+        res = self._get_current_res()
         return res and self.etud.id in res.get_etudids_attente()
 
+    def get_modimpls_attente(self) -> list[ModuleImpl]:
+        "Liste des ModuleImpl dans lesquels l'étudiant à au moins une note en ATTente"
+        res = self._get_current_res()
+        modimpls_results = [
+            modimpl_result
+            for modimpl_result in res.modimpls_results.values()
+            if self.etud.id in modimpl_result.etudids_attente
+        ]
+        modimpls = [
+            db.session.get(ModuleImpl, mr.moduleimpl_id) for mr in modimpls_results
+        ]
+        return sorted(modimpls, key=lambda mi: (mi.module.numero, mi.module.code))
+
     def record_all(self, only_validantes: bool = False) -> bool:
         """Enregistre les codes qui n'ont pas été spécifiés par le formulaire,
         et sont donc en mode "automatique".
diff --git a/app/static/css/jury_but.css b/app/static/css/jury_but.css
index c75c1a50c..525c532f0 100644
--- a/app/static/css/jury_but.css
+++ b/app/static/css/jury_but.css
@@ -19,6 +19,13 @@
     font-weight: bold;
 }
 
+ul.modimpls_att {
+    margin-top: 8px;
+    margin-left: 32px;
+    padding-top: 0;
+    color: black;
+}
+
 .jury_but h3 {
     margin-top: 0px;
 }
diff --git a/app/templates/scolar/index.j2 b/app/templates/scolar/index.j2
index c5c243f2b..8b93dee0a 100644
--- a/app/templates/scolar/index.j2
+++ b/app/templates/scolar/index.j2
@@ -16,6 +16,10 @@ table.listesems tr td.titresem {
     font-weight: bold;
     font-size: 110%;
 }
+div.semlist {
+    padding-right: 8px;
+}
+
 table.semlist tr td.datesem {
   text-align: center;
   white-space: nowrap;
@@ -113,7 +117,9 @@ table.semlist tbody tr td.modalite {
             url_for('scolar.export_table_dept_formsemestres', scodoc_dept=g.scodoc_dept)
         }}">{{scu.ICON_XLS|safe}}</a>
     </summary>
-    {{ html_table_formsemestres|safe }}
+    <div class="semlist">
+        {{ html_table_formsemestres|safe }}
+    </div>
 </details>
 {% else %}
     <p><a class="stdlink" href="{{
diff --git a/app/views/notes.py b/app/views/notes.py
index 317e71a9e..ea190b4f1 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -2393,17 +2393,16 @@ def formsemestre_validation_but(
                 <div class="bull_head">
                 <div>
                     <div class="titre_parcours">Jury BUT</div>
-                    <div class="nom_etud">{etud.nomprenom}</div>
+                    <div class="nom_etud">{etud.html_link_fiche()}</div>
                 </div>
                 <div class="bull_photo"><a href="{
-                    url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
+                    etud.url_fiche()
                     }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
                 </div>
                 </div>
                 <div class="warning">Impossible de statuer sur cet étudiant:
                         il est démissionnaire ou défaillant (voir <a class="stdlink" href="{
-                    url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etudid)
-                }">sa fiche</a>)
+                    etud.url_fiche()}">sa fiche</a>)
                 </div>
             </div>
             {navigation_div}
@@ -2461,17 +2460,27 @@ def formsemestre_validation_but(
         inscription = deca.formsemestre_impair.etuds_inscriptions.get(etud.id)
         if (not inscription) or inscription.etat != scu.INSCRIT:
             etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
-            warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_impair.semestre_id}</div>"""
+            warning += f"""<div class="warning">{etat_ins}
+                en S{deca.formsemestre_impair.semestre_id}</div>"""
 
     if deca.formsemestre_pair:
         inscription = deca.formsemestre_pair.etuds_inscriptions.get(etud.id)
         if (not inscription) or inscription.etat != scu.INSCRIT:
             etat_ins = scu.ETATS_INSCRIPTION.get(inscription.etat, "inconnu?")
-            warning += f"""<div class="warning">{etat_ins} en S{deca.formsemestre_pair.semestre_id}</div>"""
+            warning += f"""<div class="warning">{etat_ins}
+                en S{deca.formsemestre_pair.semestre_id}</div>"""
 
     if has_notes_en_attente:
-        warning += f"""<div class="warning-bloquant">{etud.nomprenom} a des notes en ATTente.
-            Vous devez régler cela avant de statuer en jury !</div>"""
+        warning += f"""<div class="warning-bloquant">{etud.html_link_fiche()
+            } a des notes en ATTente dans les modules suivants.
+            Vous devez régler cela avant de statuer en jury !
+            <ul class="modimpls_att">
+            """
+        for modimpl in deca.get_modimpls_attente():
+            warning += f"""<li><a href="{
+                    url_for("notes.moduleimpl_status", scodoc_dept=g.scodoc_dept, moduleimpl_id=modimpl.id)
+                }" class="stdlink">{modimpl.module.code} {modimpl.module.titre_str()}</a></li>"""
+        warning += "</ul></div>"
     if evaluations_a_debloquer:
         links_evals = [
             f"""<a class="stdlink" href="{url_for(
@@ -2484,6 +2493,8 @@ def formsemestre_validation_but(
                 voir {", ".join(links_evals)}
                 """
 
+    if warning:
+        warning = f"""<div class="jury_but_warning jury_but_box">{warning}</div>"""
     H.append(
         f"""
     <div>
@@ -2492,16 +2503,13 @@ def formsemestre_validation_but(
             <div class="titre_parcours">Jury BUT{deca.annee_but}
             - Parcours {(deca.parcour.libelle if deca.parcour else False) or "non spécifié"}
             - {deca.annee_scolaire_str()}</div>
-            <div class="nom_etud">{etud.nomprenom}</div>
+            <div class="nom_etud">{etud.html_link_fiche()}</div>
         </div>
         <div class="bull_photo"><a href="{
-            url_for("scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id)
-            }">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
+            etud.url_fiche()}">{etud.photo_html(title="fiche de " + etud.nomprenom)}</a>
         </div>
         </div>
-        <div class="jury_but_warning jury_but_box">
         {warning}
-        </div>
     </div>
 
     <form method="post" class="jury_but_box" id="jury_but">