diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 69fefd51..42b65372 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -91,7 +91,7 @@ from app.models.ues import UniteEns from app.models.validations import ScolarFormSemestreValidation from app.scodoc import sco_cache from app.scodoc import sco_codes_parcours as sco_codes -from app.scodoc.sco_codes_parcours import RED, UE_STANDARD +from app.scodoc.sco_codes_parcours import CODES_UE_VALIDES, RED, UE_STANDARD from app.scodoc import sco_utils as scu from app.scodoc.sco_exceptions import ScoException, ScoValueError @@ -205,6 +205,8 @@ class DecisionsProposeesAnnee(DecisionsProposees): formsemestre: FormSemestre, ): super().__init__(etud=etud) + self.formsemestre = formsemestre + "le formsemestre utilisé pour construire ce deca" self.formsemestre_id = formsemestre.id "l'id du formsemestre utilisé pour construire ce deca" formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre) @@ -219,23 +221,34 @@ class DecisionsProposeesAnnee(DecisionsProposees): ) ) ) + # Si les années scolaires sont distinctes, on est "à cheval" + self.a_cheval = ( + formsemestre_impair + and formsemestre_pair + and formsemestre_impair.annee_scolaire() + != formsemestre_pair.annee_scolaire() + ) + "vrai si on groupe deux semestres d'années scolaires différentes" # 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)" self.formsemestre_impair = formsemestre_impair - "le 1er semestre de l'année scolaire considérée (S1, S3, S5)" + "le 1er semestre du groupement (S1, S3, S5)" self.formsemestre_pair = formsemestre_pair - "le second formsemestre de la même année scolaire (S2, S4, S6)" + "le second formsemestre (S2, S4, S6), de la même année scolaire ou d'une précédente" formsemestre_last = formsemestre_pair or formsemestre_impair - "le formsemestre le plus avancé dans cette année" + "le formsemestre le plus avancé (en indice de semestre) dans le groupement" self.annee_but = (formsemestre_last.semestre_id + 1) // 2 "le rang de l'année dans le BUT: 1, 2, 3" assert self.annee_but in (1, 2, 3) self.rcues_annee = [] - "RCUEs de l'année" + """RCUEs de l'année + (peuvent concerner l'année scolaire antérieur pour les redoublants + avec UE capitalisées) + """ self.inscription_etat = etud.inscription_etat(formsemestre_last.id) "état de l'inscription dans le semestre le plus avancé (pair si année complète)" self.inscription_etat_pair = ( @@ -526,9 +539,9 @@ class DecisionsProposeesAnnee(DecisionsProposees): def compute_rcues_annee(self) -> list[RegroupementCoherentUE]: """Liste des regroupements d'UE à considérer cette année. - Pour le moment on ne considère pas de RCUE à cheval sur plusieurs années (redoublants). + On peut avoir un RCUE à cheval sur plusieurs années (redoublants avec UE capitalisées). Si on n'a pas les deux semestres, aucun RCUE. - Raises ScoValueError s'il y a des UE sans RCUE. + Raises ScoValueError s'il y a des UE sans RCUE. <= ??? XXX """ if self.formsemestre_pair is None or self.formsemestre_impair is None: return [] @@ -537,6 +550,13 @@ class DecisionsProposeesAnnee(DecisionsProposees): for ue_pair in self.ues_pair: rcue = None for ue_impair in self.ues_impair: + if self.a_cheval: + # l'UE paire DOIT être capitalisée pour être utilisée + if ( + self.decisions_ues[ue_pair.id].code_valide + not in CODES_UE_VALIDES + ): + continue # ignore cette UE antérieure non capitalisée if ue_pair.niveau_competence_id == ue_impair.niveau_competence_id: rcue = RegroupementCoherentUE( self.etud, @@ -548,10 +568,12 @@ class DecisionsProposeesAnnee(DecisionsProposees): ) ues_impair_sans_rcue.discard(ue_impair.id) break - if rcue is None: + if rcue is None and not self.a_cheval: raise NoRCUEError(deca=self, ue=ue_pair) - rcues_annee.append(rcue) - if len(ues_impair_sans_rcue) > 0: + if rcue is not None: + rcues_annee.append(rcue) + # Si jury annuel (pas à cheval), on doit avoir tous les RCUEs: + if len(ues_impair_sans_rcue) > 0 and not self.a_cheval: ue = UniteEns.query.get(ues_impair_sans_rcue.pop()) raise NoRCUEError(deca=self, ue=ue) return rcues_annee @@ -1026,6 +1048,10 @@ class DecisionsProposeesUE(DecisionsProposees): self.moy_ue_with_cap = ue_status["moy"] self.ue_status = ue_status + def __repr__(self) -> str: + return f"""<{self.__class__.__name__} ue={self.ue.acronyme} valid={self.code_valide + } codes={self.codes} explanation={self.explanation}""" + def set_rcue(self, rcue: RegroupementCoherentUE): """Rattache cette UE à un RCUE. Cela peut modifier les codes proposés (si compensation)""" diff --git a/app/but/jury_but_view.py b/app/but/jury_but_view.py index c8efb357..4cd6044f 100644 --- a/app/but/jury_but_view.py +++ b/app/but/jury_but_view.py @@ -72,16 +72,24 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str: else: avertissement_redoublement = "" + formsemestre_1 = deca.formsemestre_impair + formsemestre_2 = deca.formsemestre_pair + # Ordonne selon les dates des 2 semestres considérés (pour les redoublants à cheval): + if deca.formsemestre_pair.date_debut < deca.formsemestre_impair.date_debut: + formsemestre_1, formsemestre_2 = formsemestre_2, formsemestre_1 H.append( f"""