diff --git a/app/but/cursus_but.py b/app/but/cursus_but.py index 622cdbb002..97a555ca60 100644 --- a/app/but/cursus_but.py +++ b/app/but/cursus_but.py @@ -102,7 +102,7 @@ class EtudCursusBUT: "Liste des inscriptions aux sem. de la formation, triées par indice et chronologie" self.parcour: ApcParcours = self.inscriptions[-1].parcour "Le parcours à valider: celui du DERNIER semestre suivi (peut être None)" - self.niveaux_by_annee = {} + self.niveaux_by_annee: dict[int, list[ApcNiveau]] = {} "{ annee:int : liste des niveaux à valider }" self.niveaux: dict[int, ApcNiveau] = {} "cache les niveaux" diff --git a/app/but/jury_but.py b/app/but/jury_but.py index 05978de2d8..954481ad09 100644 --- a/app/but/jury_but.py +++ b/app/but/jury_but.py @@ -342,21 +342,11 @@ class DecisionsProposeesAnnee(DecisionsProposees): # Cas particulier du passage en BUT 3: nécessité d'avoir validé toutes les UEs du BUT 1. if self.passage_de_droit and self.annee_but == 2: inscription = formsemestre.etuds_inscriptions.get(etud.id) - if inscription: - ues_but1_non_validees = cursus_but.etud_ues_de_but1_non_validees( - etud, self.formsemestre.formation, self.parcour - ) - self.passage_de_droit = not ues_but1_non_validees - explanation += ( - f"""UEs de BUT1 non validées: { - ', '.join(ue.acronyme for ue in ues_but1_non_validees) - }. """ - if ues_but1_non_validees - else "" - ) - else: + if not inscription or inscription.etat != scu.INSCRIT: # pas inscrit dans le semestre courant ??? self.passage_de_droit = False + else: + self.passage_de_droit, explanation = self.passage_de_droit_en_but3() # Enfin calcule les codes des UEs: for dec_ue in self.decisions_ues.values(): @@ -423,6 +413,53 @@ class DecisionsProposeesAnnee(DecisionsProposees): ) self.codes = [self.codes[0]] + sorted(self.codes[1:]) + def passage_de_droit_en_but3(self) -> tuple[bool, str]: + """Vérifie si les conditions supplémentaires de passage BUT2 vers BUT3 sont satisfaites""" + cursus: EtudCursusBUT = EtudCursusBUT(self.etud, self.formsemestre.formation) + niveaux_but1 = cursus.niveaux_by_annee[1] + + niveaux_but1_non_valides = [] + for niveau in niveaux_but1: + ok = False + validation_par_annee = cursus.validation_par_competence_et_annee[ + niveau.competence_id + ] + if validation_par_annee: + validation_niveau = validation_par_annee.get("BUT1") + if validation_niveau and validation_niveau.code in CODES_RCUE_VALIDES: + ok = True + if not ok: + niveaux_but1_non_valides.append(niveau) + + # Les niveaux de BUT1 manquants passent-ils en ADSUP ? + # en vertu de l'article 4.3, + # "La validation des deux UE du niveau d’une compétence emporte la validation de + # l’ensemble des UE du niveau inférieur de cette même compétence." + explanation = "" + ok = True + for niveau_but1 in niveaux_but1_non_valides: + niveau_but2 = niveau_but1.competence.niveaux.filter_by(annee="BUT2").first() + if niveau_but2: + rcue = self.rcue_by_niveau.get(niveau_but2.id) + if (rcue is None) or ( + not rcue.est_validable() and not rcue.code_valide() + ): + # le RCUE de BUT2 n'est ni validable (avec les notes en cours) ni déjà validé + ok = False + explanation += ( + f"Compétence {niveau_but1} de BUT 1 non validée.
" + ) + else: + explanation += ( + f"Compétence {niveau_but1} de BUT 1 validée par ce BUT2.
" + ) + else: + ok = False + explanation += f"""Compétence { + niveau_but1} de BUT 1 non validée et non existante en BUT2.
""" + + return ok, explanation + # WIP TODO XXX def get_moyenne_annuelle(self) def infos(self) -> str: @@ -1235,13 +1272,16 @@ class DecisionsProposeesRCUE(DecisionsProposees): self, semestre_id: int, ordre_inferieur: int, competence: ApcCompetence ): """Au besoin, enregistre une validation d'UE ADSUP pour le niveau de compétence - semestre_id : l'indice du semestre concerné (le pair ou l'impair) + semestre_id : l'indice du semestre concerné (le pair ou l'impair du niveau courant) """ - # Les validations d'UE impaires existantes pour ce niveau inférieur ? + semestre_id_inferieur = semestre_id - 2 + if semestre_id_inferieur < 1: + return + # Les validations d'UE existantes pour ce niveau inférieur ? validations_ues: list[ScolarFormSemestreValidation] = ( ScolarFormSemestreValidation.query.filter_by(etudid=self.etud.id) .join(UniteEns) - .filter_by(semestre_idx=semestre_id) + .filter_by(semestre_idx=semestre_id_inferieur) .join(ApcNiveau) .filter_by(ordre=ordre_inferieur) .join(ApcCompetence) @@ -1256,13 +1296,14 @@ class DecisionsProposeesRCUE(DecisionsProposees): # Il faut créer une validation d'UE # cherche l'UE de notre formation associée à ce niveau # et warning si il n'y en a pas - ue = self._get_ue_inferieure(semestre_id, ordre_inferieur, competence) + ue = self._get_ue_inferieure( + semestre_id_inferieur, ordre_inferieur, competence + ) if not ue: # programme incomplet ou mal paramétré flash( - f"""Impossible de valider l'UE inférieure du niveau { - ordre_inferieur - } de la compétence {competence.titre} + f"""Impossible de valider l'UE inférieure de la compétence { + competence.titre} (niveau {ordre_inferieur}) car elle n'existe pas dans la formation """, "warning", diff --git a/app/models/but_refcomp.py b/app/models/but_refcomp.py index c4983bdd22..b33e2f2bb0 100644 --- a/app/models/but_refcomp.py +++ b/app/models/but_refcomp.py @@ -364,6 +364,9 @@ class ApcNiveau(db.Model, XMLModel): return f"""<{self.__class__.__name__} {self.id} ordre={self.ordre!r} annee={ self.annee!r} {self.competence!r}>""" + def __str__(self): + return f"""{self.competence.titre} niveau {self.ordre}""" + def to_dict(self, with_app_critiques=True): "as a dict, recursif (ou non) sur les AC" return { diff --git a/app/templates/jury/erase_decisions_annee_formation.j2 b/app/templates/jury/erase_decisions_annee_formation.j2 index fc8fd0ce02..5f81e69ce5 100644 --- a/app/templates/jury/erase_decisions_annee_formation.j2 +++ b/app/templates/jury/erase_decisions_annee_formation.j2 @@ -35,6 +35,8 @@ quelle que soit leur origine.

+{% endif %} +
Autres actions:
-{% endif %} - {% endblock %} \ No newline at end of file diff --git a/tests/ressources/yaml/cursus_but_geii_lyon.yaml b/tests/ressources/yaml/cursus_but_geii_lyon.yaml index 701585d61f..fefdc9b6bd 100644 --- a/tests/ressources/yaml/cursus_but_geii_lyon.yaml +++ b/tests/ressources/yaml/cursus_but_geii_lyon.yaml @@ -109,6 +109,16 @@ FormSemestres: idx: 1 date_debut: 2022-09-02 date_fin: 2023-01-12 + S3: + idx: 3 + codes_parcours: ['AII'] + date_debut: 2022-09-01 + date_fin: 2023-01-15 + S4: + idx: 4 + codes_parcours: ['AII'] + date_debut: 2023-01-16 + date_fin: 2023-07-10 Etudiants: geii8: @@ -1326,3 +1336,74 @@ Etudiants: # moy_rcue: 13.5000 # Pas de moyenne calculée est_compensable: False decision_annee: ATJ # Passage tout de même en S3 + # + # ----------------------- geii90 : ADSUP envoyés par BUT2 vers BUT1 + # + geii90: + prenom: etugeii90 + civilite: M + code_nip: geii90 + formsemestres: + S1: # 2 UEs, les deux en AJ + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S1.1": 9.5000 + "S1.2": 8.5000 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False + nb_competences: 2 + nb_rcue_annee: 0 + decisions_ues: + "UE11": + codes: [ "AJ", "..." ] + "UE12": + codes: [ "AJ", "..." ] + S2: # pareil, mais le jury le fait passer en S3 + notes_modules: # on joue avec les SAE seulement car elles sont "diagonales" + "S2.1": 9.8000 + "S2.2": 9.9000 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False # d'apres les notes, on ne peut pas passer + autorisations_inscription: [2] # et le jury manuel nous fait passer + nb_competences: 2 + nb_rcue_annee: 2 + valide_moitie_rcue: False + codes: [ "ADJ", "ATJ", "RED", "..." ] + code_valide: RED # le code proposé en auto + decisions_ues: + "UE21": + codes: [ "AJ", "..." ] + code_valide: AJ + moy_ue: 9.8 + "UE22": + code_valide: AJ + moy_ue: 9.9 + decisions_rcues: # on repère ici les RCUE par l'acronyme de leur 1ere UE (donc du S1) + "UE11": + code_valide: AJ # le code proposé en auto + rcue: + # moy_rcue: 14.0000 # Pas de moyenne calculée + est_compensable: False + "UE12": + code_valide: AJ # le code proposé en auto + rcue: + # moy_rcue: 13.5000 # Pas de moyenne calculée + est_compensable: False + decision_annee: ADJ # Passage tout de même en S3 ! + S3: # le S3 avec 4 niveaux + parcours: AII + notes_modules: # combinaison pour avoir ADM AJ AJ AJ + "AII3": 9 + "ER3": 10.75 + "AU3": 8 + attendu: # les codes jury que l'on doit vérifier + deca: + passage_de_droit: False # d'apres les notes, on ne peut pas passer + autorisations_inscription: [4] # passe en S4 + nb_competences: 4 + S4: # le S4 avec 4 niveaux + parcours: AII + notes_modules: # combinaison pour avoir ADM ADM ADM AJ + "PF4": 12 + "SAE4AII": 8