diff --git a/app/but/jury_but.py b/app/but/jury_but.py index e4bf39aa3a..c2143019a1 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -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""" diff --git a/app/but/jury_but_pv.py b/app/but/jury_but_pv.py index 208d1ef2c0..c01d4c5382 100644 --- a/app/but/jury_but_pv.py +++ b/app/but/jury_but_pv.py @@ -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 = "
" 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"""
{title} + + version excel
+ + """, + 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"""
{title} - - version excel
- - """, - 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 diff --git a/app/scodoc/sco_archives.py b/app/scodoc/sco_archives.py index 2fe04513dd..aa57823a20 100644 --- a/app/scodoc/sco_archives.py +++ b/app/scodoc/sco_archives.py @@ -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( diff --git a/app/scodoc/sco_pvjury.py b/app/scodoc/sco_pvjury.py index c8924119c6..d1bd0e0b55 100644 --- a/app/scodoc/sco_pvjury.py +++ b/app/scodoc/sco_pvjury.py @@ -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 = '%s' % ( - etudid, - etud["nomprenom"], - ) + etud = Identite.query.get_or_404(etudid) + etuddescr = f"""{etud.nomprenom}""" 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, ), - """

Utiliser cette page pour éditer des versions provisoires des PV. - Il est recommandé d'archiver les versions définitives: voir cette page -

""" - % formsemestre_id, + f"""
Utiliser cette page pour éditer des versions provisoires des PV. + Il est recommandé d'archiver les versions définitives: + voir cette page +
""", ] F = [ - """

Voir aussi si besoin les réglages sur la page "Paramétrage" (accessible à l'administrateur du département). + """

Voir aussi si besoin les réglages sur la page "Paramétrage" + (accessible à l'administrateur du département).

""", 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) diff --git a/app/scodoc/sco_pvpdf.py b/app/scodoc/sco_pvpdf.py index ed9cd7757a..eb0dfd4197 100644 --- a/app/scodoc/sco_pvpdf.py +++ b/app/scodoc/sco_pvpdf.py @@ -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( - """ - Procès-verbal de %s du département %s - Session unique %s - """ - % ( - titre_jury, - sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)", - sem["anneescolaire"], - ), + f""" + Procès-verbal de {titre_jury} du département { + sco_preferences.get_preference("DeptName", formsemestre_id) or "(sans nom)" + } - Session unique {sem["anneescolaire"]} + """, style, ) objects += sco_pdf.make_paras( - """ - %s - """ - % titre_diplome, + f"""{titre_diplome}""", style, ) - if showTitle: + if show_title: objects += sco_pdf.make_paras( - """Semestre: %s""" % sem["titre"], style + f"""Semestre: {formsemestre.titre}""", + style, ) if sco_preferences.get_preference("PV_TITLE_WITH_VDI", formsemestre_id): objects += sco_pdf.make_paras( - """VDI et Code: %s""" % (VDICode or ""), style + f"""VDI et Code: {(code_vdi or "")}""", style ) if date_jury: objects += sco_pdf.make_paras( - """Jury tenu le %s""" % date_jury, style + f"""Jury tenu le {date_jury}""", style ) objects += sco_pdf.make_paras( "" + (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, } + "", - bulletStyle, + style_bullet, ) objects += sco_pdf.make_paras( """Le jury propose les décisions suivantes :""", 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 = ["%s" % x for x in titles] + titles = [f"{x}" 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( - """ - %s, %s""" - % ( - sco_preferences.get_preference("DirectorName", formsemestre_id) or "", - sco_preferences.get_preference("DirectorTitle", formsemestre_id) or "", - ), + f"""{ + sco_preferences.get_preference("DirectorName", formsemestre_id) or "" + }, { + sco_preferences.get_preference("DirectorTitle", formsemestre_id) or "" + }""", style, ) diff --git a/app/scodoc/sco_recapcomplet.py b/app/scodoc/sco_recapcomplet.py index 1885049de9..58f01f6b15 100644 --- a/app/scodoc/sco_recapcomplet.py +++ b/app/scodoc/sco_recapcomplet.py @@ -166,13 +166,14 @@ def formsemestre_recapcomplet( if len(formsemestre.inscriptions) > 0: H.append("""") if sco_preferences.get_preference("use_ue_coefs", formsemestre_id): diff --git a/app/views/notes.py b/app/views/notes.py index c3e487b26e..85a53dc603 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -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")