PV jury BUT pdf

This commit is contained in:
Emmanuel Viennet 2023-02-18 18:49:52 +01:00
parent 1d6145010b
commit 52f98b66c1
7 changed files with 211 additions and 139 deletions

View File

@ -242,7 +242,7 @@ class DecisionsProposeesAnnee(DecisionsProposees):
# Si on part d'un semestre IMPAIR, il n'y aura pas de décision année proposée
# (mais on pourra évidemment valider des UE et même des RCUE)
self.jury_annuel: bool = formsemestre.semestre_id in (2, 4, 6)
"vrai si jury de fin d'année scolaire (propose code annuel)"
"vrai si jury de fin d'année scolaire (sem. pair, propose code annuel)"
self.formsemestre_impair = formsemestre_impair
"le 1er semestre du groupement (S1, S3, S5)"
@ -634,6 +634,17 @@ class DecisionsProposeesAnnee(DecisionsProposees):
d[dec_rcue.rcue.ue_2.id] = dec_rcue
return d
def formsemestre_ects(self) -> float:
"ECTS validés dans le formsemestre de départ du deca"
ues = self.ues_impair if self.formsemestre.semestre_id % 2 else self.ues_pair
return sum(
[
self.decisions_ues[ue.id].ects_acquis()
for ue in ues
if ue.id in self.decisions_ues
]
)
def next_semestre_ids(self, code: str) -> set[int]:
"""Les indices des semestres dans lequels l'étudiant est autorisé
à poursuivre après le semestre courant.
@ -1331,6 +1342,14 @@ class DecisionsProposeesUE(DecisionsProposees):
return f"{self.ue.acronyme}"
return ""
def ects_acquis(self) -> float:
"""ECTS enregistrés pour cette UE
(0 si pas de validation enregistrée)
"""
if self.validation and self.code_valide in sco_codes.CODES_UE_VALIDES:
return self.ue.ects
return 0.0
class BUTCursusEtud: # WIP TODO
"""Validation du cursus d'un étudiant"""

View File

@ -38,7 +38,7 @@ def _descr_cursus_but(etud: Identite) -> str:
return ", ".join(f"S{indice}" for indice in indices)
def pvjury_table_but(formsemestre_id: int, format="html"):
def pvjury_page_but(formsemestre_id: int, fmt="html"):
"""Page récapitulant les décisions de jury BUT
formsemestre peut être pair ou impair
"""
@ -46,15 +46,62 @@ def pvjury_table_but(formsemestre_id: int, format="html"):
assert formsemestre.formation.is_apc()
title = "Procès-verbal de jury BUT annuel"
if format == "html":
if fmt == "html":
line_sep = "<br>"
else:
line_sep = "\n"
rows, titles = pvjury_table_but(formsemestre, line_sep=line_sep)
# 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(
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
caption=title,
columns_ids=titles.keys(),
html_caption=title,
html_class="pvjury_table_but table_leftalign",
html_title=f"""<div style="margin-bottom: 8px;"><span style="font-size: 120%; font-weight: bold;">{title}</span>
<span style="padding-left: 20px;">
<a href="{url_for("notes.pvjury_page_but",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, fmt="xlsx")}"
class="stdlink">version excel</a></span></div>
""",
html_with_td_classes=True,
origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
page_title=title,
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
rows=rows,
table_id="formation_table_recap",
titles=titles,
xls_columns_width={
"nom": 32,
"cursus": 12,
"ues": 32,
"niveaux": 32,
"decision_but": 14,
"diplome": 17,
"devenir": 8,
"observations": 12,
},
xls_style_base=xls_style_base,
)
return tab.make_page(format=fmt, javascripts=["js/etud_info.js"], init_qtip=True)
def pvjury_table_but(
formsemestre: FormSemestre, line_sep: str = "\n"
) -> tuple[list[dict], dict]:
"table avec résultats jury BUT pour PV"
# remplace pour le BUT la fonction sco_pvjury.pvjury_table
annee_but = (formsemestre.semestre_id + 1) // 2
titles = {
"nom": "Nom",
"cursus": "Cursus",
"ects": "ECTS",
"ues": "UE validées",
"niveaux": "Niveaux de compétences validés",
"decision_but": f"Décision BUT{annee_but}",
@ -85,6 +132,7 @@ def pvjury_table_but(formsemestre_id: int, format="html"):
etudid=etud.id,
),
"cursus": _descr_cursus_but(etud),
"ects": f"{deca.formsemestre_ects():g}",
"ues": deca.descr_ues_validation(line_sep=line_sep) if deca else "-",
"niveaux": deca.descr_niveaux_validation(line_sep=line_sep)
if deca
@ -98,42 +146,4 @@ def pvjury_table_but(formsemestre_id: int, format="html"):
rows.append(row)
rows.sort(key=lambda x: x["_nom_order"])
# 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(
base_url=f"{request.base_url}?formsemestre_id={formsemestre_id}",
caption=title,
columns_ids=titles.keys(),
html_caption=title,
html_class="pvjury_table_but table_leftalign",
html_title=f"""<div style="margin-bottom: 8px;"><span style="font-size: 120%; font-weight: bold;">{title}</span>
<span style="padding-left: 20px;">
<a href="{url_for("notes.pvjury_table_but",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, format="xlsx")}"
class="stdlink">version excel</a></span></div>
""",
html_with_td_classes=True,
origin=f"Généré par {scu.sco_version.SCONAME} le {scu.timedate_human_repr()}",
page_title=title,
pdf_title=title,
preferences=sco_preferences.SemPreferences(),
rows=rows,
table_id="formation_table_recap",
titles=titles,
xls_columns_width={
"nom": 32,
"cursus": 12,
"ues": 32,
"niveaux": 32,
"decision_but": 14,
"diplome": 17,
"devenir": 8,
"observations": 12,
},
xls_style_base=xls_style_base,
)
return tab.make_page(format=format, javascripts=["js/etud_info.js"], init_qtip=True)
return rows, titles

View File

@ -363,7 +363,7 @@ def do_formsemestre_archive(
PVArchive.store(archive_id, "Bulletins.json", data_js)
# Décisions de jury, en XLS
if formsemestre.formation.is_apc():
response = jury_but_pv.pvjury_table_but(formsemestre_id, format="xls")
response = jury_but_pv.pvjury_page_but(formsemestre_id, fmt="xls")
data = response.get_data()
else: # formations classiques
data = sco_pvjury.formsemestre_pvjury(

View File

@ -228,7 +228,7 @@ def dict_pvjury(
'decisions_dict' : { etudid : decision (comme ci-dessus) },
}
"""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
nt: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
if etudids is None:
etudids = nt.get_etudids()
@ -510,7 +510,9 @@ def pvjury_table(
return lines, titles, columns_ids
def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
# XXX TODO cette page a vocation a disparaitre,
# remplacée par formsemestre_recapcomplet en mode jury (déjà le cas pour les BUT)
def formsemestre_pvjury(formsemestre_id, format="html", publish=True): # XXX
"""Page récapitulant les décisions de jury"""
formsemestre = FormSemestre.query.get_or_404(formsemestre_id)
is_apc = formsemestre.formation.is_apc()
@ -621,21 +623,21 @@ def formsemestre_pvjury(formsemestre_id, format="html", publish=True):
# ---------------------------------------------------------------------------
def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None):
def formsemestre_pvjury_pdf(formsemestre_id, group_ids: list[int] = None, etudid=None):
"""Generation PV jury en PDF: saisie des paramètres
Si etudid, PV pour un seul etudiant. Sinon, tout les inscrits au groupe indiqué.
"""
group_ids = group_ids or []
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
# Mise à jour des groupes d'étapes:
sco_groups.create_etapes_partition(formsemestre_id)
groups_infos = None
if etudid:
# PV pour ce seul étudiant:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
etuddescr = '<a class="discretelink" href="ficheEtud?etudid=%s">%s</a>' % (
etudid,
etud["nomprenom"],
)
etud = Identite.query.get_or_404(etudid)
etuddescr = f"""<a class="discretelink" href="{
url_for("notes.ficheEtud", scodoc_dept=g.scodoc_dept, etudid=etudid)
}">{etud.nomprenom}</a>"""
etudids = [etudid]
else:
etuddescr = ""
@ -650,18 +652,22 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None):
H = [
html_sco_header.html_sem_header(
"Edition du PV de jury %s" % etuddescr,
f"Édition du PV de jury {etuddescr}",
javascripts=sco_groups_view.JAVASCRIPTS,
cssstyles=sco_groups_view.CSSSTYLES,
init_qtip=True,
),
"""<p class="help">Utiliser cette page pour éditer des versions provisoires des PV.
<span class="fontred">Il est recommandé d'archiver les versions définitives: <a href="formsemestre_archive?formsemestre_id=%s">voir cette page</a></span>
</p>"""
% formsemestre_id,
f"""<div class="help">Utiliser cette page pour éditer des versions provisoires des PV.
<span class="fontred">Il est recommandé d'archiver les versions définitives:
<a class="stdlink" href="{url_for(
'notes.formsemestre_archive',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id)
}">voir cette page</a></span>
</div>""",
]
F = [
"""<p><em>Voir aussi si besoin les réglages sur la page "Paramétrage" (accessible à l'administrateur du département).</em>
"""<p><em>Voir aussi si besoin les réglages sur la page "Paramétrage"
(accessible à l'administrateur du département).</em>
</p>""",
html_sco_header.sco_footer(),
]
@ -692,7 +698,11 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None):
return "\n".join(H) + "\n" + tf[1] + "\n".join(F)
elif tf[0] == -1:
return flask.redirect(
"formsemestre_pvjury?formsemestre_id=%s" % (formsemestre_id)
url_for(
"notes.formsemestre_pvjury",
scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id,
)
)
else:
# submit
@ -721,12 +731,12 @@ def formsemestre_pvjury_pdf(formsemestre_id, group_ids=[], etudid=None):
finally:
PDFLOCK.release()
sem = sco_formsemestre.get_formsemestre(formsemestre_id)
dt = time.strftime("%Y-%m-%d")
date_iso = time.strftime("%Y-%m-%d")
if groups_infos:
groups_filename = "-" + groups_infos.groups_filename
else:
groups_filename = ""
filename = "PV-%s%s-%s.pdf" % (sem["titre_num"], groups_filename, dt)
filename = f"""PV-{sem["titre_num"]}{groups_filename}-{date_iso}.pdf"""
return scu.sendPDFFile(pdfdoc, filename)

View File

@ -679,10 +679,10 @@ def pvjury_pdf(
dpv,
only_diplome=False,
date_commission=date_commission,
numeroArrete=numeroArrete,
VDICode=VDICode,
numero_arrete=numeroArrete,
code_vdi=VDICode,
date_jury=date_jury,
showTitle=showTitle,
show_title=showTitle,
pv_title=pv_title,
with_paragraph_nom=with_paragraph_nom,
anonymous=anonymous,
@ -690,7 +690,8 @@ def pvjury_pdf(
jury_de_diplome = not dpv["semestre_non_terminal"]
# Si Jury de passage et qu'un étudiant valide le parcours (car il a validé antérieurement le dernier semestre)
# Si Jury de passage et qu'un étudiant valide le parcours
# (car il a validé antérieurement le dernier semestre)
# alors on génère aussi un PV de diplome (à la suite dans le même doc PDF)
if not jury_de_diplome:
validations_parcours = [x["validation_parcours"] for x in dpv["decisions"]]
@ -702,9 +703,9 @@ def pvjury_pdf(
only_diplome=True,
date_commission=date_commission,
date_jury=date_jury,
numeroArrete=numeroArrete,
VDICode=VDICode,
showTitle=showTitle,
numero_arrete=numeroArrete,
code_vdi=VDICode,
show_title=showTitle,
pv_title=pv_title,
with_paragraph_nom=with_paragraph_nom,
anonymous=anonymous,
@ -729,14 +730,39 @@ def pvjury_pdf(
return data
def _make_pv_styles(formsemestre: FormSemestre):
style = reportlab.lib.styles.ParagraphStyle({})
style.fontSize = 12
style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre.id)
style.leading = 18
style.alignment = TA_JUSTIFY
indent = 1 * cm
style_bullet = reportlab.lib.styles.ParagraphStyle({})
style_bullet.fontSize = 12
style_bullet.fontName = sco_preferences.get_preference(
"PV_FONTNAME", formsemestre.id
)
style_bullet.leading = 12
style_bullet.alignment = TA_JUSTIFY
style_bullet.firstLineIndent = 0
style_bullet.leftIndent = indent
style_bullet.bulletIndent = indent
style_bullet.bulletFontName = "Times-Roman"
style_bullet.bulletFontSize = 11
style_bullet.spaceBefore = 5 * mm
style_bullet.spaceAfter = 5 * mm
return style, style_bullet
def _pvjury_pdf_type(
dpv,
only_diplome=False,
date_commission=None,
date_jury=None,
numeroArrete=None,
VDICode=None,
showTitle=False,
numero_arrete=None,
code_vdi=None,
show_title=False,
pv_title=None,
anonymous=False,
with_paragraph_nom=False,
@ -745,6 +771,7 @@ def _pvjury_pdf_type(
dpv: result of dict_pvjury
"""
from app.scodoc import sco_pvjury
from app.but import jury_but_pv
# Jury de diplome si sem. terminal OU que l'on demande les diplomés d'un semestre antérieur
diplome = (not dpv["semestre_non_terminal"]) or only_diplome
@ -756,91 +783,71 @@ def _pvjury_pdf_type(
titre_diplome = pv_title or dpv["formation"]["titre_officiel"]
objects = []
style = reportlab.lib.styles.ParagraphStyle({})
style.fontSize = 12
style.fontName = sco_preferences.get_preference("PV_FONTNAME", formsemestre_id)
style.leading = 18
style.alignment = TA_JUSTIFY
indent = 1 * cm
bulletStyle = reportlab.lib.styles.ParagraphStyle({})
bulletStyle.fontSize = 12
bulletStyle.fontName = sco_preferences.get_preference(
"PV_FONTNAME", formsemestre_id
)
bulletStyle.leading = 12
bulletStyle.alignment = TA_JUSTIFY
bulletStyle.firstLineIndent = 0
bulletStyle.leftIndent = indent
bulletStyle.bulletIndent = indent
bulletStyle.bulletFontName = "Times-Roman"
bulletStyle.bulletFontSize = 11
bulletStyle.spaceBefore = 5 * mm
bulletStyle.spaceAfter = 5 * mm
style, style_bullet = _make_pv_styles(formsemestre)
objects += [Spacer(0, 5 * mm)]
objects += sco_pdf.make_paras(
"""
<para align="center"><b>Procès-verbal de %s du département %s - Session unique %s</b></para>
"""
% (
titre_jury,
sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)",
sem["anneescolaire"],
),
f"""
<para align="center"><b>Procès-verbal de {titre_jury} du département {
sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)"
} - Session unique {sem["anneescolaire"]}</b></para>
""",
style,
)
objects += sco_pdf.make_paras(
"""
<para align="center"><b><i>%s</i></b></para>
"""
% titre_diplome,
f"""<para align="center"><b><i>{titre_diplome}</i></b></para>""",
style,
)
if showTitle:
if show_title:
objects += sco_pdf.make_paras(
"""<para align="center"><b>Semestre: %s</b></para>""" % sem["titre"], style
f"""<para align="center"><b>Semestre: {formsemestre.titre}</b></para>""",
style,
)
if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre_id):
objects += sco_pdf.make_paras(
"""<para align="center">VDI et Code: %s</para>""" % (VDICode or ""), style
f"""<para align="center">VDI et Code: {(code_vdi or "")}</para>""", style
)
if date_jury:
objects += sco_pdf.make_paras(
"""<para align="center">Jury tenu le %s</para>""" % date_jury, style
f"""<para align="center">Jury tenu le {date_jury}</para>""", style
)
objects += sco_pdf.make_paras(
"<para>"
+ (sco_preferences.get_preference("PV_INTRO", formsemestre_id) or "")
% {
"Decnum": numeroArrete,
"VDICode": VDICode,
"Decnum": numero_arrete,
"VDICode": code_vdi,
"UnivName": sco_preferences.get_preference("UnivName", formsemestre_id),
"Type": titre_jury,
"Date": date_commission, # deprecated
"date_commission": date_commission,
}
+ "</para>",
bulletStyle,
style_bullet,
)
objects += sco_pdf.make_paras(
"""<para>Le jury propose les décisions suivantes :</para>""", style
)
objects += [Spacer(0, 4 * mm)]
lines, titles, columns_ids = sco_pvjury.pvjury_table(
dpv,
only_diplome=only_diplome,
anonymous=anonymous,
with_paragraph_nom=with_paragraph_nom,
)
if formsemestre.formation.is_apc():
rows, titles = jury_but_pv.pvjury_table_but(formsemestre)
columns_ids = list(titles.keys())
else:
rows, titles, columns_ids = sco_pvjury.pvjury_table(
dpv,
only_diplome=only_diplome,
anonymous=anonymous,
with_paragraph_nom=with_paragraph_nom,
)
# convert to lists of tuples:
columns_ids = ["etudid"] + columns_ids
lines = [[line.get(x, "") for x in columns_ids] for line in lines]
rows = [[line.get(x, "") for x in columns_ids] for line in rows]
titles = [titles.get(x, "") for x in columns_ids]
# Make a new cell style and put all cells in paragraphs
cell_style = styles.ParagraphStyle({})
@ -863,7 +870,7 @@ def _pvjury_pdf_type(
("GRID", (0, 0), (-1, -1), LINEWIDTH, Color(0, 0, 0)),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]
titles = ["<para><b>%s</b></para>" % x for x in titles]
titles = [f"<para><b>{x}</b></para>" for x in titles]
def _format_pv_cell(x):
"""convert string to paragraph"""
@ -872,22 +879,31 @@ def _pvjury_pdf_type(
else:
return x
Pt = [[_format_pv_cell(x) for x in line[1:]] for line in ([titles] + lines)]
widths = [6 * cm, 2.8 * cm, 2.8 * cm, None, None, None, None]
if dpv["has_prev"]:
widths[2:2] = [2.8 * cm]
if sco_preferences.get_preference("bul_show_mention", formsemestre_id):
widths += [None]
objects.append(Table(Pt, repeatRows=1, colWidths=widths, style=table_style))
widths_by_id = {
"nom": 5 * cm,
"cursus": 2.8 * cm,
"ects": 1.4 * cm,
"devenir": 1.8 * cm,
"decision_but": 1.8 * cm,
}
table_cells = [[_format_pv_cell(x) for x in line[1:]] for line in ([titles] + rows)]
widths = [widths_by_id.get(col_id) for col_id in columns_ids[1:]]
# if dpv["has_prev"]:
# widths[2:2] = [2.8 * cm]
# if sco_preferences.get_preference("bul_show_mention", formsemestre_id):
# widths += [None]
objects.append(
Table(table_cells, repeatRows=1, colWidths=widths, style=table_style)
)
# Signature du directeur
objects += sco_pdf.make_paras(
"""<para spaceBefore="10mm" align="right">
%s, %s</para>"""
% (
sco_preferences.get_preference("DirectorName", formsemestre_id) or "",
sco_preferences.get_preference("DirectorTitle", formsemestre_id) or "",
),
f"""<para spaceBefore="10mm" align="right">{
sco_preferences.get_preference("DirectorName", formsemestre_id) or ""
}, {
sco_preferences.get_preference("DirectorTitle", formsemestre_id) or ""
}</para>""",
style,
)

View File

@ -166,13 +166,14 @@ def formsemestre_recapcomplet(
if len(formsemestre.inscriptions) > 0:
H.append("""<div class="links_under_recap"><ul>""")
H.append(
f"""<li><a class="stdlink" href="{url_for('notes.formsemestre_recapcomplet',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
}">Décisions du jury</a>
</li>
"""
)
if not mode_jury:
H.append(
f"""<li><a class="stdlink" href="{url_for('notes.formsemestre_recapcomplet',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
}">Décisions du jury</a>
</li>
"""
)
if formsemestre.can_edit_jury():
if mode_jury:
H.append(
@ -186,6 +187,22 @@ def formsemestre_recapcomplet(
</li>
"""
)
if mode_jury:
H.append(
f"""<li><a class="stdlink" href="{
url_for('notes.formsemestre_lettres_individuelles',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
}">Courriers individuels (classeur pdf)</a>
</li>
"""
)
H.append(
f"""<li><a class="stdlink" href="{url_for('notes.formsemestre_pvjury_pdf',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_id, mode_jury=1)
}">PV officiel (pdf)</a>
</li>
"""
)
H.append("</ul></div>")
if sco_preferences.get_preference("use_ue_coefs", formsemestre_id):

View File

@ -2805,7 +2805,7 @@ 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)
sco_publish("/pvjury_page_but", jury_but_pv.pvjury_page_but, Permission.ScoView)
@bp.route("/formsemestre_saisie_jury")