PV jury annuel BUT

This commit is contained in:
Emmanuel Viennet 2022-06-30 23:49:39 +02:00
parent 372a6e6ed6
commit e8e27dd964
9 changed files with 183 additions and 6 deletions

View File

@ -667,6 +667,30 @@ class DecisionsProposeesAnnee(DecisionsProposees):
db.session.delete(validation) db.session.delete(validation)
db.session.flush() 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): class DecisionsProposeesRCUE(DecisionsProposees):
"""Liste des codes de décisions que l'on peut proposer pour """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.delete(validation)
db.session.flush() 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): class DecisionsProposeesUE(DecisionsProposees):
"""Décisions de jury sur une UE du BUT """Décisions de jury sur une UE du BUT
@ -859,6 +898,14 @@ class DecisionsProposeesUE(DecisionsProposees):
db.session.delete(validation) db.session.delete(validation)
db.session.flush() 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 class BUTCursusEtud: # WIP TODO
"""Validation du cursus d'un étudiant""" """Validation du cursus d'un étudiant"""

106
app/but/jury_but_pv.py Normal file
View File

@ -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 = "<br/>"
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"<h2>{title}</h2>",
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
table_id="formation_table_recap",
)
return tab.make_page(format=format, javascripts=[])

View File

@ -98,7 +98,16 @@ def formsemestre_saisie_jury_but(
] ]
if mode == "recap": if mode == "recap":
H.append( H.append(
"""<h3>Décisions de jury enregistrées pour les étudiants de ce semestre</h3>""" f"""<h3>Décisions de jury enregistrées pour les étudiants de ce semestre</h3>
<div class="table_jury_but_links">
<div>
<a href="{url_for(
"notes.pvjury_table_but",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre2.id)
}" class="stdlink">tableau PV de jury</a>
</div>
</div>
"""
) )
H.append( H.append(
f""" f"""

View File

@ -195,7 +195,7 @@ class RegroupementCoherentUE:
"Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None" "Si ce RCUE est ADM, CMP ou ADJ, la validation. Sinon, None"
validation = self.query_validations().first() validation = self.query_validations().first()
if (validation is not None) and ( 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 validation
return None return None

View File

@ -365,6 +365,15 @@ class Identite(db.Model):
return situation return situation
def etat_civil_pv(self, line_sep="\n") -> str:
"""Présentation, pour PV jury
M. Pierre Dupont
n° 12345678
(e) le 7/06/1974
à Paris
"""
return f"""{self.nomprenom}{line_sep}{self.code_nip or ""}{line_sep}{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: def photo_html(self, title=None, size="small") -> str:
"""HTML img tag for the photo, either in small size (h90) """HTML img tag for the photo, either in small size (h90)
or original size (size=="orig") or original size (size=="orig")

View File

@ -127,6 +127,7 @@ class GenTable(object):
filename="table", # filename, without extension filename="table", # filename, without extension
xls_sheet_name="feuille", xls_sheet_name="feuille",
xls_before_table=[], # liste de cellules a placer avant la table 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_title="", # au dessus du tableau en pdf
pdf_table_style=None, pdf_table_style=None,
pdf_col_widths=None, pdf_col_widths=None,
@ -151,6 +152,7 @@ class GenTable(object):
self.page_title = page_title self.page_title = page_title
self.pdf_link = pdf_link self.pdf_link = pdf_link
self.xls_link = xls_link self.xls_link = xls_link
self.xls_style_base = xls_style_base
self.xml_link = xml_link self.xml_link = xml_link
# HTML parameters: # HTML parameters:
if not table_id: # random id if not table_id: # random id
@ -495,7 +497,8 @@ class GenTable(object):
sheet = wb.create_sheet(sheet_name=self.xls_sheet_name) sheet = wb.create_sheet(sheet_name=self.xls_sheet_name)
sheet.rows += self.xls_before_table sheet.rows += self.xls_before_table
style_bold = sco_excel.excel_make_style(bold=True) 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)) sheet.append_row(sheet.make_row(self.get_titles_list(), style_bold))
for line in self.get_data_list(xls_mode=True): for line in self.get_data_list(xls_mode=True):
sheet.append_row(sheet.make_row(line, style_base)) sheet.append_row(sheet.make_row(line, style_base))

View File

@ -189,7 +189,7 @@ CODES_SEM_ATTENTES = {ATT: True, ATB: True, ATJ: True} # semestre en attente
CODES_SEM_REO = {NAR: 1} # reorientation CODES_SEM_REO = {NAR: 1} # reorientation
CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True} # UE validée CODES_UE_VALIDES = {ADM: True, CMP: True, ADJ: True} # UE validée
CODES_RCUE_VALIDES = CODES_UE_VALIDES # Niveau RCUE validé
# Pour le BUT: # Pour le BUT:
CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL} CODES_ANNEE_ARRET = {DEF, DEM, ABAN, ABL}
CODES_RCUE = {ADM, AJ, CMP} CODES_RCUE = {ADM, AJ, CMP}

View File

@ -59,7 +59,7 @@ class COLORS(Enum):
LIGHT_YELLOW = "FFFFFF99" 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,... # font, border, number_format, fill,...
# (cf https://openpyxl.readthedocs.io/en/stable/styles.html#working-with-styles) # (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) 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é 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: if value is None:
value = "" value = ""
elif value is True: elif value is True:

View File

@ -42,6 +42,7 @@ from flask_login import current_user
from app.but import jury_but, jury_but_validation_auto from app.but import jury_but, jury_but_validation_auto
from app.but.forms import jury_but_forms from app.but.forms import jury_but_forms
from app.but import jury_but_pv
from app.but import jury_but_view from app.but import jury_but_view
from app.comp import res_sem from app.comp import res_sem
from app.comp.res_but import ResultatsSemestreBUT from app.comp.res_but import ResultatsSemestreBUT
@ -2564,6 +2565,8 @@ def formsemestre_validation_suppress_etud(
# ------------- PV de JURY et archives # ------------- PV de JURY et archives
sco_publish("/formsemestre_pvjury", sco_pvjury.formsemestre_pvjury, Permission.ScoView) 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") @bp.route("/formsemestre_saisie_jury")
@scodoc @scodoc