1
0
forked from ScoDoc/ScoDoc

Jury BUT: amélioration gestion redoublants + #547 (WIP)

This commit is contained in:
Emmanuel Viennet 2023-01-08 15:36:05 -03:00
parent c06a6e83b6
commit c374209d22
7 changed files with 99 additions and 52 deletions

View File

@ -350,8 +350,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
) )
"vrai si l'année est réussie, tous niveaux validables ou validés par le jury" "vrai si l'année est réussie, tous niveaux validables ou validés par le jury"
self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2) self.valide_moitie_rcue = self.nb_validables > (self.nb_competences // 2)
"Peut passer si plus de la moitié validables et tous > 8" "Vrai si plus de la moitié des RCUE validables"
self.passage_de_droit = self.valide_moitie_rcue and (self.nb_rcues_under_8 == 0) self.passage_de_droit = self.valide_moitie_rcue and (self.nb_rcues_under_8 == 0)
"Vrai si peut passer dans l'année BUT suivante: plus de la moitié validables et tous > 8"
# XXX TODO ajouter condition pour passage en S5 # XXX TODO ajouter condition pour passage en S5
# Enfin calcule les codes des UE: # Enfin calcule les codes des UE:
@ -752,8 +753,9 @@ class DecisionsProposeesAnnee(DecisionsProposees):
pour cette année: décisions d'UE, de RCUE, d'année, pour cette année: décisions d'UE, de RCUE, d'année,
et autorisations d'inscription émises. et autorisations d'inscription émises.
Efface même si étudiant DEM ou DEF. Efface même si étudiant DEM ou DEF.
Si à cheval, n'efface que pour le semestre d'origine du deca.
""" """
if only_one_sem: if only_one_sem or self.a_cheval:
# N'efface que les autorisations venant de ce semestre, # N'efface que les autorisations venant de ce semestre,
# et les validations de ses UEs # et les validations de ses UEs
ScolarAutorisationInscription.delete_autorisation_etud( ScolarAutorisationInscription.delete_autorisation_etud(
@ -906,6 +908,19 @@ class DecisionsProposeesRCUE(DecisionsProposees):
or dec_prop_annee.formsemestre_pair.modalite == "EXT" or dec_prop_annee.formsemestre_pair.modalite == "EXT"
): ):
self.codes.insert(0, sco_codes.ADM) self.codes.insert(0, sco_codes.ADM)
# S'il y a une décision enregistrée: si elle est plus favorable que celle que l'on
# proposerait, la place en tête.
# Sinon, la place en seconde place
if self.code_valide and self.code_valide != self.codes[0]:
code_default = self.codes[0]
if self.code_valide in self.codes:
self.codes.remove(self.code_valide)
if sco_codes.BUT_CODES_ORDERED.get(
self.code_valide, 0
) > sco_codes.BUT_CODES_ORDERED.get(code_default, 0):
self.codes.insert(0, self.code_valide)
else:
self.codes.insert(1, self.code_valide)
def record(self, code: str, no_overwrite=False): def record(self, code: str, no_overwrite=False):
"""Enregistre le code""" """Enregistre le code"""

View File

@ -41,32 +41,21 @@ def show_etud(deca: DecisionsProposeesAnnee, read_only: bool = True) -> str:
Si pas read_only, menus sélection codes jury. Si pas read_only, menus sélection codes jury.
""" """
H = [] H = []
if deca.code_valide and not read_only:
erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>"""
else:
erase_span = ""
H.append("""<div class="but_section_annee">""") H.append("""<div class="but_section_annee">""")
if deca.jury_annuel: H.append(
H.append( f"""
f""" <div>
<div> <b>Décision de jury pour l'année :</b> {
<b>Décision de jury pour l'année :</b> { _gen_but_select("code_annee", deca.codes, deca.code_valide,
_gen_but_select("code_annee", deca.codes, deca.code_valide, disabled=True, klass="manual")
disabled=True, klass="manual") }
} <span>({'non ' if deca.code_valide is None else ''}enregistrée)</span>
<span>({'non ' if deca.code_valide is None else ''}enregistrée)</span> </div>
<span>{erase_span}</span>
</div>
""" """
) )
div_explanation = f"""<div class="but_explanation">{deca.explanation}</div>""" div_explanation = f"""<div class="but_explanation">{deca.explanation}</div>"""
else:
H.append("""<div><em>Pas de décision annuelle (sem. impair).</em></div>""")
div_explanation = ""
H.append("""</div>""") H.append("""</div>""")
formsemestre_1 = deca.formsemestre_impair formsemestre_1 = deca.formsemestre_impair
@ -245,9 +234,16 @@ def _gen_but_rcue(dec_rcue: DecisionsProposeesRCUE, niveau: ApcNiveau) -> str:
else "" else ""
) )
# Déjà enregistré ?
niveau_rcue_class = ""
if dec_rcue.code_valide is not None and dec_rcue.codes:
if dec_rcue.code_valide == dec_rcue.codes[0]:
niveau_rcue_class = "recorded"
else:
niveau_rcue_class = "recorded_different"
return f""" return f"""
<div class="but_niveau_rcue <div class="but_niveau_rcue {niveau_rcue_class}
{'recorded' if dec_rcue.code_valide is not None else ''}
"> ">
<div class="but_note with_scoplement"> <div class="but_note with_scoplement">
<div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div> <div>{scu.fmt_note(dec_rcue.rcue.moy_rcue)}</div>
@ -351,7 +347,7 @@ def jury_but_semestriel(
warning = "" warning = ""
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title="Validation BUT", page_title=f"Validation BUT S{formsemestre.semestre_id}",
formsemestre_id=formsemestre.id, formsemestre_id=formsemestre.id,
etudid=etud.id, etudid=etud.id,
cssstyles=("css/jury_but.css",), cssstyles=("css/jury_but.css",),
@ -376,21 +372,21 @@ def jury_but_semestriel(
{warning} {warning}
</div> </div>
<form method="POST"> <form method="post" id="jury_but">
""", """,
] ]
if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]): if (not read_only) and any([dec.code_valide for dec in decisions_ues.values()]):
erase_span = f"""<a href="{ erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase", url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id, scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre.id,
etudid=etud.id, only_one_sem=1)}" class="stdlink">effacer les décisions enregistrées</a>""" etudid=etud.id, only_one_sem=1)
}" class="stdlink">effacer les décisions enregistrées</a>"""
else: else:
erase_span = "Cet étudiant n'a aucune décision enregistrée pour ce semestre." erase_span = "Cet étudiant n'a aucune décision enregistrée pour ce semestre."
H.append( H.append(
f""" f"""
<div class="but_section_annee"> <div class="but_section_annee">
<span>{erase_span}</span>
</div> </div>
<div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div> <div><b>Unités d'enseignement de S{formsemestre.semestre_id}:</b></div>
""" """
@ -447,9 +443,10 @@ def jury_but_semestriel(
else: else:
H.append("""<div class="help">dernier semestre de la formation.</div>""") H.append("""<div class="help">dernier semestre de la formation.</div>""")
H.append( H.append(
""" f"""
<div class="but_buttons"> <div class="but_buttons">
<input type="submit" value="Enregistrer ces décisions"> <span><input type="submit" value="Enregistrer ces décisions"></span>
<span>{erase_span}</span>
</div> </div>
""" """
) )

View File

@ -180,8 +180,9 @@ class RegroupementCoherentUE:
return self.query_validations().count() > 0 return self.query_validations().count() > 0
def est_compensable(self): def est_compensable(self):
"""Vrai si ce RCUE est validable par compensation """Vrai si ce RCUE est validable (uniquement) par compensation
c'est à dire que sa moyenne est > 10 avec une UE < 10 c'est à dire que sa moyenne est > 10 avec une UE < 10.
Note: si ADM, est_compensable est faux.
""" """
return ( return (
(self.moy_rcue is not None) (self.moy_rcue is not None)

View File

@ -205,6 +205,20 @@ BUT_CODES_PASSAGE = {
PAS1NCI, PAS1NCI,
ATJ, ATJ,
} }
# les codes, du plus "défavorable" à l'étudiant au plus favorable:
# (valeur par défaut 0)
BUT_CODES_ORDERED = {
"NAR": 0,
"DEF": 0,
"AJ": 10,
"ATJ": 20,
"CMP": 50,
"ADC": 50,
"PASD": 50,
"PAS1NCI": 60,
"ADJ": 100,
"ADM": 100,
}
def code_semestre_validant(code: str) -> bool: def code_semestre_validant(code: str) -> bool:

View File

@ -22,7 +22,7 @@
margin-left: 32px; margin-left: 32px;
display: inline-grid; display: inline-grid;
grid-template-columns: repeat(4, auto); grid-template-columns: repeat(4, auto);
gap: 4px; gap: 8px;
} }
.but_annee_caption { .but_annee_caption {
@ -143,8 +143,14 @@ div.but_code {
div.but_niveau_ue.recorded, div.but_niveau_ue.recorded,
div.but_niveau_rcue.recorded { div.but_niveau_rcue.recorded {
border-color: rgb(136, 252, 136); border-color: rgb(0, 169, 0);
border-width: 2px; border-width: 3px;
}
div.but_niveau_ue.recorded_different,
div.but_niveau_rcue.recorded_different {
box-shadow: 0 0 0 3px red;
outline: dashed 3px rgb(0, 169, 0);
} }
div.but_niveau_ue.annee_prec { div.but_niveau_ue.annee_prec {
@ -160,6 +166,7 @@ div.but_buttons {
} }
div.but_buttons span { div.but_buttons span {
margin-left: 16px;
margin-right: 16px; margin-right: 16px;
} }

View File

@ -63,6 +63,8 @@ $(function () {
// ----- Etat du formulaire jury pour éviter sortie sans enregistrer // ----- Etat du formulaire jury pour éviter sortie sans enregistrer
let FORM_STATE = ""; let FORM_STATE = "";
let IS_SUBMITTING = false;
// Une chaine décrivant l'état du form // Une chaine décrivant l'état du form
function get_form_state() { function get_form_state() {
let codes = []; let codes = [];
@ -73,13 +75,19 @@ function get_form_state() {
$('document').ready(function () { $('document').ready(function () {
FORM_STATE = get_form_state(); FORM_STATE = get_form_state();
document.querySelector("form#jury_but").addEventListener('submit', jury_form_submit);
}); });
function is_modified() { function is_modified() {
return FORM_STATE != get_form_state(); return FORM_STATE != get_form_state();
} }
function jury_form_submit(event) {
IS_SUBMITTING = true;
}
window.addEventListener("beforeunload", function (e) { window.addEventListener("beforeunload", function (e) {
if (is_modified()) { if ((!IS_SUBMITTING) && is_modified()) {
var confirmationMessage = 'Changements non enregistrés !'; var confirmationMessage = 'Changements non enregistrés !';
(e || window.event).returnValue = confirmationMessage; (e || window.event).returnValue = confirmationMessage;
return confirmationMessage; return confirmationMessage;

View File

@ -2327,17 +2327,17 @@ def formsemestre_validation_but(
# provisoires avec NEXT et PREV # provisoires avec NEXT et PREV
try: try:
etudid = int(etudid) etudid = int(etudid)
except: except ValueError:
abort(404, "invalid etudid") abort(404, "invalid etudid")
read_only = not sco_permissions_check.can_validate_sem(formsemestre_id) read_only = not sco_permissions_check.can_validate_sem(formsemestre_id)
# --- Navigation # --- Navigation
prev = f"""{scu.EMO_PREV_ARROW}&nbsp;<a href="{url_for( prev_lnk = f"""{scu.EMO_PREV_ARROW}&nbsp;<a href="{url_for(
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept, "notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id, etudid="PREV" formsemestre_id=formsemestre_id, etudid="PREV"
)}" class="stdlink"">précédent</a> )}" class="stdlink"">précédent</a>
""" """
next = f"""<a href="{url_for( next_lnk = f"""<a href="{url_for(
"notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept, "notes.formsemestre_validation_but", scodoc_dept=g.scodoc_dept,
formsemestre_id=formsemestre_id, etudid="NEXT" formsemestre_id=formsemestre_id, etudid="NEXT"
)}" class="stdlink"">suivant</a>&nbsp;{scu.EMO_NEXT_ARROW} )}" class="stdlink"">suivant</a>&nbsp;{scu.EMO_NEXT_ARROW}
@ -2345,7 +2345,7 @@ def formsemestre_validation_but(
navigation_div = f""" navigation_div = f"""
<div class="but_navigation"> <div class="but_navigation">
<div class="prev"> <div class="prev">
{prev} {prev_lnk}
</div> </div>
<div class="back_list"> <div class="back_list">
<a href="{url_for( <a href="{url_for(
@ -2354,21 +2354,20 @@ def formsemestre_validation_but(
)}" class="stdlink">retour à la liste</a> )}" class="stdlink">retour à la liste</a>
</div> </div>
<div class="next"> <div class="next">
{next} {next_lnk}
</div> </div>
</div> </div>
""" """
H = [ H = [
html_sco_header.sco_header( html_sco_header.sco_header(
page_title="Validation BUT", page_title=f"Validation BUT S{formsemestre.semestre_id}",
formsemestre_id=formsemestre_id, formsemestre_id=formsemestre_id,
etudid=etudid, etudid=etudid,
cssstyles=("css/jury_but.css",), cssstyles=("css/jury_but.css",),
javascripts=("js/jury_but.js",), javascripts=("js/jury_but.js",),
), ),
f""" """<div class="jury_but">
<div class="jury_but">
""", """,
] ]
@ -2401,7 +2400,6 @@ def formsemestre_validation_but(
deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre) deca = jury_but.DecisionsProposeesAnnee(etud, formsemestre)
if len(deca.rcues_annee) == 0: if len(deca.rcues_annee) == 0:
# raise ScoValueError("année incomplète: pas de jury BUT annuel possible")
return jury_but_view.jury_but_semestriel( return jury_but_view.jury_but_semestriel(
formsemestre, etud, read_only, navigation_div=navigation_div formsemestre, etud, read_only, navigation_div=navigation_div
) )
@ -2421,7 +2419,7 @@ def formsemestre_validation_but(
warning = "" warning = ""
if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau): if len(deca.niveaux_competences) != len(deca.decisions_rcue_by_niveau):
if deca.a_cheval: if deca.a_cheval:
warning += f"""<div class="warning">Attention: regroupements RCUE warning += """<div class="warning">Attention: regroupements RCUE
entre années (redoublement).</div>""" entre années (redoublement).</div>"""
else: else:
warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)} warning += f"""<div class="warning">Attention: {len(deca.niveaux_competences)}
@ -2454,7 +2452,7 @@ def formsemestre_validation_but(
{warning} {warning}
</div> </div>
<form method="POST"> <form method="post" id="jury_but">
""" """
) )
@ -2467,6 +2465,10 @@ def formsemestre_validation_but(
Les champs entourés en vert sont enregistrés.</div>""" Les champs entourés en vert sont enregistrés.</div>"""
) )
else: else:
erase_span = f"""<a href="{
url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre_id,
etudid=deca.etud.id)}" class="stdlink">effacer décisions</a>"""
H.append( H.append(
f"""<div class="but_settings"> f"""<div class="but_settings">
<input type="checkbox" onchange="enable_manual_codes(this)"> <input type="checkbox" onchange="enable_manual_codes(this)">
@ -2477,7 +2479,8 @@ def formsemestre_validation_but(
</div> </div>
<div class="but_buttons"> <div class="but_buttons">
<input type="submit" value="Enregistrer ces décisions"> <span><input type="submit" value="Enregistrer ces décisions"></span>
<span>{erase_span}</span>
</div> </div>
""" """
) )
@ -2790,7 +2793,9 @@ def formsemestre_jury_but_erase(
explanation=f"""Les validations d'UE et autorisations de passage explanation=f"""Les validations d'UE et autorisations de passage
du semestre S{formsemestre.semestre_id} seront effacées.""" du semestre S{formsemestre.semestre_id} seront effacées."""
if only_one_sem if only_one_sem
else """Les validations de toutes les UE, RCUE (compétences) et année seront effacées.""", else """Les validations de toutes les UE, RCUE (compétences) et année seront effacées.
Les décisions de l'année scolaire précédente ne seront pas modifiées.
""",
cancel_url=dest_url, cancel_url=dest_url,
) )