From 4aad637ff300251f7b47503ec593866c0582477d Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Tue, 15 Apr 2025 18:59:51 +0200
Subject: [PATCH] =?UTF-8?q?Export=20de=20toutes=20les=20notesd'un=20=C3=A9?=
 =?UTF-8?q?tudiant=20aux=20=C3=A9vals=20d'un=20semestre.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 app/scodoc/sco_bulletins.py    |  8 +++
 app/scodoc/sco_saisie_notes.py | 89 +++++++++++++++++++++++++++++-----
 app/scodoc/sco_utils.py        |  3 +-
 app/views/notes.py             | 19 ++++++++
 4 files changed, 105 insertions(+), 14 deletions(-)

diff --git a/app/scodoc/sco_bulletins.py b/app/scodoc/sco_bulletins.py
index 0d09b3ba..e2483606 100644
--- a/app/scodoc/sco_bulletins.py
+++ b/app/scodoc/sco_bulletins.py
@@ -1261,6 +1261,14 @@ def make_menu_autres_operations(
             # possible slt si on a un mail...
             "enabled": etud_perso and can_send_bulletin_by_mail(formsemestre.id),
         },
+        {
+            "title": "Exporter toutes les notes d'évaluations",
+            "endpoint": "notes.formsemestre_etud_export_all_notes",
+            "args": {
+                "formsemestre_id": formsemestre.id,
+                "etudid": etud.id,
+            },
+        },
         {
             "title": "Version json",
             "endpoint": endpoint,
diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py
index a53e4607..7146dd0b 100644
--- a/app/scodoc/sco_saisie_notes.py
+++ b/app/scodoc/sco_saisie_notes.py
@@ -48,7 +48,7 @@ from app.models import (
     NotesNotes,
 )
 from app.models.etudiants import Identite
-
+from app.scodoc.gen_tables import GenTable
 from app.scodoc.sco_exceptions import (
     AccessDenied,
     NoteProcessError,
@@ -63,6 +63,7 @@ from app.scodoc import sco_evaluations
 from app.scodoc import sco_formsemestre_inscriptions
 from app.scodoc import sco_groups
 from app.scodoc import sco_groups_view
+from app.scodoc import sco_preferences
 from app.scodoc import sco_undo_notes
 import app.scodoc.notesdb as ndb
 from app.scodoc.TrivialFormulator import TF
@@ -902,6 +903,23 @@ def get_data(formsemestre: FormSemestre, etud: Identite) -> DataForm:
     return data
 
 
+def _formsemestre_module_type_order(formsemestre: FormSemestre) -> list[ModuleType]:
+    """Liste des types de modules, pour avoir l'ordre d'affichage.
+    Dépend du type de formation."""
+    if formsemestre.formation.is_apc():
+        return [
+            ModuleType.RESSOURCE,
+            ModuleType.SAE,
+            ModuleType.STANDARD,
+            ModuleType.MALUS,
+        ]
+    # Formations classiques:
+    return [
+        ModuleType.STANDARD,
+        ModuleType.MALUS,
+    ]
+
+
 def saisie_notes_par_etu(formsemestre: FormSemestre, etud: Identite):
     "Formulaire de saisie de toutes les notes d'un semestre pour un étudiant"
     # Check access
@@ -928,18 +946,8 @@ def saisie_notes_par_etu(formsemestre: FormSemestre, etud: Identite):
     if inscription.etat != scu.INSCRIT:
         raise ScoValueError("Étudiant démissionnaire ou défaillant")
     data = get_data(formsemestre, etud)
-    if formsemestre.formation.is_apc():
-        module_type_order = [
-            ModuleType.RESSOURCE,
-            ModuleType.SAE,
-            ModuleType.STANDARD,
-            ModuleType.MALUS,
-        ]
-    else:
-        module_type_order = [
-            ModuleType.STANDARD,
-            ModuleType.MALUS,
-        ]
+    module_type_order = _formsemestre_module_type_order(formsemestre)
+
     return render_template(
         "etud/saisie_notes_par_etu.j2",
         title="Saisie notes par étudiant",
@@ -958,6 +966,61 @@ def get_evaluation_etud_note(evaluation: Evaluation, etudid: int) -> NotesNotes
     ).first()
 
 
+def formsemestre_etud_export_all_notes(
+    formsemestre: FormSemestre, etud: Identite
+) -> GenTable:
+    """Table avec toutes les notes de toutes les évaluations du formsemestre,
+    sans aucun calcul de moyennes.
+    """
+    data = get_data(formsemestre, etud)
+    titles = {
+        "modimpl_id": "modimpl_id",
+        "code_module": "Module",
+        "evaluation_id": "evaluation_id",
+        "evaluation_description": "Description",
+        "date_debut": "Date début",
+        "date_fin": "Date fin",
+        "evaluation_type": "Type",
+        "coef": "Coef",
+        "note": "Note",
+        "bareme": "Barème",
+    }
+    columns_ids = titles.keys()
+    # poids_{ue_acronyme} (une colonne par UE)
+    rows = []
+    for module_type in _formsemestre_module_type_order(formsemestre):
+        for data_modimpl in data.data_sections[module_type].data_modimpl:
+            for data_eval in data_modimpl.data_evals:
+                rows.append(
+                    {
+                        "modimpl_id": data_modimpl.modimpl.id,
+                        "code_module": data_modimpl.module_code or "",
+                        "evaluation_id": data_eval.evaluation.id,
+                        "evaluation_description": data_eval.evaluation.description
+                        or "",
+                        "bareme": data_eval.evaluation.note_max,
+                        "coef": data_eval.evaluation.coefficient,
+                        "date_debut": data_eval.evaluation.date_debut or "",
+                        "date_fin": data_eval.evaluation.date_fin or "",
+                        "evaluation_type": data_eval.evaluation.type_abbrev(),
+                        "note": (
+                            scu.fmt_note(data_eval.note.value, keep_numeric=True)
+                            if data_eval.note
+                            else ""
+                        ),
+                    }
+                )
+    name = scu.make_filename(f"notes_{etud.nomprenom}_{formsemestre.titre_num()}")
+    return GenTable(
+        titles=titles,
+        columns_ids=columns_ids,
+        rows=rows,
+        preferences=sco_preferences.SemPreferences(formsemestre.id),
+        xls_sheet_name=name,
+        filename=name + scu.XLSX_SUFFIX,
+    )
+
+
 def get_sorted_etuds_notes(
     evaluation: Evaluation, etudids: list, formsemestre_id: int
 ) -> list[dict]:
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index e1ee3286..b6aecabd 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -655,7 +655,8 @@ def fmt_note(
     val, note_max=None, keep_numeric=False, fixed_precision_str=True
 ) -> str | float:
     """conversion note en str pour affichage dans tables HTML ou PDF.
-    Si keep_numeric, laisse les valeur numeriques telles quelles (pour export Excel)
+    Si keep_numeric, laisse les valeur numeriques telles quelles (pour export Excel).
+    Si note_max > 0, normalise sur cette valeur.
     Si fixed_precision_str (défaut), formatte sur 4 chiffres ("01.23"),
     sinon utilise %g (chaine précision variable, pour les formulaires)
     """
diff --git a/app/views/notes.py b/app/views/notes.py
index 8f5929a2..c3ca15a9 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -1942,6 +1942,25 @@ def moduleimpl_feuille_import_notes(moduleimpl_id: int):
     return scu.send_file(xls, filename, scu.XLSX_SUFFIX, mime=scu.XLSX_MIMETYPE)
 
 
+# --- Export des notes
+@bp.route("formsemestre_etud_export_all_notes/<int:formsemestre_id>/<int:etudid>")
+@scodoc
+@permission_required(Permission.ScoView)
+def formsemestre_etud_export_all_notes(formsemestre_id: int, etudid: int):
+    """Exporte un classeur xlsx avec toutes les notes de l'étudiant
+    aux évaluations de ce semestre (sans les moyennes).
+    """
+    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
+    etud = Identite.get_etud(etudid)
+    table = sco_saisie_notes.formsemestre_etud_export_all_notes(formsemestre, etud)
+    return scu.send_file(
+        table.excel(),
+        table.filename,
+        scu.XLSX_SUFFIX,
+        mime=scu.XLSX_MIMETYPE,
+    )
+
+
 # --- Bulletins
 @bp.route("/formsemestre_bulletins_pdf")
 @scodoc
-- 
GitLab