############################################################################## # 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, ) from app.models.config import ScoDocSiteConfig from app.scodoc import html_sco_header 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 niveau in deca.niveaux_competences: H.append( f"""
{niveau.competence.titre}
""" ) dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id) # peut être None ues = [ ue for ue in deca.ues_impair if ue.niveau_competence and ue.niveau_competence.id == niveau.id ] ue_impair = ues[0] if ues else None ues = [ ue for ue in deca.ues_pair if ue.niveau_competence and ue.niveau_competence.id == niveau.id ] ue_pair = ues[0] if ues else None # Les UEs à afficher, # qui seront toujours en readonly sur le formsemestre de l'année précédente du redoublant ues_ro = [ ( ue_impair, (deca.a_cheval and deca.formsemestre_id != deca.formsemestre_impair.id), ), ( ue_pair, deca.a_cheval and deca.formsemestre_id != deca.formsemestre_pair.id, ), ] # 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 = {}, ) -> str: "Le menu html select avec les codes" # if disabled: # mauvaise idée car le disabled est traité en JS # return f"""
{code_valide}
""" 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) + "") }
""" else: moy_ue_str = f"""{scu.fmt_note(dec_ue.moy_ue)}""" if dec_ue.code_valide: scoplement = f"""
Code {dec_ue.code_valide} enregistré le {dec_ue.validation.event_date.strftime("%d/%m/%Y")} à {dec_ue.validation.event_date.strftime("%Hh%M")}
""" else: scoplement = "" 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: return """
Pas de RCUE (UE non capitalisée ?)
""" scoplement = ( f"""
{ dec_rcue.validation.to_html() }
""" if dec_rcue.validation else "" ) # 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, dec_rcue.code_valide, disabled=True, klass="manual code_rcue", data = { "niveau_id" : str(niveau.id)} )}
""" 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_parcours().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.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" ) 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_parcours().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 = Identite.query.get_or_404(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 ""