diff --git a/app/models/ues.py b/app/models/ues.py index caa46ce41b..8627fea167 100644 --- a/app/models/ues.py +++ b/app/models/ues.py @@ -260,8 +260,10 @@ class UniteEns(db.Model): class DispenseUE(db.Model): """Dispense d'UE - Utilisé en APC (BUT) pour indiquer les étudiants redoublants avec une UE capitalisée - qu'ils ne refont pas. + Utilisé en APC (BUT) pour indiquer + - les étudiants redoublants avec une UE capitalisée qu'ils ne refont pas. + - les étudiants "non inscrit" à une UE car elle ne fait pas partie de leur Parcours. + La dispense d'UE n'est PAS une validation: - elle n'est pas affectée par les décisions de jury (pas effacée) - elle est associée à un formsemestre diff --git a/app/scodoc/sco_moduleimpl_inscriptions.py b/app/scodoc/sco_moduleimpl_inscriptions.py index 4c593c06d0..c693c9228f 100644 --- a/app/scodoc/sco_moduleimpl_inscriptions.py +++ b/app/scodoc/sco_moduleimpl_inscriptions.py @@ -613,11 +613,16 @@ def _list_but_ue_inscriptions(res: NotesTableCompat, read_only: bool = True) ->
- L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules +

L'inscription ou désinscription aux UEs du BUT n'affecte pas les inscriptions aux modules mais permet de "dispenser" un étudiant de suivre certaines UEs de son parcours. - Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'autres cas particuliers. - La dispense d'UE est réversible à tout moment (avant le jury de fin de semestre) +

+

Il peut s'agit d'étudiants redoublants ayant déjà acquis l'UE, ou d'une UE + présente dans le semestre mais pas dans le parcours de l'étudiant, ou bien d'autres + cas particuliers. +

+

La dispense d'UE est réversible à tout moment (avant le jury de fin de semestre) et n'affecte pas les notes saisies. +

""" diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index af91313ff5..549b375d1c 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -242,7 +242,19 @@ def formsemestre_recapcomplet( """ ) - + # Légende + H.append( + """ +
+
Codes utilisés dans cette table:
+
+
~
valeur manquante
+
=
UE dispensée
+
nan
valeur non disponible
+
+
+ """ + ) H.append(html_sco_header.sco_footer()) # HTML or binary data ? if len(H) > 1: diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 1129225d71..34c9d05e34 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -2672,6 +2672,30 @@ table.notes_recapcomplet a:hover { text-decoration: underline; } +div.table_recap_caption { + width: fit-content; + padding: 8px; + border-radius: 8px; + background-color: rgb(202, 255, 180); +} + +div.table_recap_caption div.title { + font-weight: bold; +} + +div.table_recap_caption div.captions { + display: grid; + grid-template-columns: 48px 200px; +} + +div.table_recap_caption div.captions div:nth-child(odd) { + text-align: center; +} + +div.table_recap_caption div.captions div:nth-child(even) { + font-style: italic; +} + /* bulletin */ div.notes_bulletin { margin-right: 5px; diff --git a/app/tables/jury_recap.py b/app/tables/jury_recap.py index 2be036a68b..5d9e5b8858 100644 --- a/app/tables/jury_recap.py +++ b/app/tables/jury_recap.py @@ -154,7 +154,7 @@ class TableJury(TableRecap): niveau: ApcNiveau = validation_rcue.niveau() titre = f"C{niveau.competence.numero}" # à voir (nommer les compétences...) row.add_cell( - f"c_{competence_id}_annee", + f"c_{competence_id}_{annee}", titre, validation_rcue.code, group="cursus_" + annee, diff --git a/app/tables/recap.py b/app/tables/recap.py index 989629ae19..ca55a10196 100644 --- a/app/tables/recap.py +++ b/app/tables/recap.py @@ -285,9 +285,9 @@ class TableRecap(tb.Table): notes = res.modimpl_notes(modimpl.id, ue.id) if np.isnan(notes).all(): # aucune note valide - row_min.add_cell(col_id, None, np.nan) - row_max.add_cell(col_id, None, np.nan) - moy = np.nan + row_min.add_cell(col_id, None, "") + row_max.add_cell(col_id, None, "") + moy = "" else: row_min.add_cell(col_id, None, self.fmt_note(np.nanmin(notes))) row_max.add_cell(col_id, None, self.fmt_note(np.nanmax(notes))) @@ -297,7 +297,7 @@ class TableRecap(tb.Table): None, self.fmt_note(moy), # aucune note dans ce module ? - classes=["col_empty" if np.isnan(moy) else ""], + classes=["col_empty" if (moy == "" or np.isnan(moy)) else ""], ) row_apo.add_cell(col_id, None, modimpl.module.code_apogee or "") @@ -618,7 +618,7 @@ class RowRecap(tb.Row): ): """Ajoute cols moy_gen moy_ue et tous les modules...""" etud = self.etud - table = self.table + table: TableRecap = self.table res = table.res # --- Si DEM ou DEF, ne montre aucun résultat d'UE ni moy. gen. if res.get_etud_etat(etud.id) != scu.INSCRIT: @@ -701,13 +701,17 @@ class RowRecap(tb.Row): def add_ue_cols(self, ue: UniteEns, ue_status: dict, col_group: str = None): "Ajoute résultat UE au row (colonne col_ue)" # sous-classé par JuryRow pour ajouter les codes - table = self.table + table: TableRecap = self.table formsemestre: FormSemestre = table.res.formsemestre table.group_titles[ "col_ue" ] = f"UEs du S{formsemestre.semestre_id} {formsemestre.annee_scolaire()}" col_id = f"moy_ue_{ue.id}" - val = ue_status["moy"] + val = ( + ue_status["moy"] + if (self.etud.id, ue.id) not in table.res.dispense_ues + else "=" + ) note_classes = [] if isinstance(val, float): if val < table.barre_moy: diff --git a/tests/ressources/yaml/cursus_but_gccd_cy.yaml b/tests/ressources/yaml/cursus_but_gccd_cy.yaml index 614c21e382..fe52f10b1a 100644 --- a/tests/ressources/yaml/cursus_but_gccd_cy.yaml +++ b/tests/ressources/yaml/cursus_but_gccd_cy.yaml @@ -134,7 +134,7 @@ FormSemestres: codes_parcours: ['BAT', 'TP'] Etudiants: - A_ok: # Etudiant qui va tout valider directement + A_ok: # Etudiant parcours BAT qui va tout valider directement prenom: Étudiant_BAT civilite: M formsemestres: @@ -157,9 +157,41 @@ Etudiants: S5: parcours: BAT + dispense_ues: ['UE5.2'] notes_modules: "R5.01": 15 # toutes UE "SAÉ 5.BAT.01": 10 # UE5.1 "SAÉ 5.BAT.02": 11 # UE5.4 S6: parcours: BAT + + B_ok: # Etudiant parcours TP qui va tout valider directement + prenom: Étudiant_TP + civilite: M + formsemestres: + S1: + parcours: TP + notes_modules: + "R1.01": 11 # toutes UEs + S2: + parcours: TP + notes_modules: + "R2.01": 12 # toutes UEs + S3: + parcours: TP + notes_modules: + "R3.01": 13 # toutes UEs + S4: + parcours: TP + notes_modules: + "R4.01": 14 # toutes UE + + S5: + parcours: TP + dispense_ues: ['UE5.1'] + notes_modules: + "R5.01": 15 # toutes UE + "SAÉ 5.BAT.01": 10 # UE5.1 + "SAÉ 5.BAT.02": 11 # UE5.4 + S6: + parcours: TP diff --git a/tests/unit/yaml_setup.py b/tests/unit/yaml_setup.py index 0f8544ff99..19696acd67 100644 --- a/tests/unit/yaml_setup.py +++ b/tests/unit/yaml_setup.py @@ -53,12 +53,14 @@ from app.auth.models import User from app.models import ( ApcParcours, + DispenseUE, Evaluation, Formation, FormSemestre, Identite, Module, ModuleImpl, + UniteEns, ) from app.scodoc import sco_formations @@ -263,6 +265,9 @@ def inscrit_les_etudiants(formation: Formation, doc: dict): group_ids = [group.id] else: group_ids = [] + # Génère des dispenses d'UEs + if "dispense_ues" in sem_infos: + etud_dispense_ues(formsemestre, etud, sem_infos["dispense_ues"]) sco_formsemestre_inscriptions.do_formsemestre_inscription_with_modules( formsemestre.id, etud.id, @@ -275,6 +280,19 @@ def inscrit_les_etudiants(formation: Formation, doc: dict): formsemestre.update_inscriptions_parcours_from_groups() +def etud_dispense_ues( + formsemestre: FormSemestre, etud: Identite, ue_acronymes: list[str] +): + """Génère des dispenses d'UE""" + for ue_acronyme in set(ue_acronymes): + ue: UniteEns = formsemestre.formation.ues.filter_by( + acronyme=ue_acronyme + ).first() + assert ue + disp = DispenseUE(formsemestre_id=formsemestre.id, ue_id=ue.id, etudid=etud.id) + db.session.add(disp) + + def setup_from_yaml(filename: str) -> dict: """Lit le fichier yaml et construit l'ensemble des objets""" with open(filename, encoding="utf-8") as f: