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"""
Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}
-
S{deca.formsemestre_impair.semestre_id - if deca.formsemestre_impair else "-"}
-
S{deca.formsemestre_pair.semestre_id - if deca.formsemestre_pair else "-"} - {avertissement_redoublement}
+
S{formsemestre_1.semestre_id + if formsemestre_1 else "-"} + {formsemestre_1.annee_scolaire_str() if formsemestre_1 else ""} +
+
S{formsemestre_2.semestre_id + if formsemestre_2 else "-"} + {formsemestre_2.annee_scolaire_str() if formsemestre_2 else ""} +
RCUE
""" ) @@ -91,43 +99,54 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
{niveau.competence.titre}
""" ) - dec_rcue = deca.decisions_rcue_by_niveau.get(niveau.id) - if dec_rcue is None: + 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.id == niveau.id] + ue_impair = ues[0] if ues else None + ues = [ue for ue in deca.ues_pair if ue.niveau_competence.id == niveau.id] + ue_pair = ues[0] if ues else None + # Les UEs à afficher, 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 deca.formsemestre_pair.date_debut < deca.formsemestre_impair.date_debut: + ues_ro[0], ues_ro[1] = ues_ro[1], ues_ro[0] + # Colonnes d'UE: + for ue, ue_read_only in ues_ro: H.append( - """
""" - ) - continue - # Semestre impair - H.append( - _gen_but_niveau_ue( - dec_rcue.rcue.ue_1, - deca.decisions_ues[dec_rcue.rcue.ue_1.id], - disabled=read_only, - ) - ) - # Semestre pair - H.append( - _gen_but_niveau_ue( - dec_rcue.rcue.ue_2, - deca.decisions_ues[dec_rcue.rcue.ue_2.id], - disabled=read_only, - ) - ) - # RCUE - H.append( - f"""
-
{scu.fmt_note(dec_rcue.rcue.moy_rcue)}
-
{ - _gen_but_select("code_rcue_"+str(niveau.id), - dec_rcue.codes, - dec_rcue.code_valide, - disabled=True, klass="manual" + _gen_but_niveau_ue( + ue, + deca.decisions_ues[ue.id], + disabled=read_only or ue_read_only, + annee_prec=ue_read_only, ) - }
-
""" - ) + ) + + # RCUE + if dec_rcue is None: + H.append("""
""") + else: + H.append( + f"""
+
{scu.fmt_note(dec_rcue.rcue.moy_rcue)}
+
{ + _gen_but_select("code_rcue_"+str(niveau.id), + dec_rcue.codes, + dec_rcue.code_valide, + disabled=True, klass="manual" + ) + }
+
""" + ) H.append("") # but_annee return "\n".join(H) @@ -140,6 +159,8 @@ def _gen_but_select( klass: 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}
""" h = "\n".join( [ f"""