diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 913297cc1..401f8993c 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -206,6 +206,7 @@ class DecisionsProposeesAnnee(DecisionsProposees): ): super().__init__(etud=etud) self.formsemestre_id = formsemestre.id + "l'id du formsemestre utilisé pour construire ce deca" formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre) assert ( (formsemestre_pair is None) @@ -461,22 +462,9 @@ class DecisionsProposeesAnnee(DecisionsProposees): if (formsemestre is None) or (not formsemestre.formation.is_apc()): ues = [] else: - formation: Formation = formsemestre.formation - # Parcour dans lequel l'étudiant est inscrit, et liste des UEs - if res.etuds_parcour_id[etudid] is None: - # pas de parcour: prend toutes les UEs (non bonus) - ues = [ue for ue in res.etud_ues(etudid) if ue.type == UE_STANDARD] - ues.sort(key=lambda u: u.numero) - else: - parcour = ApcParcours.query.get(res.etuds_parcour_id[etudid]) - if parcour is not None: - self.parcour = parcour - ues = ( - formation.query_ues_parcour(parcour) - .filter_by(semestre_idx=formsemestre.semestre_id) - .order_by(UniteEns.numero) - .all() - ) + parcour, ues = list_ue_parcour_etud(formsemestre, self.etud, res) + if parcour is not None: + self.parcour = parcour ues_sems.append(ues) return ues_sems @@ -689,30 +677,40 @@ class DecisionsProposeesAnnee(DecisionsProposees): # s'il n'y a pas de code, efface dec.record(code, no_overwrite=True) - def erase(self): + def erase(self, only_one_sem=False): """Efface les décisions de jury de cet étudiant pour cette année: décisions d'UE, de RCUE, d'année, et autorisations d'inscription émises. """ - for dec_ue in self.decisions_ues.values(): - dec_ue.erase() - for dec_rcue in self.decisions_rcue_by_niveau.values(): - dec_rcue.erase() - if self.formsemestre_impair: + if only_one_sem: + # N'efface que les autorisations venant de ce semestre, + # et les validations de ses UEs ScolarAutorisationInscription.delete_autorisation_etud( - self.etud.id, self.formsemestre_impair.id + self.etud.id, self.formsemestre_id ) - if self.formsemestre_pair: - ScolarAutorisationInscription.delete_autorisation_etud( - self.etud.id, self.formsemestre_pair.id + for dec_ue in self.decisions_ues.values(): + if dec_ue.formsemestre.id == self.formsemestre_id: + dec_ue.erase() + else: + for dec_ue in self.decisions_ues.values(): + dec_ue.erase() + for dec_rcue in self.decisions_rcue_by_niveau.values(): + dec_rcue.erase() + if self.formsemestre_impair: + ScolarAutorisationInscription.delete_autorisation_etud( + self.etud.id, self.formsemestre_impair.id + ) + if self.formsemestre_pair: + ScolarAutorisationInscription.delete_autorisation_etud( + self.etud.id, self.formsemestre_pair.id + ) + validations = ApcValidationAnnee.query.filter_by( + etudid=self.etud.id, + formsemestre_id=self.formsemestre_impair.id, + ordre=self.annee_but, ) - validations = ApcValidationAnnee.query.filter_by( - etudid=self.etud.id, - formsemestre_id=self.formsemestre_impair.id, - ordre=self.annee_but, - ) - for validation in validations: - db.session.delete(validation) + for validation in validations: + db.session.delete(validation) db.session.flush() self.invalidate_formsemestre_cache() @@ -757,6 +755,26 @@ class DecisionsProposeesAnnee(DecisionsProposees): return line_sep.join(validations) +def list_ue_parcour_etud( + formsemestre: FormSemestre, etud: Identite, res: ResultatsSemestreBUT +) -> tuple[ApcParcours, list[UniteEns]]: + """Parcour dans lequel l'étudiant est inscrit, et liste des UEs pour ce semestre""" + if res.etuds_parcour_id[etud.id] is None: + parcour = None + # pas de parcour: prend toutes les UEs (non bonus) + ues = [ue for ue in res.etud_ues(etud.id) if ue.type == UE_STANDARD] + ues.sort(key=lambda u: u.numero) + else: + parcour = ApcParcours.query.get(res.etuds_parcour_id[etud.id]) + ues = ( + formsemestre.formation.query_ues_parcour(parcour) + .filter_by(semestre_idx=formsemestre.semestre_id) + .order_by(UniteEns.numero) + .all() + ) + return parcour, ues + + class DecisionsProposeesRCUE(DecisionsProposees): """Liste des codes de décisions que l'on peut proposer pour le RCUE de cet étudiant dans cette année. @@ -995,8 +1013,8 @@ class DecisionsProposeesUE(DecisionsProposees): etudid=self.etud.id, msg=f"Validation UE {self.ue.id}", ) - log(f"DecisionsProposeesUE: recording {self.validation}") db.session.add(self.validation) + log(f"DecisionsProposeesUE: recording {self.validation}") sco_cache.invalidate_formsemestre(formsemestre_id=self.formsemestre.id) self.recorded = True diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index fa5171200..1aa72d1e9 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -6,13 +6,28 @@ """Jury BUT: affichage/formulaire """ -from flask import g, url_for -from app.models.etudiants import Identite -from app.scodoc import sco_utils as scu +import re + +import flask +from flask import flash, url_for +from flask import g, request + +from app import db +from app.but import jury_but from app.but.jury_but import DecisionsProposeesAnnee, DecisionsProposeesUE -from app.models import FormSemestre, FormSemestreInscription, UniteEns +from app.comp import res_sem +from app.comp.res_but import ResultatsSemestreBUT +from app.models import ( + FormSemestre, + FormSemestreInscription, + Identite, + UniteEns, + ScolarAutorisationInscription, +) +from app.scodoc import html_sco_header from app.scodoc.sco_exceptions import ScoValueError +from app.scodoc import sco_utils as scu def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: @@ -29,17 +44,19 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: erase_span = "" 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") - } - ({'non ' if deca.code_valide is None else ''}enregistrée) - {erase_span} + f""" +
+
+ Décision de jury pour l'année : { + _gen_but_select("code_annee", deca.codes, deca.code_valide, + disabled=True, klass="manual") + } + ({'non ' if deca.code_valide is None else ''}enregistrée) + {erase_span} +
+
{deca.explanation}
-
{deca.explanation}
-
""" + """ ) H.append( @@ -140,7 +157,181 @@ def _gen_but_niveau_ue(
""" -# +def jury_but_semestriel( + formsemestre: FormSemestre, etud: Identite, read_only: bool +) -> str: + """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 + ) + est_autorise_a_passer = (formsemestre.semestre_id + 1) in ( + a.semestre_id + for a in ScolarAutorisationInscription.query.filter_by( + etudid=etud.id, + origin_formsemestre_id=formsemestre.id, + ) + ) + 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 + m = re.match(r"^code_ue_(\d+)$", key) + if m: + ue_id = int(m.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 est_autorise_a_passer: + 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="Validation BUT", + 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é

+ {warning} +
+ +
+ """, + ] + if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]): + erase_span = f"""effacer décisions""" + else: + erase_span = "aucune décision enregistrée pour ce semestre" + + H.append( + f""" +
+ {erase_span} +
+
Unités d'enseignement de S{formsemestre.semestre_id}:
+
+
+
+
+
+ """ + ) + for ue in ues: + dec_ue = decisions_ues[ue.id] + H.append("""
""") + H.append( + _gen_but_niveau_ue( + ue, + dec_ue.moy_ue, + dec_ue, + disabled=read_only, + ) + ) + H.append( + """
+
""" + ) + H.append("
") # but_annee + + if read_only: + H.append( + """
+ Vous n'avez pas la permission de modifier ces décisions. + 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( + """ +
+ +
+ """ + ) + return "\n".join(H) + + +# ------------- def infos_fiche_etud_html(etudid: int) -> str: """Section html pour fiche etudiant provisoire pour BUT 2022 diff --git a/app/models/etudiants.py b/app/models/etudiants.py index 44879c64f..5a88d3fd7 100644 --- a/app/models/etudiants.py +++ b/app/models/etudiants.py @@ -311,7 +311,7 @@ class Identite(db.Model): "situation": situation, } - def inscription_etat(self, formsemestre_id): + def inscription_etat(self, formsemestre_id: int) -> str: """État de l'inscription de cet étudiant au semestre: False si pas inscrit, ou scu.INSCRIT, DEMISSION, DEF """ diff --git a/app/scodoc/sco_groups.py b/app/scodoc/sco_groups.py index eec920abe..ffee6a7e6 100644 --- a/app/scodoc/sco_groups.py +++ b/app/scodoc/sco_groups.py @@ -336,10 +336,10 @@ def get_group_infos(group_id, etat=None): # was _getlisteetud for etud in members: # long: comment eviter ces boucles ? etud_add_group_infos(etud, sem["formsemestre_id"]) - if group["group_name"] != None: - group_tit = "%s %s" % (group["partition_name"], group["group_name"]) - else: + if group["partition_name"] is None: group_tit = "tous" + else: + group_tit = f"""{group["partition_name"]} {group["group_name"]}""" return members, group, group_tit, sem, nbdem diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index eaf9ff0f9..ad59d6741 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -361,7 +361,7 @@ class DisplayedGroupsInfos(object): self.formsemestre = sem self.members.extend(group_members) groups_titles.append(group_tit) - if group["group_name"] == None: + if group["partition_name"] == None: self.tous_les_etuds_du_sem = True else: # liste les partitions explicitement sélectionnés (= des groupes de group_ids) @@ -416,7 +416,7 @@ class DisplayedGroupsInfos(object): """html hidden input with groups""" H = [] for group_id in self.group_ids: - H.append('' % group_id) + H.append(f'') return "\n".join(H) diff --git a/app/views/notes.py b/app/views/notes.py index cdd0dd997..e9dcfbe8b 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -2285,7 +2285,8 @@ def formsemestre_validation_but( deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) if len(deca.rcues_annee) == 0: - raise ScoValueError("année incomplète: pas de jury BUT annuel possible") + # raise ScoValueError("année incomplète: pas de jury BUT annuel possible") + return jury_but_view.jury_but_semestriel(formsemestre, etud, read_only) if request.method == "POST": if not read_only: deca.record_form(request.form) @@ -2322,7 +2323,7 @@ def formsemestre_validation_but(
{warning} - + """ ) @@ -2331,15 +2332,16 @@ def formsemestre_validation_but( if read_only: H.append( - """
Vous n'avez pas la permission de modifier ces décisions. + """
+ Vous n'avez pas la permission de modifier ces décisions. Les champs entourés en vert sont enregistrés.
""" ) else: H.append( f"""
- permettre la saisie manuelles des codes d'année et de niveaux. - Dans ce cas, il vous revient de vous assurer de la cohérence entre + permettre la saisie manuelles des codes d'année et de niveaux. + Dans ce cas, il vous revient de vous assurer de la cohérence entre vos codes d'UE/RCUE/Année !
@@ -2649,8 +2651,13 @@ def formsemestre_jury_but_recap(formsemestre_id: int, selected_etudid: int = Non ) @scodoc @permission_required(Permission.ScoView) -def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None): - """Supprime la décision de jury BUT pour cette année""" +def formsemestre_jury_but_erase( + formsemestre_id: int, etudid: int = None, only_one_sem=False +): + """Supprime la décision de jury BUT pour cette année. + Si only_one_sem, n'efface que pour le formsemestre indiqué, pas les deux de l'année. + """ + only_one_sem = int(request.args.get("only_one_sem") or False) formsemestre = FormSemestre.query.get_or_404(formsemestre_id) if not formsemestre.formation.is_apc(): raise ScoValueError("semestre non BUT") @@ -2665,16 +2672,23 @@ def formsemestre_jury_but_erase(formsemestre_id: int, etudid: int = None): ) if request.method == "POST": deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) - deca.erase() + deca.erase(only_one_sem=only_one_sem) db.session.commit() log(f"formsemestre_jury_but_erase({formsemestre_id}, {etudid})") - flash("décisions de jury effacées") + flash( + "décisions de jury du semestre effacées" + if only_one_sem + else "décisions de jury des semestres de l'année BUT effacées" + ) return redirect(dest_url) return render_template( "confirm_dialog.html", title=f"Effacer les validations de jury de {etud.nomprenom} ?", - explanation="""Les validations de toutes les UE, RCUE (compétences) et année seront effacées.""", + explanation=f"""Les validations d'UE et autorisations de passage + du semestre S{formsemestre.semestre_id} seront effacées.""" + if only_one_sem + else """Les validations de toutes les UE, RCUE (compétences) et année seront effacées.""", cancel_url=dest_url, )