diff --git a/app/but/bulletin_but.py b/app/but/bulletin_but.py index db129f9d..3012a7a9 100644 --- a/app/but/bulletin_but.py +++ b/app/but/bulletin_but.py @@ -13,6 +13,7 @@ from flask import url_for, g from app.comp.res_but import ResultatsSemestreBUT from app.models import FormSemestre, Identite +from app.models.ues import UniteEns from app.scodoc import sco_bulletins, sco_utils as scu from app.scodoc import sco_bulletins_json from app.scodoc import sco_bulletins_pdf @@ -62,18 +63,15 @@ class BulletinBUT: # } return d - def etud_ue_results(self, etud, ue): + def etud_ue_results(self, etud: Identite, ue: UniteEns, decision_ue: dict) -> dict: "dict synthèse résultats UE" res = self.res + d = { "id": ue.id, "titre": ue.titre, "numero": ue.numero, "type": ue.type, - "ECTS": { - "acquis": 0.0, # XXX TODO voir jury #sco92 - "total": ue.ects or 0.0, # float même si non renseigné - }, "color": ue.color, "competence": None, # XXX TODO lien avec référentiel "moyenne": None, @@ -86,6 +84,11 @@ class BulletinBUT: "ressources": self.etud_ue_mod_results(etud, ue, res.ressources), "saes": self.etud_ue_mod_results(etud, ue, res.saes), } + if self.prefs["bul_show_ects"]: + d["ECTS"] = { + "acquis": decision_ue.get("ects", 0.0), + "total": ue.ects or 0.0, # float même si non renseigné + } if ue.type != UE_SPORT: if self.prefs["bul_show_ue_rangs"]: rangs, effectif = res.ue_rangs[ue.id] @@ -277,11 +280,17 @@ class BulletinBUT: "numero": formsemestre.semestre_id, "inscription": "", # inutilisé mais nécessaire pour le js de Seb. "groupes": [], # XXX TODO - "absences": { + } + if self.prefs["bul_show_abs"]: + semestre_infos["absences"] = { "injustifie": nbabs - nbabsjust, "total": nbabs, - }, - } + } + decisions_ues = self.res.get_etud_decision_ues(etud.id) or {} + if self.prefs["bul_show_ects"]: + ects_tot = sum([ue.ects or 0 for ue in res.ues]) if res.ues else 0.0 + ects_acquis = sum([d.get("ects", 0) for d in decisions_ues.values()]) + semestre_infos["ECTS"] = {"acquis": ects_acquis, "total": ects_tot} semestre_infos.update( sco_bulletins_json.dict_decision_jury(etud.id, formsemestre.id) ) @@ -307,7 +316,9 @@ class BulletinBUT: ), "saes": self.etud_mods_results(etud, res.saes, version=version), "ues": { - ue.acronyme: self.etud_ue_results(etud, ue) + ue.acronyme: self.etud_ue_results( + etud, ue, decision_ue=decisions_ues.get(ue.id, {}) + ) for ue in res.ues # si l'UE comporte des modules auxquels on est inscrit: if ( diff --git a/app/comp/res_common.py b/app/comp/res_common.py index c28eaa60..fa2b9c30 100644 --- a/app/comp/res_common.py +++ b/app/comp/res_common.py @@ -65,6 +65,9 @@ class ResultatsSemestre(ResultatsCache): self.moyennes_matieres = {} """Moyennes de matières, si calculées. { matiere_id : Series, index etudid }""" + def __repr__(self): + return f"<{self.__class__.__name__}(id={self.id}, formsemestre='{self.formsemestre}')>" + def compute(self): "Charge les notes et inscriptions et calcule toutes les moyennes" # voir ce qui est chargé / calculé ici et dans les sous-classes @@ -177,7 +180,6 @@ class ResultatsSemestre(ResultatsCache): if not self.validations: self.validations = res_sem.load_formsemestre_validations(self.formsemestre) ue_capitalisees = self.validations.ue_capitalisees - ue_by_code = {} for etudid in ue_capitalisees.index: recompute_mg = False # ue_codes = set(ue_capitalisees.loc[etudid]["ue_code"]) diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py index ceea7eb6..060c859f 100644 --- a/app/models/formsemestre.py +++ b/app/models/formsemestre.py @@ -161,7 +161,6 @@ class FormSemestre(db.Model): d["periode"] = 1 # typiquement, début en septembre: S1, S3... else: d["periode"] = 2 # typiquement, début en février: S2, S4... - d["titre_num"] = self.titre_num() d["titreannee"] = self.titre_annee() d["mois_debut"] = self.mois_debut() d["mois_fin"] = self.mois_fin() @@ -174,7 +173,6 @@ class FormSemestre(db.Model): d["session_id"] = self.session_id() d["etapes"] = self.etapes_apo_vdi() d["etapes_apo_str"] = self.etapes_apo_str() - d["responsables"] = [u.id for u in self.responsables] # liste des ids return d def query_ues(self, with_sport=False) -> flask_sqlalchemy.BaseQuery: @@ -302,6 +300,10 @@ class FormSemestre(db.Model): else: return ", ".join([u.get_nomcomplet() for u in self.responsables]) + def est_responsable(self, user): + "True si l'user est l'un des responsables du semestre" + return user.id in [u.id for u in self.responsables] + def annee_scolaire_str(self): "2021 - 2022" return scu.annee_scolaire_repr(self.date_debut.year, self.date_debut.month) diff --git a/app/scodoc/sco_bulletins_pdf.py b/app/scodoc/sco_bulletins_pdf.py index 92eabf23..501bd98c 100644 --- a/app/scodoc/sco_bulletins_pdf.py +++ b/app/scodoc/sco_bulletins_pdf.py @@ -51,10 +51,11 @@ Chaque semestre peut si nécessaire utiliser un type de bulletin différent. """ import io +import pprint +import pydoc import re import time import traceback -import pydoc from flask import g, request @@ -140,7 +141,11 @@ def process_field(field, cdict, style, suppress_empty_pars=False, format="pdf"): cdict ) # note that None values are mapped to empty strings except: - log("process_field: invalid format=%s" % field) + log( + f"""process_field: invalid format. field={field!r} + values={pprint.pformat(cdict)} + """ + ) text = ( "format invalide !" + traceback.format_exc() diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index e5c07fd1..4da36cb7 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -1172,7 +1172,7 @@ class BasePreferences(object): "bul_show_abs", # ex "gestion_absence" { "initvalue": 1, - "title": "Indiquer les absences sous les bulletins", + "title": "Indiquer les absences dans les bulletins", "input_type": "boolcheckbox", "category": "bul", "labels": ["non", "oui"], diff --git a/app/static/css/releve-but.css b/app/static/css/releve-but.css index d9756ace..41e15448 100644 --- a/app/static/css/releve-but.css +++ b/app/static/css/releve-but.css @@ -175,11 +175,24 @@ section>div:nth-child(1){ .ue .rang{ font-weight: 400; } +.absencesRecap { + align-items: baseline; +} +.absencesRecap > div:nth-child(2n) { + font-weight: normal; +} +.abs { + font-weight: bold; +} .decision{ margin: 5px 0; font-weight: bold; font-size: 20px; - text-decoration: underline var(--couleurIntense); +} +#ects_tot { + margin-left: 8px; + font-weight: bold; + font-size: 20px; } .enteteSemestre{ color: black; diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index eab9bb12..61d2e2ec 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -2152,6 +2152,18 @@ div.eval_description { padding: 3px; } +div.bul_foot { + max-width: 1000px; + background: #FFE7D5; + border-radius: 16px; + border: 1px solid #AAA; + padding: 16px 32px; + margin: auto; +} +div.bull_appreciations { + border-left: 1px solid black; + padding-left: 5px; +} /* Saisie des notes */ div.saisienote_etape1 { diff --git a/app/static/js/releve-but.js b/app/static/js/releve-but.js index 97f97e29..897ccbe6 100644 --- a/app/static/js/releve-but.js +++ b/app/static/js/releve-but.js @@ -83,7 +83,7 @@ class releveBUT extends HTMLElement {
-
+
Inscrit le
Les moyennes ci-dessus servent à situer l'étudiant dans la promotion et ne correspondent pas à des validations de compétences ou d'UE.
@@ -204,9 +204,10 @@ class releveBUT extends HTMLElement {
Min. promo. :
${data.semestre.notes.min}
-
Absences
-
N.J. ${data.semestre.absences?.injustifie ?? "-"}
-
Total ${data.semestre.absences?.total ?? "-"}
+
Absences
1/2 jour.
+
Non justifiées
+
${data.semestre.absences?.injustifie ?? "-"}
+
Total
${data.semestre.absences?.total ?? "-"}
photo de l'étudiant `; @@ -223,7 +224,8 @@ class releveBUT extends HTMLElement { }).join("") }*/ this.shadow.querySelector(".infoSemestre").innerHTML = output; - this.shadow.querySelector(".decision").innerHTML = data.semestre.decision?.code || ""; + this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || ""); + this.shadow.querySelector("#ects_tot").innerHTML = "ECTS : " + (data.semestre.ECTS?.acquis || "-") + " / " + (data.semestre.ECTS?.total || "-"); } /*******************************/ @@ -256,7 +258,7 @@ class releveBUT extends HTMLElement { Bonus : ${dataUE.bonus || 0} - Malus : ${dataUE.malus || 0}  - - ECTS : ${dataUE.ECTS.acquis} / ${dataUE.ECTS.total} + ECTS : ${dataUE.ECTS?.acquis || "-"} / ${dataUE.ECTS?.total || "-"} `; @@ -377,9 +379,9 @@ class releveBUT extends HTMLElement { setOptions(options) { Object.entries(options).forEach(([option, value]) => { if (value === false) { - document.body.classList.add(option.replace("show", "hide")) + this.shadow.children[0].classList.add(option.replace("show", "hide")); } - }) + }); } diff --git a/app/templates/bul_foot.html b/app/templates/bul_foot.html index 873b43c7..9addd770 100644 --- a/app/templates/bul_foot.html +++ b/app/templates/bul_foot.html @@ -1,28 +1,54 @@ {# -*- mode: jinja-html -*- #} {# Pied des bulletins HTML #} -

Situation actuelle: -{% if inscription_courante %} -{{inscription_str}} -{% else %} - {{inscription_str}} -{% endif %} -

+
+
+

Situation actuelle: + {% if inscription_courante %} + {{inscription_str}} + {% else %} + {{inscription_str}} + {% endif %} +

-{% if formsemestre.modalite == "EXT" %} -

- Éditer les validations d'UE dans ce semestre extérieur -

-{% endif %} +
+

Appréciations

+ {% for app in appreciations %} +

{{app.date}}{{ + app.comment}}{% if can_edit_appreciations %}modifier + supprimer{% endif %} + +

+ {% endfor %} + {% if can_edit_appreciations %} +

Ajouter une appréciation +

+ {% endif %} +
+ + {% if formsemestre.modalite == "EXT" %} +

+ Éditer les validations d'UE dans ce semestre extérieur +

+ {% endif %} +
+
{# Place du diagramme radar #}
diff --git a/app/templates/formsemestre_page_title.html b/app/templates/formsemestre_page_title.html index 265d8cae..d6ea4ce4 100644 --- a/app/templates/formsemestre_page_title.html +++ b/app/templates/formsemestre_page_title.html @@ -5,7 +5,7 @@
{{formsemestre.titre}} diff --git a/app/views/notes.py b/app/views/notes.py index 9f243cb9..c8587c4f 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -323,6 +323,9 @@ def formsemestre_bulletinetud( elif format == "html": return render_template( "but/bulletin.html", + appreciations=models.BulAppreciations.query.filter_by( + etudid=etudid, formsemestre_id=formsemestre.id + ).order_by(models.BulAppreciations.date), bul_url=url_for( "notes.formsemestre_bulletinetud", scodoc_dept=g.scodoc_dept, @@ -332,6 +335,8 @@ def formsemestre_bulletinetud( force_publishing=1, # pour ScoDoc lui même version=version, ), + can_edit_appreciations=formsemestre.est_responsable(current_user) + or (current_user.has_permission(Permission.ScoEtudInscrit)), etud=etud, formsemestre=formsemestre, inscription_courante=etud.inscription_courante(),