############################################################################## # ScoDoc # Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved. # See LICENSE ############################################################################## """Jury BUT: affichage/formulaire """ import re import numpy as np import flask from flask import flash, render_template, url_for from flask import g, request from app import db from app.but import jury_but from app.but.jury_but import ( DecisionsProposeesAnnee, DecisionsProposeesRCUE, DecisionsProposeesUE, ) from app.comp import res_sem from app.comp.res_but import ResultatsSemestreBUT from app.models import ( ApcNiveau, FormSemestre, FormSemestreInscription, Identite, UniteEns, ScolarAutorisationInscription, ScolarFormSemestreValidation, ScolarNews, ) from app.models.config import ScoDocSiteConfig from app.scodoc import html_sco_header from app.scodoc import codes_cursus as sco_codes from app.scodoc.sco_exceptions import ScoValueError from app.scodoc import sco_preferences from app.scodoc import sco_utils as scu def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: """Affichage des décisions annuelles BUT Si pas read_only, menus sélection codes jury. """ H = [] if deca.jury_annuel: H.append( f"""
Décision de jury pour l'année : { _gen_but_select("code_annee", deca.codes, deca.code_valide, disabled=True, klass="manual") } ({deca.code_valide or 'non'} enregistrée)
""" ) formsemestre_1 = deca.formsemestre_impair formsemestre_2 = deca.formsemestre_pair # Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval): reverse_semestre = ( deca.formsemestre_pair and deca.formsemestre_impair and deca.formsemestre_pair.date_debut < deca.formsemestre_impair.date_debut ) if reverse_semestre: formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1 H.append( f"""
Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}
{deca.explanation}
{"S" +str(formsemestre_1.semestre_id) if formsemestre_1 else "-"} {formsemestre_1.annee_scolaire_str() if formsemestre_1 else ""}
{"S"+str(formsemestre_2.semestre_id) if formsemestre_2 else "-"} {formsemestre_2.annee_scolaire_str() if formsemestre_2 else ""}
RCUE
""" ) for dec_rcue in deca.get_decisions_rcues_annee(): rcue = dec_rcue.rcue niveau = rcue.niveau H.append( f"""
{niveau.competence.titre}
""" ) ue_impair, ue_pair = rcue.ue_1, rcue.ue_2 # Les UEs à afficher, # qui ues_ro = [ ( ue_impair, rcue.ue_cur_impair is None, ), ( ue_pair, rcue.ue_cur_pair is None, ), ] # Ordonne selon les dates des 2 semestres considérés: if reverse_semestre: ues_ro[0], ues_ro[1] = ues_ro[1], ues_ro[0] # Colonnes d'UE: for ue, ue_read_only in ues_ro: if ue: H.append( _gen_but_niveau_ue( ue, deca.decisions_ues[ue.id], disabled=read_only or ue_read_only, annee_prec=ue_read_only, niveau_id=ue.niveau_competence.id, ) ) else: H.append("""
""") # Colonne RCUE H.append(_gen_but_rcue(dec_rcue, niveau)) H.append("
") # but_annee return "\n".join(H) def _gen_but_select( name: str, codes: list[str], code_valide: str, disabled: bool = False, klass: str = "", data: dict = None, code_valide_label: str = "", ) -> str: "Le menu html select avec les codes" # if disabled: # mauvaise idée car le disabled est traité en JS # return f"""
{code_valide}
""" data = data or {} options_htm = "\n".join( [ f"""""" for code in codes ] ) return f""" """ def _gen_but_niveau_ue( ue: UniteEns, dec_ue: DecisionsProposeesUE, disabled: bool = False, annee_prec: bool = False, niveau_id: int = None, ) -> str: if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]: moy_ue_str = f"""{ scu.fmt_note(dec_ue.moy_ue_with_cap)}""" scoplement = f"""
UE {ue.acronyme} capitalisée le {dec_ue.ue_status["event_date"].strftime("%d/%m/%Y")}
UE en cours { "sans notes" if np.isnan(dec_ue.moy_ue) else ("avec moyenne " + scu.fmt_note(dec_ue.moy_ue) + "") }
""" elif dec_ue.formsemestre is None: # Validation d'UE antérieure (semestre hors année scolaire courante) if dec_ue.validation: moy_ue_str = f"""{scu.fmt_note(dec_ue.validation.moy_ue)}""" scoplement = f"""
UE {ue.acronyme} antérieure validée {dec_ue.validation.code} le {dec_ue.validation.event_date.strftime("%d/%m/%Y")}
Non reprise dans l'année en cours
""" else: moy_ue_str = """-""" scoplement = """
Pas d'UE en cours ou validée dans cette compétence de ce côté.
""" else: moy_ue_str = f"""{scu.fmt_note(dec_ue.moy_ue)}""" if dec_ue.code_valide: date_str = ( f"""enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")} à {dec_ue.validation.event_date.strftime("%Hh%M")} """ if dec_ue.validation and dec_ue.validation.event_date else "" ) scoplement = f"""
Code {dec_ue.code_valide} {date_str}
""" else: scoplement = "" ue_class = "" # 'recorded' if dec_ue.code_valide is not None else '' if dec_ue.code_valide is not None and dec_ue.codes: if dec_ue.code_valide == dec_ue.codes[0]: ue_class = "recorded" else: ue_class = "recorded_different" return f"""
{ue.acronyme}
{moy_ue_str}
{scoplement}
{ _gen_but_select("code_ue_"+str(ue.id), dec_ue.codes, dec_ue.code_valide, disabled=disabled, klass=f"code_ue ue_rcue_{niveau_id}" if not disabled else "" ) }
""" def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str: if dec_rcue is None or not dec_rcue.rcue.complete: return """
Pas de RCUE (UE non capitalisée ?)
""" code_propose_menu = dec_rcue.code_valide # le code enregistré code_valide_label = code_propose_menu if dec_rcue.validation: if dec_rcue.code_valide == dec_rcue.codes[0]: descr_validation = dec_rcue.validation.html() else: # on une validation enregistrée différence de celle proposée descr_validation = f"""Décision recommandée: {dec_rcue.codes[0]}. Il y avait {dec_rcue.validation.html()}""" if ( sco_codes.BUT_CODES_ORDER[dec_rcue.codes[0]] > sco_codes.BUT_CODES_ORDER[dec_rcue.code_valide] ): code_propose_menu = dec_rcue.codes[0] code_valide_label = ( f"{dec_rcue.codes[0]} (actuel {dec_rcue.code_valide})" ) scoplement = f"""
{descr_validation}
""" else: scoplement = "" # "pas de validation" # Déjà enregistré ? niveau_rcue_class = "" if dec_rcue.code_valide is not None and dec_rcue.codes: if dec_rcue.code_valide == dec_rcue.codes[0]: niveau_rcue_class = "recorded" else: niveau_rcue_class = "recorded_different" return f"""
{scu.fmt_note(dec_rcue.rcue.moy_rcue)}
{scoplement}
{_gen_but_select("code_rcue_"+str(niveau.id), dec_rcue.codes, code_propose_menu, disabled=True, klass="manual code_rcue", data = { "niveau_id" : str(niveau.id)}, code_valide_label = code_valide_label, )}
""" def jury_but_semestriel( formsemestre: FormSemestre, etud: Identite, read_only: bool, navigation_div: str = "", ) -> str: """Page: formulaire saisie décision d'UE d'un semestre BUT isolé (pas jury annuel).""" res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre) parcour, ues = jury_but.list_ue_parcour_etud(formsemestre, etud, res) inscription_etat = etud.inscription_etat(formsemestre.id) semestre_terminal = ( formsemestre.semestre_id >= formsemestre.formation.get_cursus().NB_SEM ) autorisations_passage = ScolarAutorisationInscription.query.filter_by( etudid=etud.id, origin_formsemestre_id=formsemestre.id, ).all() # Par défaut: autorisé à passer dans le semestre suivant si sem. impair, # ou si décision déjà enregistrée: est_autorise_a_passer = (formsemestre.semestre_id % 2) or ( formsemestre.semestre_id + 1 ) in (a.semestre_id for a in autorisations_passage) decisions_ues = { ue.id: DecisionsProposeesUE(etud, formsemestre, ue, inscription_etat) for ue in ues } for dec_ue in decisions_ues.values(): dec_ue.compute_codes() if request.method == "POST": if not read_only: for key in request.form: code = request.form[key] # Codes d'UE code_match = re.match(r"^code_ue_(\d+)$", key) if code_match: ue_id = int(code_match.group(1)) dec_ue = decisions_ues.get(ue_id) if not dec_ue: raise ScoValueError(f"UE invalide ue_id={ue_id}") dec_ue.record(code) db.session.commit() flash("codes enregistrés") if not semestre_terminal: if request.form.get("autorisation_passage"): if not formsemestre.semestre_id + 1 in ( a.semestre_id for a in autorisations_passage ): ScolarAutorisationInscription.delete_autorisation_etud( etud.id, formsemestre.id ) ScolarAutorisationInscription.autorise_etud( etud.id, formsemestre.formation.formation_code, formsemestre.id, formsemestre.semestre_id + 1, ) db.session.commit() flash( f"""autorisation de passage en S{formsemestre.semestre_id + 1 } enregistrée""" ) else: if est_autorise_a_passer: ScolarAutorisationInscription.delete_autorisation_etud( etud.id, formsemestre.id ) db.session.commit() flash( f"autorisation de passage en S{formsemestre.semestre_id + 1} annulée" ) ScolarNews.add( typ=ScolarNews.NEWS_JURY, obj=formsemestre.id, text=f"""Saisie décision jury dans {formsemestre.html_link_status()}""", url=url_for( "notes.formsemestre_status", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, ), ) return flask.redirect( url_for( "notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, etudid=etud.id, ) ) # GET if formsemestre.semestre_id % 2 == 0: warning = f"""
Cet étudiant de S{formsemestre.semestre_id} ne peut pas passer en jury BUT annuel car il lui manque le semestre précédent.
""" else: warning = "" H = [ html_sco_header.sco_header( page_title=f"Validation BUT S{formsemestre.semestre_id}", formsemestre_id=formsemestre.id, etudid=etud.id, cssstyles=("css/jury_but.css",), javascripts=("js/jury_but.js",), ), f"""
Jury BUT S{formsemestre.id} - Parcours {(parcour.libelle if parcour else False) or "non spécifié"}
{etud.nomprenom}

Jury sur un semestre BUT isolé (ne concerne que les UEs)

{warning}
""", ] erase_span = "" if not read_only: # Requête toutes les validations (pas seulement celles du deca courant), # au cas où: changement d'architecture, saisie en mode classique, ... validations = ScolarFormSemestreValidation.query.filter_by( etudid=etud.id, formsemestre_id=formsemestre.id ).all() if validations: erase_span = f"""effacer les décisions enregistrées""" else: erase_span = ( "Cet étudiant n'a aucune décision enregistrée pour ce semestre." ) H.append( f"""
Unités d'enseignement de S{formsemestre.semestre_id}:
""" ) if not ues: H.append( """
Aucune UE ! Vérifiez votre programme de formation, et l'association UEs / Niveaux de compétences
""" ) else: H.append( """
""" ) for ue in ues: dec_ue = decisions_ues[ue.id] H.append("""
""") H.append( _gen_but_niveau_ue( ue, dec_ue, disabled=read_only, ) ) H.append( """
""" ) H.append("
") # but_annee div_autorisations_passage = ( f"""
Autorisé à passer en : { ", ".join( ["S" + str(a.semestre_id or '') for a in autorisations_passage ] )}
""" if autorisations_passage else """
pas d'autorisations de passage enregistrées.
""" ) H.append(div_autorisations_passage) if read_only: H.append( f"""
{"Vous n'avez pas la permission de modifier ces décisions." if formsemestre.etat else "Semestre verrouillé."} Les champs entourés en vert sont enregistrés.
""" ) else: if formsemestre.semestre_id < formsemestre.formation.get_cursus().NB_SEM: H.append( f"""
autoriser à passer dans le semestre S{formsemestre.semestre_id+1}
""" ) else: H.append("""
dernier semestre de la formation.
""") H.append( f"""
{erase_span}
""" ) H.append(navigation_div) H.append("
") H.append( render_template( "but/documentation_codes_jury.j2", nom_univ=f"""Export {sco_preferences.get_preference("InstituteName") or sco_preferences.get_preference("UnivName") or "Apogée"}""", codes=ScoDocSiteConfig.get_codes_apo_dict(), ) ) return "\n".join(H) # ------------- def infos_fiche_etud_html(etudid: int) -> str: """Section html pour fiche etudiant provisoire pour BUT 2022 """ etud = Identite.get_etud(etudid) inscriptions = ( FormSemestreInscription.query.join(FormSemestreInscription.formsemestre) .filter( FormSemestreInscription.etudid == etud.id, ) .order_by(FormSemestre.date_debut) ) formsemestres_but = [ i.formsemestre for i in inscriptions if i.formsemestre.formation.is_apc() ] if len(formsemestres_but) == 0: return "" # temporaire quick & dirty: affiche le dernier try: deca = DecisionsProposeesAnnee(etud, formsemestres_but[-1]) return f"""
{show_etud(deca, read_only=True)}
""" except ScoValueError: pass return ""