2022-06-09 07:39:58 +02:00
|
|
|
|
##############################################################################
|
|
|
|
|
# ScoDoc
|
|
|
|
|
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
|
|
|
|
|
# See LICENSE
|
|
|
|
|
##############################################################################
|
|
|
|
|
|
|
|
|
|
"""Jury BUT: logique de gestion
|
2022-06-20 17:56:27 +02:00
|
|
|
|
|
|
|
|
|
Utilisation:
|
|
|
|
|
1) chargement page jury, pour un étudiant et un formsemestre BUT quelconque
|
|
|
|
|
- DecisionsProposeesAnnee(formsemestre)
|
|
|
|
|
cherche l'autre formsemestre de la même année scolaire (peut ne pas exister)
|
|
|
|
|
cherche les RCUEs de l'année (BUT1, 2, 3)
|
|
|
|
|
pour un redoublant, le RCUE peut considérer un formsemestre d'une année antérieure.
|
|
|
|
|
|
|
|
|
|
on instancie des DecisionsProposees pour les
|
|
|
|
|
différents éléments (UEs, RCUEs, Année, Diplôme)
|
|
|
|
|
Cela donne
|
|
|
|
|
- les codes possibles (dans .codes)
|
|
|
|
|
- le code actuel si une décision existe déjà (dans code_valide)
|
|
|
|
|
- pour les UEs, le rcue s'il y en a un)
|
|
|
|
|
|
|
|
|
|
2) Validation pour l'utilisateur (form)) => enregistrement code
|
|
|
|
|
- on vérifie que le code soumis est bien dans les codes possibles
|
|
|
|
|
- on enregistre la décision (dans ScolarFormSemestreValidation pour les UE,
|
|
|
|
|
ApcValidationRCUE pour les RCUE, et ApcValidationAnnee pour les années)
|
|
|
|
|
- Si RCUE validé, on déclenche d'éventuelles validations:
|
|
|
|
|
("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.")
|
|
|
|
|
|
|
|
|
|
Les jurys de semestre BUT impairs entrainent systématiquement la génération d'une
|
|
|
|
|
autorisation d'inscription dans le semestre pair suivant: `ScolarAutorisationInscription`.
|
|
|
|
|
Les jurys de semestres pairs non (S2, S4, S6): il y a une décision sur l'année (ETP)
|
|
|
|
|
- autorisation en S_2n+1 (S3 ou S5) si: ADM, ADJ, PASD, PAS1CN
|
|
|
|
|
- autorisation en S2n-1 (S1, S3 ou S5) si: RED
|
|
|
|
|
- rien si pour les autres codes d'année.
|
|
|
|
|
|
|
|
|
|
Le formulaire permet de choisir des codes d'UE, RCUE et Année (ETP).
|
|
|
|
|
Mais normalement, les codes d'UE sont à choisir: les RCUE et l'année s'en déduisent.
|
|
|
|
|
Si l'utilisateur coche "décision manuelle", il peut alors choisir les codes RCUE et années.
|
|
|
|
|
|
|
|
|
|
La soumission du formulaire:
|
|
|
|
|
- etud, formation
|
|
|
|
|
- UEs: [(formsemestre, ue, code), ...]
|
|
|
|
|
- RCUE: [(formsemestre, ue, code), ...] le formsemestre est celui d'indice pair du niveau
|
|
|
|
|
(S2, S4 ou S6), il sera regoupé avec celui impair de la même année ou de la suivante.
|
|
|
|
|
- Année: [(formsemestre, code)]
|
|
|
|
|
|
|
|
|
|
DecisionsProposeesAnnee:
|
|
|
|
|
si 1/2 des rcue et aucun < 8 + pour S5 condition sur les UE de BUT1 et BUT2
|
|
|
|
|
=> charger les DecisionsProposeesRCUE
|
|
|
|
|
|
|
|
|
|
DecisionsProposeesRCUE: les RCUEs pour cette année
|
|
|
|
|
validable, compensable, ajourné. Utilise classe RegroupementCoherentUE
|
|
|
|
|
|
|
|
|
|
DecisionsProposeesUE: décisions de jury sur une UE du BUT
|
|
|
|
|
initialisation sans compensation (ue isolée), mais
|
|
|
|
|
DecisionsProposeesRCUE appelera .set_compensable()
|
|
|
|
|
si on a la possibilité de la compenser dans le RCUE.
|
2022-06-09 07:39:58 +02:00
|
|
|
|
"""
|
|
|
|
|
from operator import attrgetter
|
2022-06-20 17:56:27 +02:00
|
|
|
|
from typing import Union
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
from app import log
|
2022-06-09 07:39:58 +02:00
|
|
|
|
from app.comp.res_but import ResultatsSemestreBUT
|
|
|
|
|
from app.comp import res_sem
|
2022-06-20 17:56:27 +02:00
|
|
|
|
|
2022-06-09 07:39:58 +02:00
|
|
|
|
from app.models.but_refcomp import (
|
|
|
|
|
ApcAnneeParcours,
|
|
|
|
|
ApcCompetence,
|
|
|
|
|
ApcNiveau,
|
2022-06-20 17:56:27 +02:00
|
|
|
|
ApcParcours,
|
2022-06-09 07:39:58 +02:00
|
|
|
|
ApcParcoursNiveauCompetence,
|
|
|
|
|
)
|
2022-06-20 17:56:27 +02:00
|
|
|
|
from app.models import but_validations
|
|
|
|
|
from app.models.but_validations import (
|
|
|
|
|
ApcValidationAnnee,
|
|
|
|
|
ApcValidationRCUE,
|
|
|
|
|
RegroupementCoherentUE,
|
|
|
|
|
)
|
2022-06-09 07:39:58 +02:00
|
|
|
|
from app.models.etudiants import Identite
|
|
|
|
|
from app.models.formations import Formation
|
2022-06-20 17:56:27 +02:00
|
|
|
|
from app.models.formsemestre import FormSemestre, FormSemestreInscription
|
2022-06-09 07:39:58 +02:00
|
|
|
|
from app.models.ues import UniteEns
|
2022-06-20 17:56:27 +02:00
|
|
|
|
from app.models.validations import ScolarFormSemestreValidation
|
|
|
|
|
from app.scodoc import sco_codes_parcours as sco_codes
|
2022-06-09 07:39:58 +02:00
|
|
|
|
from app.scodoc import sco_utils as scu
|
2022-06-20 17:56:27 +02:00
|
|
|
|
from app.scodoc.sco_exceptions import ScoException, ScoValueError
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
class DecisionsProposees:
|
|
|
|
|
"""Une décision de jury proposé, constituée d'une liste de codes et d'une explication.
|
|
|
|
|
Super-classe, spécialisée pour les UE, les RCUE, les années et le diplôme.
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
validation : None ou une instance de d'une classe avec un champ code
|
|
|
|
|
ApcValidationRCUE, ApcValidationAnnee ou ScolarFormSemestreValidation
|
|
|
|
|
"""
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
|
|
|
|
# Codes toujours proposés sauf si include_communs est faux:
|
2022-06-20 17:56:27 +02:00
|
|
|
|
codes_communs = [
|
|
|
|
|
sco_codes.RAT,
|
|
|
|
|
sco_codes.DEF,
|
|
|
|
|
sco_codes.ABAN,
|
|
|
|
|
sco_codes.DEM,
|
|
|
|
|
sco_codes.UEBSL,
|
|
|
|
|
]
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
etud: Identite = None,
|
|
|
|
|
code: Union[str, list[str]] = None,
|
|
|
|
|
explanation="",
|
|
|
|
|
code_valide=None,
|
|
|
|
|
include_communs=True,
|
|
|
|
|
):
|
|
|
|
|
self.etud = etud
|
|
|
|
|
self.codes = []
|
|
|
|
|
"Les codes attribuables par ce jury"
|
2022-06-09 07:39:58 +02:00
|
|
|
|
if include_communs:
|
|
|
|
|
self.codes = self.codes_communs
|
|
|
|
|
if isinstance(code, list):
|
|
|
|
|
self.codes = code + self.codes_communs
|
|
|
|
|
elif code is not None:
|
|
|
|
|
self.codes = [code] + self.codes_communs
|
2022-06-20 17:56:27 +02:00
|
|
|
|
self.code_valide: str = code_valide
|
|
|
|
|
"La décision actuelle enregistrée"
|
|
|
|
|
self.explanation: str = explanation
|
|
|
|
|
"Explication en à afficher à côté de la décision"
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
2022-06-20 17:56:27 +02:00
|
|
|
|
return f"""<{self.__class__.__name__} valid={self.code_valide
|
|
|
|
|
} codes={self.codes} explanation={self.explanation}"""
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
class DecisionsProposeesAnnee(DecisionsProposees):
|
|
|
|
|
"""Décisions de jury sur une année (ETP) du BUT
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
Le texte:
|
|
|
|
|
La poursuite d'études dans un semestre pair d’une même année est de droit
|
|
|
|
|
pour tout étudiant. 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 poursuite d'études dans le semestre 5 nécessite de plus la validation
|
|
|
|
|
de toutes les UE des semestres 1 et 2 dans les conditions de validation
|
|
|
|
|
des points 4.3 (moy_ue >= 10) et 4.4 (compensation rcue), ou par décision
|
|
|
|
|
de jury.
|
2022-06-09 07:39:58 +02:00
|
|
|
|
"""
|
2022-06-20 17:56:27 +02:00
|
|
|
|
|
|
|
|
|
# Codes toujours proposés sauf si include_communs est faux:
|
|
|
|
|
codes_communs = [
|
|
|
|
|
sco_codes.RAT,
|
|
|
|
|
sco_codes.ABAN,
|
|
|
|
|
sco_codes.ABL,
|
|
|
|
|
sco_codes.DEF,
|
|
|
|
|
sco_codes.DEM,
|
|
|
|
|
sco_codes.EXCLU,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
etud: Identite,
|
|
|
|
|
formsemestre: FormSemestre,
|
|
|
|
|
):
|
|
|
|
|
super().__init__(etud=etud)
|
|
|
|
|
formsemestre_impair, formsemestre_pair = self.comp_formsemestres(formsemestre)
|
|
|
|
|
assert (
|
|
|
|
|
(formsemestre_pair is None)
|
|
|
|
|
or (formsemestre_impair is None)
|
|
|
|
|
or (
|
|
|
|
|
((formsemestre_pair.semestre_id - formsemestre_impair.semestre_id) == 1)
|
|
|
|
|
and (
|
|
|
|
|
formsemestre_pair.formation.referentiel_competence_id
|
|
|
|
|
== formsemestre_impair.formation.referentiel_competence_id
|
|
|
|
|
)
|
|
|
|
|
)
|
2022-06-09 07:39:58 +02:00
|
|
|
|
)
|
2022-06-20 17:56:27 +02:00
|
|
|
|
|
|
|
|
|
self.formsemestre_impair = formsemestre_impair
|
|
|
|
|
"le 1er semestre de l'année scolaire considérée (S1, S3, S5)"
|
|
|
|
|
self.formsemestre_pair = formsemestre_pair
|
|
|
|
|
"le second formsemestre de la même année scolaire (S2, S4, S6)"
|
|
|
|
|
self.annee_but = formsemestre_impair.semestre_id // 2 + 1
|
|
|
|
|
"le rang de l'année dans le BUT: 1, 2, 3"
|
|
|
|
|
assert self.annee_but in (1, 2, 3)
|
|
|
|
|
self.validation = ApcValidationAnnee.query.filter_by(
|
|
|
|
|
etudid=self.etud.id,
|
|
|
|
|
formsemestre_id=formsemestre_impair.id,
|
|
|
|
|
ordre=self.annee_but,
|
|
|
|
|
).first()
|
|
|
|
|
if self.validation is not None:
|
|
|
|
|
self.code_valide = self.validation.code
|
|
|
|
|
self.parcour = None
|
|
|
|
|
"Le parcours considéré (celui du semestre pair, ou à défaut impair)"
|
|
|
|
|
self.ues_impair, self.ues_pair = self.compute_ues_annee() # pylint: disable=all
|
|
|
|
|
assert self.parcour is not None
|
|
|
|
|
self.rcues_annee = self.compute_rcues_annee()
|
|
|
|
|
"RCUEs de l'année"
|
|
|
|
|
|
|
|
|
|
self.nb_competences = len(
|
|
|
|
|
ApcNiveau.niveaux_annee_de_parcours(self.parcour, self.annee_but).all()
|
|
|
|
|
) # note that .count() won't give the same res
|
|
|
|
|
self.nb_validables = len(
|
|
|
|
|
[rcue for rcue in self.rcues_annee if rcue.est_validable()]
|
2022-06-09 07:39:58 +02:00
|
|
|
|
)
|
2022-06-20 17:56:27 +02:00
|
|
|
|
self.nb_rcues_under_8 = len(
|
|
|
|
|
[rcue for rcue in self.rcues_annee if not rcue.est_suffisant()]
|
2022-06-09 07:39:58 +02:00
|
|
|
|
)
|
2022-06-20 17:56:27 +02:00
|
|
|
|
# année ADM si toutes RCUE validées (sinon PASD)
|
|
|
|
|
admis = self.nb_validables == self.nb_competences
|
|
|
|
|
valide_moitie_rcue = self.nb_validables > self.nb_competences // 2
|
|
|
|
|
# Peut passer si plus de la moitié validables et tous > 8
|
|
|
|
|
passage_de_droit = valide_moitie_rcue and (self.nb_rcues_under_8 == 0)
|
|
|
|
|
# XXX TODO ajouter condition pour passage en S5
|
|
|
|
|
|
|
|
|
|
# Reste à attribuer ADM, ADJ, PASD, PAS1NCI, RED, NAR
|
|
|
|
|
expl_rcues = f"{self.nb_validables} validables sur {self.nb_competences}"
|
|
|
|
|
if admis:
|
|
|
|
|
self.codes = [sco_codes.ADM] + self.codes
|
|
|
|
|
self.explanation = expl_rcues
|
|
|
|
|
elif passage_de_droit:
|
|
|
|
|
self.codes = [sco_codes.PASD, sco_codes.ADJ] + self.codes
|
|
|
|
|
self.explanation = expl_rcues
|
|
|
|
|
elif valide_moitie_rcue: # mais au moins 1 rcue insuffisante
|
|
|
|
|
self.codes = [sco_codes.PAS1NCI, sco_codes.ADJ] + self.codes
|
|
|
|
|
self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} < 8"
|
|
|
|
|
else:
|
|
|
|
|
self.codes = [sco_codes.RED, sco_codes.NAR, sco_codes.ADJ] + self.codes
|
|
|
|
|
self.explanation = expl_rcues + f" et {self.nb_rcues_under_8} < 8"
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
def infos(self) -> str:
|
|
|
|
|
"informations, for debugging purpose"
|
|
|
|
|
return f"""DecisionsProposeesAnnee
|
|
|
|
|
etud: {self.etud}
|
|
|
|
|
formsemestre_pair: {self.formsemestre_pair}
|
|
|
|
|
formsemestre_impair: {self.formsemestre_impair}
|
|
|
|
|
RCUEs: {self.rcues_annee}
|
|
|
|
|
nb_competences: {self.nb_competences}
|
|
|
|
|
nb_nb_validables: {self.nb_validables}
|
|
|
|
|
codes: {self.codes}
|
|
|
|
|
explanation: {self.explanation}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def comp_formsemestres(
|
|
|
|
|
self, formsemestre: FormSemestre
|
|
|
|
|
) -> tuple[FormSemestre, FormSemestre]:
|
|
|
|
|
"les deux formsemestres de l'année scolaire à laquelle appartient formsemestre"
|
|
|
|
|
if formsemestre.semestre_id % 2 == 0:
|
|
|
|
|
other_semestre_id = formsemestre.semestre_id - 1
|
|
|
|
|
else:
|
|
|
|
|
other_semestre_id = formsemestre.semestre_id + 1
|
|
|
|
|
annee_scolaire = formsemestre.annee_scolaire()
|
|
|
|
|
other_formsemestre = None
|
|
|
|
|
for inscr in self.etud.formsemestre_inscriptions:
|
|
|
|
|
if (
|
|
|
|
|
# Même spécialité BUT (tolère ainsi des variantes de formation)
|
|
|
|
|
(
|
|
|
|
|
inscr.formsemestre.formation.referentiel_competence
|
|
|
|
|
== formsemestre.formation.referentiel_competence
|
|
|
|
|
)
|
|
|
|
|
# L'autre semestre
|
|
|
|
|
and (inscr.formsemestre.semestre_id == other_semestre_id)
|
|
|
|
|
# de la même année scolaire:
|
|
|
|
|
and (inscr.formsemestre.annee_scolaire() == annee_scolaire)
|
|
|
|
|
):
|
|
|
|
|
other_formsemestre = inscr.formsemestre
|
|
|
|
|
if formsemestre.semestre_id % 2 == 0:
|
|
|
|
|
return other_formsemestre, formsemestre
|
|
|
|
|
return formsemestre, other_formsemestre
|
|
|
|
|
|
|
|
|
|
def compute_ues_annee(self) -> list[list[UniteEns], list[UniteEns]]:
|
|
|
|
|
"""UEs à valider cette année pour cet étudiant, selon son parcours.
|
|
|
|
|
Ramène [ listes des UE du semestre impair, liste des UE du semestre pair ].
|
|
|
|
|
"""
|
|
|
|
|
etudid = self.etud.id
|
|
|
|
|
ues_sems = []
|
|
|
|
|
for formsemestre in self.formsemestre_impair, self.formsemestre_pair:
|
|
|
|
|
if formsemestre is None:
|
|
|
|
|
ues = []
|
|
|
|
|
else:
|
|
|
|
|
formation: Formation = formsemestre.formation
|
|
|
|
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(
|
|
|
|
|
formsemestre
|
|
|
|
|
)
|
|
|
|
|
# Parcour dans lequel l'étudiant est inscrit, et liste des UEs
|
|
|
|
|
if res.etuds_parcour_id[etudid] is None:
|
|
|
|
|
# pas de parcour: prend toutes les UEs (non bonus)
|
|
|
|
|
ues = res.etud_ues(etudid)
|
|
|
|
|
else:
|
|
|
|
|
parcour = ApcParcours.query.get(res.etuds_parcour_id[etudid])
|
|
|
|
|
if parcour is not None:
|
|
|
|
|
self.parcour = parcour
|
|
|
|
|
ues = (
|
|
|
|
|
formation.query_ues_parcour(parcour)
|
|
|
|
|
.filter_by(semestre_idx=formsemestre.semestre_id)
|
|
|
|
|
.all()
|
|
|
|
|
)
|
|
|
|
|
ues_sems.append(ues)
|
|
|
|
|
return ues_sems
|
|
|
|
|
|
|
|
|
|
def check_ues_ready_jury(self) -> list[str]:
|
|
|
|
|
"""Vérifie que les toutes les UEs (hors bonus) de l'année sont
|
|
|
|
|
bien associées à des niveaux de compétences.
|
|
|
|
|
Renvoie liste vide si ok, sinon liste de message explicatifs
|
|
|
|
|
"""
|
|
|
|
|
messages = []
|
|
|
|
|
for ue in self.ues_impair + self.ues_pair:
|
|
|
|
|
if ue.niveau_competence is None:
|
|
|
|
|
messages.append(
|
|
|
|
|
f"UE {ue.acronyme} non associée à un niveau de compétence"
|
|
|
|
|
)
|
|
|
|
|
if ue.semestre_idx is None:
|
|
|
|
|
messages.append(
|
|
|
|
|
f"UE {ue.acronyme} n'a pas d'indice de semestre dans la formation"
|
2022-06-09 07:39:58 +02:00
|
|
|
|
)
|
2022-06-20 17:56:27 +02:00
|
|
|
|
return messages
|
|
|
|
|
|
|
|
|
|
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).
|
|
|
|
|
Si on n'a pas les deux semestres, aucun RCUE.
|
|
|
|
|
Raises ScoValueError s'il y a des UE sans RCUE.
|
|
|
|
|
"""
|
|
|
|
|
if self.formsemestre_pair is None or self.formsemestre_impair is None:
|
|
|
|
|
return []
|
|
|
|
|
rcues_annee = []
|
|
|
|
|
ues_impair_sans_rcue = {ue.id for ue in self.ues_impair}
|
|
|
|
|
for ue_pair in self.ues_pair:
|
|
|
|
|
rcue = None
|
|
|
|
|
for ue_impair in self.ues_impair:
|
|
|
|
|
if ue_pair.niveau_competence_id == ue_impair.niveau_competence_id:
|
|
|
|
|
rcue = RegroupementCoherentUE(
|
|
|
|
|
self.etud,
|
|
|
|
|
self.formsemestre_impair,
|
|
|
|
|
ue_impair,
|
|
|
|
|
self.formsemestre_pair,
|
|
|
|
|
ue_pair,
|
|
|
|
|
)
|
|
|
|
|
ues_impair_sans_rcue.remove(ue_impair.id)
|
|
|
|
|
break
|
|
|
|
|
if rcue is None:
|
|
|
|
|
raise ScoValueError(f"pas de RCUE pour l'UE {ue_pair.acronyme}")
|
|
|
|
|
rcues_annee.append(rcue)
|
|
|
|
|
if len(ues_impair_sans_rcue) > 0:
|
|
|
|
|
ue = ues_impair_sans_rcue.pop()
|
|
|
|
|
raise ScoValueError(f"pas de RCUE pour l'UE {ue.acronyme}")
|
|
|
|
|
return rcues_annee
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DecisionsProposeesRCUE(DecisionsProposees):
|
2022-06-09 07:39:58 +02:00
|
|
|
|
"""Liste des codes de décisions que l'on peut proposer pour
|
2022-06-20 17:56:27 +02:00
|
|
|
|
le RCUE de cet étudiant dans cette année.
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
|
|
|
|
ADM, CMP, ADJ, AJ, RAT, DEF, ABAN
|
2022-06-20 17:56:27 +02:00
|
|
|
|
"""
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
codes_communs = [
|
|
|
|
|
sco_codes.ADJ,
|
|
|
|
|
sco_codes.RAT,
|
|
|
|
|
sco_codes.DEF,
|
|
|
|
|
sco_codes.ABAN,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self, dec_prop_annee: DecisionsProposeesAnnee, rcue: RegroupementCoherentUE
|
|
|
|
|
):
|
|
|
|
|
super().__init__(etud=dec_prop_annee.etud)
|
|
|
|
|
self.rcue = rcue
|
|
|
|
|
|
|
|
|
|
validation = rcue.query_validations().first()
|
|
|
|
|
if validation is not None:
|
|
|
|
|
self.code_valide = validation.code
|
|
|
|
|
if rcue.est_compense():
|
|
|
|
|
self.codes.insert(0, sco_codes.CMP)
|
|
|
|
|
elif rcue.est_validable():
|
|
|
|
|
self.codes.insert(0, sco_codes.ADM)
|
|
|
|
|
else:
|
|
|
|
|
self.codes.insert(0, sco_codes.AJ)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DecisionsProposeesUE(DecisionsProposees):
|
|
|
|
|
"""Décisions de jury sur une UE du BUT
|
|
|
|
|
|
|
|
|
|
Liste des codes de décisions que l'on peut proposer pour
|
|
|
|
|
cette UE d'un étudiant dans un semestre.
|
|
|
|
|
|
|
|
|
|
Si DEF ou DEM ou ABAN ou ABL sur année BUT: seulement DEF, DEM, ABAN, ABL
|
|
|
|
|
|
|
|
|
|
si moy_ue > 10, ADM
|
|
|
|
|
sinon si compensation dans RCUE: CMP
|
|
|
|
|
sinon: ADJ, AJ
|
|
|
|
|
|
|
|
|
|
et proposer toujours: RAT, DEF, ABAN, DEM, UEBSL (codes_communs)
|
2022-06-09 07:39:58 +02:00
|
|
|
|
"""
|
2022-06-20 17:56:27 +02:00
|
|
|
|
|
|
|
|
|
# Codes toujours proposés sauf si include_communs est faux:
|
|
|
|
|
codes_communs = [
|
|
|
|
|
sco_codes.RAT,
|
|
|
|
|
sco_codes.DEF,
|
|
|
|
|
sco_codes.ABAN,
|
|
|
|
|
sco_codes.DEM,
|
|
|
|
|
sco_codes.UEBSL,
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
etud: Identite,
|
|
|
|
|
formsemestre: FormSemestre,
|
|
|
|
|
ue: UniteEns,
|
|
|
|
|
):
|
|
|
|
|
super().__init__(etud=etud)
|
|
|
|
|
self.ue: UniteEns = ue
|
|
|
|
|
self.rcue: RegroupementCoherentUE = None
|
|
|
|
|
# Une UE peut être validée plusieurs fois en cas de redoublement (qu'elle soit capitalisée ou non)
|
|
|
|
|
# mais ici on a restreint au formsemestre donc une seule (prend la première)
|
|
|
|
|
self.validation = ScolarFormSemestreValidation.query.filter_by(
|
|
|
|
|
etudid=self.etud.id, formsemestre_id=formsemestre.id, ue_id=ue.id
|
|
|
|
|
).first()
|
|
|
|
|
if self.validation is not None:
|
|
|
|
|
self.code_valide = self.validation.code
|
|
|
|
|
if ue.type == sco_codes.UE_SPORT:
|
|
|
|
|
self.explanation = "UE bonus, pas de décision de jury"
|
|
|
|
|
self.codes = [] # aucun code proposé
|
|
|
|
|
return
|
|
|
|
|
# Code sur année ?
|
|
|
|
|
decision_annee = ApcValidationAnnee.query.filter_by(
|
|
|
|
|
etudid=etud.id, annee_scolaire=formsemestre.annee_scolaire()
|
|
|
|
|
).first()
|
|
|
|
|
if (
|
|
|
|
|
decision_annee is not None
|
|
|
|
|
and decision_annee.code in sco_codes.CODES_ANNEE_ARRET
|
|
|
|
|
): # DEF, DEM, ABAN, ABL
|
|
|
|
|
self.explanation = f"l'année a le code {decision_annee.code}"
|
|
|
|
|
self.codes = [decision_annee.code] # sans les codes communs
|
|
|
|
|
return
|
|
|
|
|
# Moyenne de l'UE ?
|
|
|
|
|
res: ResultatsSemestreBUT = res_sem.load_formsemestre_results(formsemestre)
|
|
|
|
|
if not ue.id in res.etud_moy_ue:
|
|
|
|
|
self.explanation = "UE sans résultat"
|
|
|
|
|
return
|
|
|
|
|
if not etud.id in res.etud_moy_ue[ue.id]:
|
|
|
|
|
self.explanation = "Étudiant sans résultat dans cette UE"
|
|
|
|
|
return
|
|
|
|
|
moy_ue = res.etud_moy_ue[ue.id][etud.id]
|
|
|
|
|
if moy_ue > (sco_codes.ParcoursBUT.BARRE_MOY - sco_codes.NOTES_TOLERANCE):
|
|
|
|
|
self.codes.insert(0, sco_codes.ADM)
|
|
|
|
|
self.explanation = (f"Moyenne >= {sco_codes.ParcoursBUT.BARRE_MOY}/20",)
|
|
|
|
|
|
|
|
|
|
# Compensation dans un RCUE ?
|
|
|
|
|
rcues = but_validations.find_rcues(formsemestre, ue, etud)
|
|
|
|
|
for rcue in rcues:
|
|
|
|
|
if rcue.est_validable():
|
|
|
|
|
self.codes.insert(0, sco_codes.CMP)
|
|
|
|
|
self.explanation = f"Compensée par {rcue.other_ue(ue)} (moyenne RCUE={scu.fmt_note(rcue.moy_rcue)}/20"
|
|
|
|
|
self.rcue = rcue
|
|
|
|
|
return # s'arrête au 1er RCU validable
|
|
|
|
|
|
|
|
|
|
# Échec à valider cette UE
|
|
|
|
|
self.codes = [sco_codes.AJ, sco_codes.ADJ] + self.codes
|
|
|
|
|
self.explanation = "notes insuffisantes"
|
2022-06-09 07:39:58 +02:00
|
|
|
|
|
|
|
|
|
|
2022-06-20 17:56:27 +02:00
|
|
|
|
class BUTCursusEtud: # WIP TODO
|
2022-06-09 07:39:58 +02:00
|
|
|
|
"""Validation du cursus d'un étudiant"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, formsemestre: FormSemestre, etud: Identite):
|
|
|
|
|
if formsemestre.formation.referentiel_competence is None:
|
|
|
|
|
raise ScoException("BUTCursusEtud: pas de référentiel de compétences")
|
|
|
|
|
assert len(etud.formsemestre_inscriptions) > 0
|
|
|
|
|
self.formsemestre = formsemestre
|
|
|
|
|
self.etud = etud
|
|
|
|
|
#
|
|
|
|
|
# La dernière inscription en date va donner le parcours (donc les compétences à valider)
|
|
|
|
|
self.last_inscription = sorted(
|
|
|
|
|
etud.formsemestre_inscriptions, key=attrgetter("formsemestre.date_debut")
|
|
|
|
|
)[-1]
|
|
|
|
|
|
|
|
|
|
def est_diplomable(self) -> bool:
|
|
|
|
|
"""Vrai si toutes les compétences sont validables"""
|
|
|
|
|
return all(
|
|
|
|
|
self.competence_validable(competence)
|
|
|
|
|
for competence in self.competences_du_parcours()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def est_diplome(self) -> bool:
|
|
|
|
|
"""Vrai si BUT déjà validé"""
|
|
|
|
|
# vrai si la troisième année est validée
|
|
|
|
|
# On cherche les validations de 3ieme annee (ordre=3) avec le même référentiel
|
|
|
|
|
# de formation que nous.
|
|
|
|
|
return (
|
|
|
|
|
ApcValidationAnnee.query.filter_by(etudid=self.etud.id, ordre=3)
|
|
|
|
|
.join(FormSemestre, FormSemestre.id == ApcValidationAnnee.formsemestre_id)
|
|
|
|
|
.join(Formation, FormSemestre.formation_id == Formation.id)
|
|
|
|
|
.filter(
|
|
|
|
|
Formation.referentiel_competence_id
|
|
|
|
|
== self.formsemestre.formation.referentiel_competence_id
|
|
|
|
|
)
|
|
|
|
|
.count()
|
|
|
|
|
> 0
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def competences_du_parcours(self) -> list[ApcCompetence]:
|
|
|
|
|
"""Construit liste des compétences du parcours, qui doivent être
|
|
|
|
|
validées pour obtenir le diplôme.
|
|
|
|
|
Le parcours est celui de la dernière inscription.
|
|
|
|
|
"""
|
|
|
|
|
parcour = self.last_inscription.parcour
|
|
|
|
|
query = self.formsemestre.formation.formation.query_competences_parcour(parcour)
|
|
|
|
|
if query is None:
|
|
|
|
|
return []
|
|
|
|
|
return query.all()
|
|
|
|
|
|
|
|
|
|
def competence_validee(self, competence: ApcCompetence) -> bool:
|
|
|
|
|
"""Vrai si la compétence est validée, c'est à dire que tous ses
|
|
|
|
|
niveaux sont validés (ApcValidationRCUE).
|
|
|
|
|
"""
|
2022-06-20 17:56:27 +02:00
|
|
|
|
# XXX A REVOIR
|
2022-06-09 07:39:58 +02:00
|
|
|
|
validations = (
|
|
|
|
|
ApcValidationRCUE.query.filter_by(etudid=self.etud.id)
|
|
|
|
|
.join(UniteEns, UniteEns.id == ApcValidationRCUE.ue1_id)
|
|
|
|
|
.join(ApcNiveau, ApcNiveau.id == UniteEns.niveau_competence_id)
|
|
|
|
|
.join(ApcCompetence, ApcCompetence.id == ApcNiveau.competence_id)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def competence_validable(self, competence: ApcCompetence):
|
|
|
|
|
"""Vrai si la compétence est "validable" automatiquement, c'est à dire
|
|
|
|
|
que les conditions de notes sont satisfaites pour l'acquisition de
|
|
|
|
|
son niveau le plus élevé, qu'il ne manque que l'enregistrement de la décision.
|
|
|
|
|
|
|
|
|
|
En vertu de la règle "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.",
|
|
|
|
|
il suffit de considérer le dernier niveau dans lequel l'étudiant est inscrit.
|
|
|
|
|
"""
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def ues_emportees(self, niveau: ApcNiveau) -> list[tuple[FormSemestre, UniteEns]]:
|
|
|
|
|
"""La liste des UE à valider si on valide ce niveau.
|
|
|
|
|
Ne liste que les UE qui ne sont pas déjà acquises.
|
|
|
|
|
|
|
|
|
|
Selon la règle donéne par l'arrêté BUT:
|
|
|
|
|
* 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.
|
|
|
|
|
"""
|
|
|
|
|
pass
|