BUT: jury inter-année pour les redoublants
This commit is contained in:
parent
a4840f494b
commit
8e6dc37a87
@ -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)"""
|
||||
|
@ -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"""
|
||||
<div class="titre_niveaux"><b>Niveaux de compétences et unités d'enseignement du BUT{deca.annee_but}</b></div>
|
||||
<div class="but_annee">
|
||||
<div class="titre"></div>
|
||||
<div class="titre">S{deca.formsemestre_impair.semestre_id
|
||||
if deca.formsemestre_impair else "-"}</div>
|
||||
<div class="titre">S{deca.formsemestre_pair.semestre_id
|
||||
if deca.formsemestre_pair else "-"}
|
||||
<span class="avertissement_redoublement">{avertissement_redoublement}</span></div>
|
||||
<div class="titre">S{formsemestre_1.semestre_id
|
||||
if formsemestre_1 else "-"}
|
||||
<span class="avertissement_redoublement">{formsemestre_1.annee_scolaire_str() if formsemestre_1 else ""}</span>
|
||||
</div>
|
||||
<div class="titre">S{formsemestre_2.semestre_id
|
||||
if formsemestre_2 else "-"}
|
||||
<span class="avertissement_redoublement">{formsemestre_2.annee_scolaire_str() if formsemestre_2 else ""}</span>
|
||||
</div>
|
||||
<div class="titre">RCUE</div>
|
||||
"""
|
||||
)
|
||||
@ -91,43 +99,54 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
|
||||
<div title="{niveau.competence.titre_long}">{niveau.competence.titre}</div>
|
||||
</div>"""
|
||||
)
|
||||
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(
|
||||
"""<div class="niveau_vide"></div><div class="niveau_vide"></div><div class="niveau_vide"></div>"""
|
||||
)
|
||||
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"""<div class="but_niveau_rcue
|
||||
{'recorded' if dec_rcue.code_valide is not None else ''}
|
||||
">
|
||||
<div class="but_note">{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
|
||||
<div class="but_code">{
|
||||
_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,
|
||||
)
|
||||
}</div>
|
||||
</div>"""
|
||||
)
|
||||
)
|
||||
|
||||
# RCUE
|
||||
if dec_rcue is None:
|
||||
H.append("""<div class="niveau_vide"></div>""")
|
||||
else:
|
||||
H.append(
|
||||
f"""<div class="but_niveau_rcue
|
||||
{'recorded' if dec_rcue.code_valide is not None else ''}
|
||||
">
|
||||
<div class="but_note">{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
|
||||
<div class="but_code">{
|
||||
_gen_but_select("code_rcue_"+str(niveau.id),
|
||||
dec_rcue.codes,
|
||||
dec_rcue.code_valide,
|
||||
disabled=True, klass="manual"
|
||||
)
|
||||
}</div>
|
||||
</div>"""
|
||||
)
|
||||
H.append("</div>") # 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"""<div class="but_code {klass}">{code_valide}</div>"""
|
||||
h = "\n".join(
|
||||
[
|
||||
f"""<option value="{code}"
|
||||
@ -157,7 +178,12 @@ def _gen_but_select(
|
||||
"""
|
||||
|
||||
|
||||
def _gen_but_niveau_ue(ue: UniteEns, dec_ue: DecisionsProposeesUE, disabled=False):
|
||||
def _gen_but_niveau_ue(
|
||||
ue: UniteEns,
|
||||
dec_ue: DecisionsProposeesUE,
|
||||
disabled: bool = False,
|
||||
annee_prec: bool = False,
|
||||
):
|
||||
if dec_ue.ue_status and dec_ue.ue_status["is_capitalized"]:
|
||||
moy_ue_str = f"""<span class="ue_cap">{
|
||||
scu.fmt_note(dec_ue.moy_ue_with_cap)}</span>"""
|
||||
@ -178,6 +204,7 @@ def _gen_but_niveau_ue(ue: UniteEns, dec_ue: DecisionsProposeesUE, disabled=Fals
|
||||
|
||||
return f"""<div class="but_niveau_ue {
|
||||
'recorded' if dec_ue.code_valide is not None else ''}
|
||||
{'annee_prec' if annee_prec else ''}
|
||||
">
|
||||
<div title="{ue.titre}">{ue.acronyme}</div>
|
||||
<div class="but_note with_scoplement">
|
||||
|
@ -58,7 +58,7 @@ class ScolarFormSemestreValidation(db.Model):
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({self.formsemestre_id}, {self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})"
|
||||
return f"{self.__class__.__name__}(sem={self.formsemestre_id}, etuid={self.etudid}, code={self.code}, ue={self.ue}, moy_ue={self.moy_ue})"
|
||||
|
||||
def __str__(self):
|
||||
if self.ue_id:
|
||||
|
@ -122,17 +122,29 @@ select:invalid {
|
||||
background: red;
|
||||
}
|
||||
|
||||
select.but_code option.recorded {
|
||||
select.but_code option.recorded,
|
||||
div.but_code recorded {
|
||||
color: rgb(3, 157, 3);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.but_code {
|
||||
font-weight: bold;
|
||||
color: blue;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
div.but_niveau_ue.recorded,
|
||||
div.but_niveau_rcue.recorded {
|
||||
border-color: rgb(136, 252, 136);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
div.but_niveau_ue.annee_prec {
|
||||
background-color: rgb(167, 167, 0);
|
||||
}
|
||||
|
||||
div.but_niveau_ue.modified {
|
||||
background-color: rgb(255, 214, 254);
|
||||
}
|
||||
|
@ -60,7 +60,9 @@ def test_but_jury_GMP_lm(test_client):
|
||||
# puis lance les tests de jury
|
||||
doc = yaml_setup.setup_from_yaml("tests/unit/cursus_but_gmp_iutlm.yaml")
|
||||
|
||||
formsemestres = FormSemestre.query.order_by(FormSemestre.semestre_id).all()
|
||||
formsemestres = FormSemestre.query.order_by(
|
||||
FormSemestre.date_debut, FormSemestre.semestre_id
|
||||
).all()
|
||||
# Vérifie les deca de tous les semestres:
|
||||
for formsemestre in formsemestres:
|
||||
_check_deca(formsemestre)
|
||||
@ -68,7 +70,6 @@ def test_but_jury_GMP_lm(test_client):
|
||||
# Saisie de toutes les décisions de jury
|
||||
for formsemestre in formsemestres:
|
||||
formsemestre_validation_auto_but(formsemestre, only_adm=False)
|
||||
|
||||
# Vérifie résultats attendus:
|
||||
for formsemestre in formsemestres:
|
||||
_test_but_jury(formsemestre, doc)
|
||||
|
Loading…
Reference in New Issue
Block a user