Jury BUT: cas du passage en BUT3 avec BUT1 non validé. Corrige validation ADSUP UEs. Test unitaire: geii90.

This commit is contained in:
Emmanuel Viennet 2023-07-02 16:02:09 +02:00
parent 10de8c4cc2
commit 937a96d086
5 changed files with 148 additions and 23 deletions

View File

@ -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"

View File

@ -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: <b>{
', '.join(ue.acronyme for ue in ues_but1_non_validees)
}</b>. """
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 dune compétence emporte la validation de
# lensemble 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.<br> "
)
else:
explanation += (
f"Compétence {niveau_but1} de BUT 1 validée par ce BUT2.<br> "
)
else:
ok = False
explanation += f"""Compétence {
niveau_but1} de BUT 1 non validée et non existante en BUT2.<br> """
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",

View File

@ -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 {

View File

@ -35,6 +35,8 @@ quelle que soit leur origine.</p>
</form>
</div>
{% endif %}
<div class="sco_box">
<div class="sco_box_title">Autres actions:</div>
<ul>
@ -60,8 +62,6 @@ quelle que soit leur origine.</p>
</ul>
</div>
{% endif %}
{% endblock %}

View File

@ -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