From f23630d7fdb10fd64bb53f99dcbf0cc7b7903d14 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Sun, 24 Mar 2024 07:39:47 +0100
Subject: [PATCH] =?UTF-8?q?Jury=20BUT:=20am=C3=A9liore=20pr=C3=A9sentation?=
 =?UTF-8?q?=20et=20information=20sur=20les=20UEs=20capitalis=C3=A9es.=20Cl?=
 =?UTF-8?q?oses=20#670?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/but/jury_but.py         | 16 +++++++++-----
 app/but/jury_but_view.py    | 44 +++++++++++++++++++++++++++----------
 app/but/rcue.py             | 12 +++++++++-
 app/comp/res_common.py      |  4 +++-
 app/static/css/jury_but.css |  6 +++++
 app/static/css/scodoc.css   |  8 +++++++
 6 files changed, 71 insertions(+), 19 deletions(-)

diff --git a/app/but/jury_but.py b/app/but/jury_but.py
index 87bbe4721..5bd71c7ec 100644
--- a/app/but/jury_but.py
+++ b/app/but/jury_but.py
@@ -413,12 +413,12 @@ class DecisionsProposeesAnnee(DecisionsProposees):
         # Si validée par niveau supérieur:
         if self.code_valide == sco_codes.ADSUP:
             self.codes.insert(0, sco_codes.ADSUP)
-        self.explanation = f"<div>{explanation}</div>"
+        self.explanation = f'<div class="deca-expl">{explanation}</div>'
         messages = self.descr_pb_coherence()
         if messages:
             self.explanation += (
-                '<div class="warning">'
-                + '</div><div class="warning">'.join(messages)
+                '<div class="warning warning-info">'
+                + '</div><div class="warning warning-info">'.join(messages)
                 + "</div>"
             )
         self.codes = [self.codes[0]] + sorted((c or "") for c in self.codes[1:])
@@ -1014,19 +1014,23 @@ class DecisionsProposeesAnnee(DecisionsProposees):
                             if dec_ue.code_valide not in CODES_UE_VALIDES:
                                 if (
                                     dec_ue.ue_status
-                                    and dec_ue.ue_status["was_capitalized"]
+                                    and dec_ue.ue_status["is_capitalized"]
                                 ):
                                     messages.append(
                                         f"Information: l'UE {ue.acronyme} capitalisée est utilisée pour un RCUE cette année"
                                     )
                                 else:
                                     messages.append(
-                                        f"L'UE {ue.acronyme} n'est pas validée mais son RCUE l'est !"
+                                        f"L'UE {ue.acronyme} n'est pas validée mais son RCUE l'est (probablement une validation antérieure)"
                                     )
                         else:
                             messages.append(
                                 f"L'UE {ue.acronyme} n'a pas décision (???)"
                             )
+                        # Voyons si on est dispensé de cette ue ?
+                        res = self.res_impair if ue.semestre_idx % 2 else self.res_pair
+                        if res and (self.etud.id, ue.id) in res.dispense_ues:
+                            messages.append(f"Pas (ré)inscrit à l'UE {ue.acronyme}")
         return messages
 
     def valide_diplome(self) -> bool:
@@ -1531,7 +1535,7 @@ class DecisionsProposeesUE(DecisionsProposees):
 
     def __repr__(self) -> str:
         return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide
-        } codes={self.codes} explanation={self.explanation}>"""
+        } codes={self.codes} explanation="{self.explanation}">"""
 
     def compute_codes(self):
         """Calcul des .codes attribuables et de l'explanation associée"""
diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py
index 915d75393..169abdf27 100644
--- a/app/but/jury_but_view.py
+++ b/app/but/jury_but_view.py
@@ -109,23 +109,29 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
             </div>"""
         )
         ue_impair, ue_pair = rcue.ue_1, rcue.ue_2
-        # Les UEs à afficher,
-        #  qui
-        ues_ro = [
+        # Les UEs à afficher : on regarde si read only et si dispense (non ré-inscription à l'UE)
+        ues_ro_dispense = [
             (
                 ue_impair,
                 rcue.ue_cur_impair is None,
+                deca.res_impair
+                and (deca.etud.id, ue_impair.id) in deca.res_impair.dispense_ues,
             ),
             (
                 ue_pair,
                 rcue.ue_cur_pair is None,
+                deca.res_pair
+                and (deca.etud.id, ue_pair.id) in deca.res_pair.dispense_ues,
             ),
         ]
         # Ordonne selon les dates des 2 semestres considérés:
         if reverse_semestre:
-            ues_ro[0], ues_ro[1] = ues_ro[1], ues_ro[0]
+            ues_ro_dispense[0], ues_ro_dispense[1] = (
+                ues_ro_dispense[1],
+                ues_ro_dispense[0],
+            )
         # Colonnes d'UE:
-        for ue, ue_read_only in ues_ro:
+        for ue, ue_read_only, ue_dispense in ues_ro_dispense:
             if ue:
                 H.append(
                     _gen_but_niveau_ue(
@@ -134,6 +140,7 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
                         disabled=read_only or ue_read_only,
                         annee_prec=ue_read_only,
                         niveau_id=ue.niveau_competence.id,
+                        ue_dispense=ue_dispense,
                     )
                 )
             else:
@@ -188,21 +195,30 @@ def _gen_but_niveau_ue(
     disabled: bool = False,
     annee_prec: bool = False,
     niveau_id: int = None,
+    ue_dispense: bool = False,
 ) -> str:
     if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
         moy_ue_str = f"""<span class="ue_cap">{
             scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
+
+        if ue_dispense:
+            etat_en_cours = """Non (ré)inscrit à cette UE"""
+        else:
+            etat_en_cours = f"""UE en cours
+            {   "sans notes" if np.isnan(dec_ue.moy_ue)
+                else
+                ("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>")
+            }
+            """
+
         scoplement = f"""<div class="scoplement">
             <div>
             <b>UE {ue.acronyme} capitalisée </b>
             <span>le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
             </span>
             </div>
-            <div>UE en cours
-            {   "sans notes" if np.isnan(dec_ue.moy_ue)
-                else
-                ("avec moyenne <b>" + scu.fmt_note(dec_ue.moy_ue) + "</b>")
-            }
+            <div>
+                { etat_en_cours }
             </div>
         </div>
         """
@@ -244,7 +260,13 @@ def _gen_but_niveau_ue(
             </div>
             """
         else:
-            scoplement = ""
+            if dec_ue.ue_status and dec_ue.ue_status["was_capitalized"]:
+                scoplement = """<div class="scoplement">
+                UE déjà capitalisée avec résultat moins favorable.
+                </div>
+                """
+            else:
+                scoplement = ""
 
     ue_class = ""  # 'recorded' if dec_ue.code_valide is not None else ''
     if dec_ue.code_valide is not None and dec_ue.codes:
diff --git a/app/but/rcue.py b/app/but/rcue.py
index 2fce54ffc..9a6ee73ce 100644
--- a/app/but/rcue.py
+++ b/app/but/rcue.py
@@ -75,7 +75,7 @@ class RegroupementCoherentUE:
             else None
         )
 
-        # Autres validations pour l'UE paire
+        # Autres validations pour les UEs paire/impaire
         self.validation_ue_best_pair = best_autre_ue_validation(
             etud.id,
             niveau.id,
@@ -101,14 +101,24 @@ class RegroupementCoherentUE:
         "résultats formsemestre de l'UE si elle est courante, None sinon"
         self.ue_status_impair = None
         if self.ue_cur_impair:
+            # UE courante
             ue_status = res_impair.get_etud_ue_status(etud.id, self.ue_cur_impair.id)
             self.moy_ue_1 = ue_status["moy"] if ue_status else None  # avec capitalisée
             self.ue_1 = self.ue_cur_impair
             self.res_impair = res_impair
             self.ue_status_impair = ue_status
         elif self.validation_ue_best_impair:
+            # UE capitalisée
             self.moy_ue_1 = self.validation_ue_best_impair.moy_ue
             self.ue_1 = self.validation_ue_best_impair.ue
+            if (
+                res_impair
+                and self.validation_ue_best_impair
+                and self.validation_ue_best_impair.ue
+            ):
+                self.ue_status_impair = res_impair.get_etud_ue_status(
+                    etud.id, self.validation_ue_best_impair.ue.id
+                )
         else:
             self.moy_ue_1, self.ue_1 = None, None
         self.moy_ue_1_val = self.moy_ue_1 if self.moy_ue_1 is not None else 0.0
diff --git a/app/comp/res_common.py b/app/comp/res_common.py
index 6e06487fc..9915c6162 100644
--- a/app/comp/res_common.py
+++ b/app/comp/res_common.py
@@ -438,7 +438,7 @@ class ResultatsSemestre(ResultatsCache):
 
     def get_etud_ue_status(self, etudid: int, ue_id: int) -> dict | None:
         """L'état de l'UE pour cet étudiant.
-        Result: dict, ou None si l'UE n'est pas dans ce semestre.
+        Result: dict, ou None si l'UE n'existe pas ou n'est pas dans ce semestre.
         {
             "is_capitalized": # vrai si la version capitalisée est celle prise en compte (meilleure)
             "was_capitalized":# si elle a été capitalisée (meilleure ou pas)
@@ -456,6 +456,8 @@ class ResultatsSemestre(ResultatsCache):
         }
         """
         ue: UniteEns = db.session.get(UniteEns, ue_id)
+        if not ue:
+            return None
         ue_dict = ue.to_dict()
 
         if ue.type == UE_SPORT:
diff --git a/app/static/css/jury_but.css b/app/static/css/jury_but.css
index 525c532f0..295e9496a 100644
--- a/app/static/css/jury_but.css
+++ b/app/static/css/jury_but.css
@@ -279,4 +279,10 @@ div.but_doc table tr td.amue {
 .but_autorisations_passage.but_explanation {
     font-weight: normal;
     color: var(--color-explanation);
+}
+
+.deca-expl {
+    font-size: 110%;
+    margin-bottom: 8px;
+    margin-left: 16px;
 }
\ No newline at end of file
diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css
index 31ceab22c..6bd88ebc5 100644
--- a/app/static/css/scodoc.css
+++ b/app/static/css/scodoc.css
@@ -3434,6 +3434,14 @@ div.formsemestre-warning-box {
   vertical-align: -40%;
 }
 
+.warning.warning-info::before {
+  height:24px;
+  width: 24px;
+  background-size: 24px 24px;
+  background-image: url(/ScoDoc/static/icons/warning-info.svg);
+}
+
+
 .warning-light {
   font-style: italic;
   color: rgb(166, 50, 159);
-- 
GitLab