From e8e27dd9643e0fab9344a9e890d94346129836d0 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Thu, 30 Jun 2022 23:49:39 +0200 Subject: [PATCH] PV jury annuel BUT --- app/but/jury_but.py | 47 ++++++++++++++ app/but/jury_but_pv.py | 106 +++++++++++++++++++++++++++++++ app/but/jury_but_recap.py | 11 +++- app/models/but_validations.py | 2 +- app/models/etudiants.py | 9 +++ app/scodoc/gen_tables.py | 5 +- app/scodoc/sco_codes_parcours.py | 2 +- app/scodoc/sco_excel.py | 4 +- app/views/notes.py | 3 + 9 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 app/but/jury_but_pv.py diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 3a3472c33..3a797ad83 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -667,6 +667,30 @@ class DecisionsProposeesAnnee(DecisionsProposees): db.session.delete(validation) db.session.flush() + def get_autorisations_passage(self) -> list[int]: + """Les liste des indices de semestres auxquels on est autorisé à + s'inscrire depuis cette année""" + formsemestre = self.formsemestre_pair or self.formsemestre_impair + if not formsemestre: + return [] + return [ + a.semestre_id + for a in ScolarAutorisationInscription.query.filter_by( + etudid=self.etud.id, + origin_formsemestre_id=formsemestre.id, + ) + ] + + def descr_niveaux_validation(self, line_sep: str = "\n") -> str: + """Description textuelle des niveaux validés (enregistrés) + pour PV jurys + """ + validations = [ + dec_rcue.descr_validation() + for dec_rcue in self.decisions_rcue_by_niveau.values() + ] + return line_sep.join([v for v in validations if v]) + class DecisionsProposeesRCUE(DecisionsProposees): """Liste des codes de décisions que l'on peut proposer pour @@ -742,6 +766,21 @@ class DecisionsProposeesRCUE(DecisionsProposees): db.session.delete(validation) db.session.flush() + def descr_validation(self) -> str: + """Description validation niveau enregistrée, pour PV jury. + Si le niveau est validé, done son acronyme, sinon chaine vide. + """ + if self.code_valide in sco_codes.CODES_RCUE_VALIDES: + if ( + self.rcue and self.rcue.ue_1 and self.rcue.ue_1.niveau_competence + ): # prudence ! + niveau_titre = self.rcue.ue_1.niveau_competence.competence.titre or "" + ordre = self.rcue.ue_1.niveau_competence.ordre + else: + return "?" # oups ? + return f"{niveau_titre} niv. {ordre}" + return "" + class DecisionsProposeesUE(DecisionsProposees): """Décisions de jury sur une UE du BUT @@ -859,6 +898,14 @@ class DecisionsProposeesUE(DecisionsProposees): db.session.delete(validation) db.session.flush() + def descr_validation(self) -> str: + """Description validation niveau enregistrée, pour PV jury. + Si l'UE est validé, done son acronyme, sinon chaine vide. + """ + if self.code_valide in sco_codes.CODES_UE_VALIDES: + return f"{self.ue.acronyme}" + return "" + class BUTCursusEtud: # WIP TODO """Validation du cursus d'un étudiant""" diff --git a/app/but/jury_but_pv.py b/app/but/jury_but_pv.py new file mode 100644 index 000000000..1524c0997 --- /dev/null +++ b/app/but/jury_but_pv.py @@ -0,0 +1,106 @@ +############################################################################## +# ScoDoc +# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved. +# See LICENSE +############################################################################## + +"""Jury BUT: table synthèse résultats semestre / PV +""" +from flask import g, request, url_for + +from openpyxl.styles import Font, Border, Side, Alignment, PatternFill + +from app import log +from app.but import jury_but +from app.models.etudiants import Identite +from app.models.formsemestre import FormSemestre +from app.scodoc.gen_tables import GenTable +from app.scodoc import sco_excel +from app.scodoc.sco_exceptions import ScoValueError +from app.scodoc import sco_preferences +from app.scodoc import sco_utils as scu + + +def _descr_cursus_but(etud: Identite) -> str: + "description de la liste des semestres BUT suivis" + # prend simplement tous les semestre de type APC, ce qui sera faux si + # l'étudiant change de spécialité au sein du même département + # (ce qui ne peut normalement pas se produire) + indices = [ + ins.formsemestre.semestre_id + for ins in etud.formsemestre_inscriptions + if ins.formsemestre.formation.is_apc() + ] + return ", ".join(f"S{indice}" for indice in indices) + + +def pvjury_table_but(formsemestre_id: int, format="html") -> list[dict]: + """Page récapitulant les décisions de jury BUT + formsemestre peut être pair ou impair + """ + formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id) + assert formsemestre.formation.is_apc() + title = "Jury BUT annuel" + + if format == "html": + line_sep = "
" + else: + line_sep = "\n" + # remplace pour le BUT la fonction sco_pvjury.pvjury_table + annee_but = (formsemestre.semestre_id + 1) // 2 + titles = { + "nom": "Nom", + "cursus": "Cursus", + "niveaux": "Niveaux de compétences validés", + "decision_but": f"Décision BUT{annee_but}", + "diplome": "Résultat au diplôme", + "devenir": "Devenir", + "observations": "Observations", + } + rows = [] + for etudid in formsemestre.etuds_inscriptions: + etud: Identite = Identite.query.get(etudid) + try: + deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) + if deca.annee_but != annee_but: # wtf ? + log( + f"pvjury_table_but: inconsistent annee_but {deca.annee_but} != {annee_but}" + ) + continue + except ScoValueError: + deca = None + row = { + "nom": etud.etat_civil_pv(line_sep=line_sep), + "cursus": _descr_cursus_but(etud), + "niveaux": deca.descr_niveaux_validation(line_sep=line_sep) + if deca + else "-", + "decision_but": deca.code_valide if deca else "", + "devenir": ", ".join([f"S{i}" for i in deca.get_autorisations_passage()]), + } + + rows.append(row) + + # Style excel... passages à la ligne sur \n + xls_style_base = sco_excel.excel_make_style() + xls_style_base["alignment"] = Alignment(wrapText=True, vertical="top") + + tab = GenTable( + columns_ids=titles.keys(), + rows=rows, + titles=titles, + origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}", + caption=title, + html_caption=title, + html_class="pvjury_table_but table_leftalign", + # html_class_ignore_default=True, + html_with_td_classes=True, + xls_style_base=xls_style_base, + base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}", + page_title=title, + html_title=f"

{title}

", + pdf_title=title, + preferences=sco_preferences.SemPreferences(), + table_id="formation_table_recap", + ) + return tab.make_page(format=format, javascripts=[]) diff --git a/app/but/jury_but_recap.py b/app/but/jury_but_recap.py index 1eca11cfb..c2944d98c 100644 --- a/app/but/jury_but_recap.py +++ b/app/but/jury_but_recap.py @@ -98,7 +98,16 @@ def formsemestre_saisie_jury_but( ] if mode == "recap": H.append( - """

Décisions de jury enregistrées pour les étudiants de ce semestre

""" + f"""

Décisions de jury enregistrées pour les étudiants de ce semestre

+ + """ ) H.append( f""" diff --git a/app/models/but_validations.py b/app/models/but_validations.py index 5ec0e09dd..6c2814424 100644 --- a/app/models/but_validations.py +++ b/app/models/but_validations.py @@ -195,7 +195,7 @@ class RegroupementCoherentUE: "Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None" validation = self.query_validations().first() if (validation is not None) and ( - validation.code in {sco_codes.ADM, sco_codes.ADJ, sco_codes.CMP} + validation.code in sco_codes.CODES_RCUE_VALIDES ): return validation return None diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 21b3a6384..6bed20db5 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -365,6 +365,15 @@ class Identite(db.Model): return situation + def etat_civil_pv(self, line_sep="\n") -> str: + """Présentation, pour PV jury + M. Pierre Dupont + n° 12345678 + né(e) le 7/06/1974 + à Paris + """ + return f"""{self.nomprenom}{line_sep}n°{self.code_nip or ""}{line_sep}né{self.e} le {self.date_naissance.strftime("%d/%m/%Y") if self.date_naissance else ""}{line_sep}à {self.lieu_naissance or ""}""" + def photo_html(self, title=None, size="small") -> str: """HTML img tag for the photo, either in small size (h90) or original size (size=="orig") diff --git a/app/scodoc/gen_tables.py b/app/scodoc/gen_tables.py index 1c708ca5c..48e5d0630 100644 --- a/app/scodoc/gen_tables.py +++ b/app/scodoc/gen_tables.py @@ -127,6 +127,7 @@ class GenTable(object): filename="table", # filename, without extension xls_sheet_name="feuille", xls_before_table=[], # liste de cellules a placer avant la table + xls_style_base=None, # style excel pour les cellules pdf_title="", # au dessus du tableau en pdf pdf_table_style=None, pdf_col_widths=None, @@ -151,6 +152,7 @@ class GenTable(object): self.page_title = page_title self.pdf_link = pdf_link self.xls_link = xls_link + self.xls_style_base = xls_style_base self.xml_link = xml_link # HTML parameters: if not table_id: # random id @@ -495,7 +497,8 @@ class GenTable(object): sheet = wb.create_sheet(sheet_name=self.xls_sheet_name) sheet.rows += self.xls_before_table style_bold = sco_excel.excel_make_style(bold=True) - style_base = sco_excel.excel_make_style() + style_base = self.xls_style_base or sco_excel.excel_make_style() + sheet.append_row(sheet.make_row(self.get_titles_list(), style_bold)) for line in self.get_data_list(xls_mode=True): sheet.append_row(sheet.make_row(line, style_base)) diff --git a/app/scodoc/sco_codes_parcours.py b/app/scodoc/sco_codes_parcours.py index 84f9087a7..dc59aeb2e 100644 --- a/app/scodoc/sco_codes_parcours.py +++ b/app/scodoc/sco_codes_parcours.py @@ -189,7 +189,7 @@ CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente CODES_SEM_REO = {NAR: 1} # reorientation CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True} # UE validée - +CODES_RCUE_VALIDES = CODES_UE_VALIDES # Niveau RCUE validé # Pour le BUT: CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL} CODES_RCUE = {ADM, AJ, CMP} diff --git a/app/scodoc/sco_excel.py b/app/scodoc/sco_excel.py index 5d1b2d729..d6247207c 100644 --- a/app/scodoc/sco_excel.py +++ b/app/scodoc/sco_excel.py @@ -59,7 +59,7 @@ class COLORS(Enum): LIGHT_YELLOW = "FFFFFF99" -# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attributdans la liste suivante: +# Un style est enregistré comme un dictionnaire qui précise la valeur d'un attribut dans la liste suivante: # font, border, number_format, fill,... # (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles) @@ -288,7 +288,7 @@ class ScoExcelSheet: value -- contenu de la cellule (texte, numérique, booléen ou date) style -- style par défaut (dictionnaire cf. excel_make_style) de la feuille si non spécifié """ - # adapatation des valeurs si nécessaire + # adaptation des valeurs si nécessaire if value is None: value = "" elif value is True: diff --git a/app/views/notes.py b/app/views/notes.py index b89b24438..11b794810 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -42,6 +42,7 @@ from flask_login import current_user from app.but import jury_but, jury_but_validation_auto from app.but.forms import jury_but_forms +from app.but import jury_but_pv from app.but import jury_but_view from app.comp import res_sem from app.comp.res_but import ResultatsSemestreBUT @@ -2564,6 +2565,8 @@ def formsemestre_validation_suppress_etud( # ------------- PV de JURY et archives sco_publish("/formsemestre_pvjury", sco_pvjury.formsemestre_pvjury, Permission.ScoView) +sco_publish("/pvjury_table_but", jury_but_pv.pvjury_table_but, Permission.ScoView) + @bp.route("/formsemestre_saisie_jury") @scodoc