diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 244989edd..af1eed4ff 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -630,19 +630,38 @@ class DecisionsProposeesAnnee(DecisionsProposees): d[dec_rcue.rcue.ue_2.id] = dec_rcue return d - def next_annee_semestre_id(self, code: str) -> int: - """L'indice du semestre dans lequel l'étudiant est autorisé à - poursuivre l'année suivante. None si aucun.""" - if self.formsemestre_pair is None: - return None # seulement sur année - if code == RED: - return self.formsemestre_pair.semestre_id - 1 - elif ( - code in sco_codes.BUT_CODES_PASSAGE + 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. + """ + ids = set() + # La poursuite d'études dans un semestre pair d’une même année + # est de droit pour tout étudiant: + if (self.formsemestre.semestre_id % 2) and sco_codes.ParcoursBUT.NB_SEM: + ids.add(self.formsemestre.semestre_id + 1) + + # La poursuite d’études dans un semestre impair est possible si + # et seulement si l’étudiant a obtenu : + # - la moyenne à plus de la moitié des regroupements cohérents d’UE ; + # - et une moyenne égale ou supérieure à 8 sur 20 à chaque RCUE. + # + # La condition a paru trop stricte à de nombreux collègues. + # ScoDoc ne contraint donc pas à la respecter strictement. + # Si le code est dans BUT_CODES_PASSAGE (ADM, ADJ, PASD, PAS1NCI, ATJ), + # autorise à passer dans le semestre suivant + if ( + self.jury_annuel + and code in sco_codes.BUT_CODES_PASSAGE and self.formsemestre_pair.semestre_id < sco_codes.ParcoursBUT.NB_SEM ): - return self.formsemestre_pair.semestre_id + 1 - return None + ids.add(self.formsemestre.semestre_id + 1) + + if code == RED: + ids.add( + self.formsemestre.semestre_id - (self.formsemestre.semestre_id + 1) % 2 + ) + + return ids def record_form(self, form: dict): """Enregistre les codes de jury en base @@ -704,47 +723,43 @@ class DecisionsProposeesAnnee(DecisionsProposees): raise ScoValueError( f"code annee {html.escape(code)} invalide pour formsemestre {html.escape(self.formsemestre)}" ) - if code == self.code_valide or (self.code_valide is not None and no_overwrite): - self.recorded = True - return False # no change - if self.validation: - db.session.delete(self.validation) - db.session.commit() - if code is None: - self.validation = None - else: - self.validation = ApcValidationAnnee( - etudid=self.etud.id, - formsemestre=self.formsemestre_impair, - ordre=self.annee_but, - annee_scolaire=self.annee_scolaire(), - code=code, - ) - db.session.add(self.validation) - db.session.commit() - log(f"Recording {self}: {code}") - Scolog.logdb( - method="jury_but", - etudid=self.etud.id, - msg=f"Validation année BUT{self.annee_but}: {code}", - ) + + if code != self.code_valide and (self.code_valide is None or not no_overwrite): + # Enregistrement du code annuel BUT + if self.validation: + db.session.delete(self.validation) + db.session.commit() + if code is None: + self.validation = None + else: + self.validation = ApcValidationAnnee( + etudid=self.etud.id, + formsemestre=self.formsemestre_impair, + ordre=self.annee_but, + annee_scolaire=self.annee_scolaire(), + code=code, + ) + db.session.add(self.validation) + db.session.commit() + log(f"Recording {self}: {code}") + Scolog.logdb( + method="jury_but", + etudid=self.etud.id, + msg=f"Validation année BUT{self.annee_but}: {code}", + ) # --- Autorisation d'inscription dans semestre suivant ? - if self.formsemestre_pair is not None: - if code is None: - ScolarAutorisationInscription.delete_autorisation_etud( - etudid=self.etud.id, - origin_formsemestre_id=self.formsemestre_pair.id, - ) - else: - next_semestre_id = self.next_annee_semestre_id(code) - if next_semestre_id is not None: - ScolarAutorisationInscription.autorise_etud( - self.etud.id, - self.formsemestre_pair.formation.formation_code, - self.formsemestre_pair.id, - next_semestre_id, - ) + ScolarAutorisationInscription.delete_autorisation_etud( + etudid=self.etud.id, + origin_formsemestre_id=self.formsemestre.id, + ) + for next_semestre_id in self.next_semestre_ids(code): + ScolarAutorisationInscription.autorise_etud( + self.etud.id, + self.formsemestre.formation.formation_code, + self.formsemestre.id, + next_semestre_id, + ) db.session.commit() self.recorded = True @@ -872,18 +887,18 @@ class DecisionsProposeesAnnee(DecisionsProposees): self.invalidate_formsemestre_cache() def get_autorisations_passage(self) -> list[int]: - """Les liste des indices de semestres auxquels on est autorisé à - s'inscrire depuis cette année""" - formsemestre = self.formsemestre_pair or self.formsemestre_impair - if not formsemestre: - return [] - return [ - a.semestre_id - for a in ScolarAutorisationInscription.query.filter_by( - etudid=self.etud.id, - origin_formsemestre_id=formsemestre.id, - ) - ] + """Liste des indices de semestres auxquels on est autorisé à + s'inscrire depuis le semestre courant. + """ + return sorted( + [ + a.semestre_id + for a in ScolarAutorisationInscription.query.filter_by( + etudid=self.etud.id, + origin_formsemestre_id=self.formsemestre.id, + ) + ] + ) def descr_niveaux_validation(self, line_sep: str = "\n") -> str: """Description textuelle des niveaux validés (enregistrés) diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index acb6b695b..a58f41e2c 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -11,7 +11,7 @@ import re import numpy as np import flask -from flask import flash, url_for +from flask import flash, render_template, url_for from flask import g, request from app import db @@ -32,8 +32,10 @@ from app.models import ( 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 @@ -43,21 +45,20 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: """ H = [] - H.append("""