Interface pour UE externes et éditions des validations

This commit is contained in:
Emmanuel Viennet 2023-06-30 17:26:41 +02:00
parent 35fb269a41
commit 3325b41690
11 changed files with 157 additions and 65 deletions

View File

@ -114,16 +114,16 @@ def _validation_ue_delete(etudid: int, validation_id: int):
# rattachées à un formsemestre) # rattachées à un formsemestre)
if not g.scodoc_dept: # accès API if not g.scodoc_dept: # accès API
if not current_user.has_permission(Permission.ScoEtudInscrit): if not current_user.has_permission(Permission.ScoEtudInscrit):
return json_error(403, "validation_delete: non autorise") return json_error(403, "opération non autorisée (117)")
else: else:
if validation.formsemestre: if validation.formsemestre:
if ( if (
validation.formsemestre.dept_id != g.scodoc_dept_id validation.formsemestre.dept_id != g.scodoc_dept_id
) or not validation.formsemestre.can_edit_jury(): ) or not validation.formsemestre.can_edit_jury():
return json_error(403, "validation_delete: non autorise") return json_error(403, "opération non autorisée (123)")
elif not current_user.has_permission(Permission.ScoEtudInscrit): elif not current_user.has_permission(Permission.ScoEtudInscrit):
# Validation non rattachée à un semestre: on doit être chef # Validation non rattachée à un semestre: on doit être chef
return json_error(403, "validation_delete: non autorise") return json_error(403, "opération non autorisée (126)")
log(f"validation_ue_delete: etuid={etudid} {validation}") log(f"validation_ue_delete: etuid={etudid} {validation}")
db.session.delete(validation) db.session.delete(validation)

View File

@ -158,8 +158,16 @@ class ApcValidationAnnee(db.Model):
if self.date if self.date
else "(sans date)" else "(sans date)"
) )
link = (
self.formsemestre.html_link_status(
label=f"{self.formsemestre.titre_formation(with_sem_idx=1)}",
title=self.formsemestre.titre_annee(),
)
if self.formsemestre
else "externe/antérieure"
)
return f"""Validation <b>année BUT{self.ordre}</b> émise par return f"""Validation <b>année BUT{self.ordre}</b> émise par
{self.formsemestre.html_link_status() if self.formsemestre else "-"} {link}
: <b>{self.code}</b> : <b>{self.code}</b>
{date_str} {date_str}
""" """

View File

@ -163,12 +163,12 @@ class FormSemestre(db.Model):
def __repr__(self): def __repr__(self):
return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>" return f"<{self.__class__.__name__} {self.id} {self.titre_annee()}>"
def html_link_status(self) -> str: def html_link_status(self, label=None, title=None) -> str:
"html link to status page" "html link to status page"
return f"""<a class="stdlink" href="{ return f"""<a class="stdlink" href="{
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept, url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept,
formsemestre_id=self.id,) formsemestre_id=self.id,)
}">{self.titre_mois()}</a> }" title="{title or ''}">{label or self.titre_mois()}</a>
""" """
@classmethod @classmethod

View File

@ -94,6 +94,14 @@ class ScolarFormSemestreValidation(db.Model):
if self.moy_ue is not None if self.moy_ue is not None
else "" else ""
) )
link = (
self.formsemestre.html_link_status(
label=f"{self.formsemestre.titre_formation(with_sem_idx=1)}",
title=self.formsemestre.titre_annee(),
)
if self.formsemestre
else "externe/antérieure"
)
return f"""Validation return f"""Validation
{'<span class="redboldtext">externe</span>' if self.is_external else ""} {'<span class="redboldtext">externe</span>' if self.is_external else ""}
de l'UE <b>{self.ue.acronyme}</b> de l'UE <b>{self.ue.acronyme}</b>
@ -101,9 +109,7 @@ class ScolarFormSemestreValidation(db.Model):
+ ", ".join([p.code for p in self.ue.parcours])) + ", ".join([p.code for p in self.ue.parcours]))
+ "</span>" + "</span>"
if self.ue.parcours else ""} if self.ue.parcours else ""}
de {self.ue.formation.acronyme} {("émise par " + link)}
{("émise par " + self.formsemestre.html_link_status())
if self.formsemestre else "externe/antérieure"}
: <b>{self.code}</b>{moyenne} : <b>{self.code}</b>{moyenne}
le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")} le {self.event_date.strftime("%d/%m/%Y")} à {self.event_date.strftime("%Hh%M")}
""" """
@ -149,10 +155,16 @@ class ScolarAutorisationInscription(db.Model):
def html(self) -> str: def html(self) -> str:
"Affichage html" "Affichage html"
link = (
self.origin_formsemestre.html_link_status(
label=f"{self.origin_formsemestre.titre_formation(with_sem_idx=1)}",
title=self.origin_formsemestre.titre_annee(),
)
if self.origin_formsemestre
else "externe/antérieure"
)
return f"""Autorisation de passage vers <b>S{self.semestre_id}</b> émise par return f"""Autorisation de passage vers <b>S{self.semestre_id}</b> émise par
{self.origin_formsemestre.html_link_status() {link}
if self.origin_formsemestre
else "-"}
le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")} le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")}
""" """

View File

@ -196,6 +196,8 @@ CODES_SEM_ATTENTES = {ATT, ATB, ATJ} # semestre en attente
CODES_SEM_REO = {NAR} # reorientation CODES_SEM_REO = {NAR} # reorientation
# Les codes d'UEs
CODES_JURY_UE = {ADM, CMP, ADJ, ADJR, ADSUP, AJ, ATJ, RAT, DEF, ABAN, DEM, UEBSL}
CODES_UE_VALIDES_DE_DROIT = {ADM, CMP} # validation "de droit" CODES_UE_VALIDES_DE_DROIT = {ADM, CMP} # validation "de droit"
CODES_UE_VALIDES = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR, ADSUP} CODES_UE_VALIDES = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR, ADSUP}
"UE validée" "UE validée"

View File

@ -398,7 +398,7 @@ def formsemestre_validation_etud(
selected_choice = choice selected_choice = choice
break break
if not selected_choice: if not selected_choice:
raise ValueError("code choix invalide ! (%s)" % codechoice) raise ValueError(f"code choix invalide ! ({codechoice})")
# #
Se.valide_decision(selected_choice) # enregistre Se.valide_decision(selected_choice) # enregistre
return _redirect_valid_choice( return _redirect_valid_choice(
@ -1132,6 +1132,7 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
}, },
) )
) )
ue_codes = sorted(codes_cursus.CODES_JURY_UE)
form_descr += [ form_descr += [
( (
"date", "date",
@ -1152,6 +1153,18 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
"title": "Moyenne (/20) obtenue dans cette UE:", "title": "Moyenne (/20) obtenue dans cette UE:",
}, },
), ),
(
"code_jury",
{
"input_type": "menu",
"title": "Code jury",
"explanation": " code donné par le jury (ADM si validée normalement)",
"allow_null": True,
"allowed_values": [""] + ue_codes,
"labels": ["-"] + ue_codes,
"default": ADM,
},
),
] ]
tf = TrivialFormulator( tf = TrivialFormulator(
request.base_url, request.base_url,
@ -1173,17 +1186,20 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
de {etud.html_link_fiche()} de {etud.html_link_fiche()}
</h2> </h2>
<p class="help">Utiliser cette page pour enregistrer une UE validée antérieurement, <p class="help">Utiliser cette page pour enregistrer des UEs validées antérieurement,
<em>dans un semestre hors ScoDoc</em>.</p> <em>dans un semestre hors ScoDoc</em>.</p>
<p class="expl"><b>Les UE validées dans ScoDoc sont déjà <p class="expl"><b>Les UE validées dans ScoDoc sont
automatiquement prises en compte</b>. Cette page n'est utile que pour les étudiants ayant automatiquement prises en compte</b>.
suivi un début de cursus dans <b>un autre établissement</b>, ou bien dans un semestre géré </p>
<b>sans ScoDoc</b> et qui <b>redouble</b> ce semestre <p>Cette page est surtout utile pour les étudiants ayant
(<em>pour les semestres précédents gérés avec ScoDoc, suivi un début de cursus dans <b>un autre établissement</b>, ou qui
passer par la page jury normale)</em>). ont suivi une UE à l'étranger ou dans un semestre géré <b>sans ScoDoc</b>.
</p>
<p>Pour les semestres précédents gérés avec ScoDoc, passer par la page jury normale.
</p>
<p>Notez que l'UE est validée, avec enregistrement immédiat de la décision et
l'attribution des ECTS si le code jury est validant (ADM).
</p> </p>
<p>Notez que l'UE est validée (ADM), avec enregistrement immédiat de la décision et
l'attribution des ECTS.</p>
<p>On ne peut valider ici que les UEs du cursus <b>{formation.titre}</b></p> <p>On ne peut valider ici que les UEs du cursus <b>{formation.titre}</b></p>
{_get_etud_ue_cap_html(etud, formsemestre)} {_get_etud_ue_cap_html(etud, formsemestre)}
@ -1221,12 +1237,16 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
else: else:
semestre_id = None semestre_id = None
if tf[2]["code_jury"] not in CODES_JURY_UE:
flash("Code UE invalide")
return flask.redirect(dest_url)
do_formsemestre_validate_previous_ue( do_formsemestre_validate_previous_ue(
formsemestre, formsemestre,
etud.id, etud.id,
tf[2]["ue_id"], tf[2]["ue_id"],
tf[2]["moy_ue"], tf[2]["moy_ue"],
tf[2]["date"], tf[2]["date"],
code=tf[2]["code_jury"],
semestre_id=semestre_id, semestre_id=semestre_id,
) )
flash("Validation d'UE enregistrée") flash("Validation d'UE enregistrée")
@ -1258,7 +1278,7 @@ def _get_etud_ue_cap_html(etud: Identite, formsemestre: FormSemestre) -> str:
<div class="help">Liste de toutes les UEs validées par {etud.html_link_fiche()}, <div class="help">Liste de toutes les UEs validées par {etud.html_link_fiche()},
sur des semestres ou déclarées comme "antérieures" (externes). sur des semestres ou déclarées comme "antérieures" (externes).
</div> </div>
<ul>""" <ul class="liste_validations">"""
] ]
for validation in validations: for validation in validations:
if validation.formsemestre_id is None: if validation.formsemestre_id is None:
@ -1267,17 +1287,20 @@ def _get_etud_ue_cap_html(etud: Identite, formsemestre: FormSemestre) -> str:
origine = f", du semestre {formsemestre.html_link_status()}" origine = f", du semestre {formsemestre.html_link_status()}"
if validation.semestre_id is not None: if validation.semestre_id is not None:
origine += f" (<b>S{validation.semestre_id}</b>)" origine += f" (<b>S{validation.semestre_id}</b>)"
H.append( H.append(f"""<li>{validation.html()}""")
f""" if validation.formsemestre.can_edit_jury():
<li>{validation.html()} H.append(
f"""
<form class="inline-form"> <form class="inline-form">
<button <button
data-v_id="{validation.id}" data-type="validation_ue" data-etudid="{etud.id}" data-v_id="{validation.id}" data-type="validation_ue" data-etudid="{etud.id}"
>effacer</button> >effacer</button>
</form> </form>
</li> """,
""", )
) else:
H.append(scu.icontag("lock_img", border="0", title="Semestre verrouillé"))
H.append("</li>")
H.append("</ul></div>") H.append("</ul></div>")
return "\n".join(H) return "\n".join(H)
@ -1300,7 +1323,7 @@ def do_formsemestre_validate_previous_ue(
ue: UniteEns = UniteEns.query.get_or_404(ue_id) ue: UniteEns = UniteEns.query.get_or_404(ue_id)
cnx = ndb.GetDBConnexion() cnx = ndb.GetDBConnexion()
if ue_coefficient != None: if ue_coefficient is not None:
sco_formsemestre.do_formsemestre_uecoef_edit_or_create( sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
cnx, formsemestre.id, ue_id, ue_coefficient cnx, formsemestre.id, ue_id, ue_coefficient
) )

View File

@ -59,11 +59,13 @@ from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_t
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
def _menu_scolarite(authuser, sem: dict, etudid: int): def _menu_scolarite(
authuser, formsemestre: FormSemestre, etudid: int, etat_inscription: str
):
"""HTML pour menu "scolarite" pour un etudiant dans un semestre. """HTML pour menu "scolarite" pour un etudiant dans un semestre.
Le contenu du menu depend des droits de l'utilisateur et de l'état de l'étudiant. Le contenu du menu depend des droits de l'utilisateur et de l'état de l'étudiant.
""" """
locked = not sem["etat"] locked = not formsemestre.etat
if locked: if locked:
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0") lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
return lockicon # no menu return lockicon # no menu
@ -71,10 +73,10 @@ def _menu_scolarite(authuser, sem: dict, etudid: int):
Permission.ScoEtudInscrit Permission.ScoEtudInscrit
) and not authuser.has_permission(Permission.ScoEtudChangeGroups): ) and not authuser.has_permission(Permission.ScoEtudChangeGroups):
return "" # no menu return "" # no menu
ins = sem["ins"]
args = {"etudid": etudid, "formsemestre_id": ins["formsemestre_id"]}
if ins["etat"] != "D": args = {"etudid": etudid, "formsemestre_id": formsemestre.id}
if etat_inscription != scu.DEMISSION:
dem_title = "Démission" dem_title = "Démission"
dem_url = "scolar.form_dem" dem_url = "scolar.form_dem"
else: else:
@ -82,14 +84,14 @@ def _menu_scolarite(authuser, sem: dict, etudid: int):
dem_url = "scolar.do_cancel_dem" dem_url = "scolar.do_cancel_dem"
# Note: seul un etudiant inscrit (I) peut devenir défaillant. # Note: seul un etudiant inscrit (I) peut devenir défaillant.
if ins["etat"] != codes_cursus.DEF: if etat_inscription != codes_cursus.DEF:
def_title = "Déclarer défaillance" def_title = "Déclarer défaillance"
def_url = "scolar.form_def" def_url = "scolar.form_def"
elif ins["etat"] == codes_cursus.DEF: elif etat_inscription == codes_cursus.DEF:
def_title = "Annuler la défaillance" def_title = "Annuler la défaillance"
def_url = "scolar.do_cancel_def" def_url = "scolar.do_cancel_def"
def_enabled = ( def_enabled = (
(ins["etat"] != "D") (etat_inscription != scu.DEMISSION)
and authuser.has_permission(Permission.ScoEtudInscrit) and authuser.has_permission(Permission.ScoEtudInscrit)
and not locked and not locked
) )
@ -128,6 +130,12 @@ def _menu_scolarite(authuser, sem: dict, etudid: int):
"enabled": authuser.has_permission(Permission.ScoEtudInscrit) "enabled": authuser.has_permission(Permission.ScoEtudInscrit)
and not locked, and not locked,
}, },
{
"title": "Gérer les validations d'UEs antérieures",
"endpoint": "notes.formsemestre_validate_previous_ue",
"args": args,
"enabled": formsemestre.can_edit_jury(),
},
{ {
"title": "Inscrire à un autre semestre", "title": "Inscrire à un autre semestre",
"endpoint": "notes.formsemestre_inscription_with_modules_form", "endpoint": "notes.formsemestre_inscription_with_modules_form",
@ -250,8 +258,8 @@ def ficheEtud(etudid=None):
info["last_formsemestre_id"] = "" info["last_formsemestre_id"] = ""
sem_info = {} sem_info = {}
for sem in info["sems"]: for sem in info["sems"]:
formsemestre: FormSemestre = FormSemestre.query.get(sem["formsemestre_id"])
if sem["ins"]["etat"] != scu.INSCRIT: if sem["ins"]["etat"] != scu.INSCRIT:
formsemestre: FormSemestre = FormSemestre.query.get(sem["formsemestre_id"])
descr, _ = etud_descr_situation_semestre( descr, _ = etud_descr_situation_semestre(
etudid, etudid,
formsemestre, formsemestre,
@ -283,7 +291,7 @@ def ficheEtud(etudid=None):
) )
grlink = ", ".join(grlinks) grlink = ", ".join(grlinks)
# infos ajoutées au semestre dans le parcours (groupe, menu) # infos ajoutées au semestre dans le parcours (groupe, menu)
menu = _menu_scolarite(authuser, sem, etudid) menu = _menu_scolarite(authuser, formsemestre, etudid, sem["ins"]["etat"])
if menu: if menu:
sem_info[sem["formsemestre_id"]] = ( sem_info[sem["formsemestre_id"]] = (
"<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>" "<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>"

View File

@ -7,3 +7,7 @@ div.jury_decisions_list div {
span.parcours { span.parcours {
color:blueviolet; color:blueviolet;
} }
div.ue_list_etud_validations ul.liste_validations li {
margin-bottom: 8px;
}

View File

@ -11,27 +11,30 @@ document.addEventListener("DOMContentLoaded", () => {
// Handle button click event here // Handle button click event here
event.preventDefault(); event.preventDefault();
const etudid = event.target.dataset.etudid; const etudid = event.target.dataset.etudid;
const v_id = event.target.dataset.v_id; const validation_id = event.target.dataset.v_id;
const validation_type = event.target.dataset.type; const validation_type = event.target.dataset.type;
if (confirm("Supprimer cette validation ?")) { if (confirm("Supprimer cette validation ?")) {
fetch( delete_validation(etudid, validation_type, validation_id);
`${SCO_URL}/../api/etudiant/${etudid}/jury/${validation_type}/${v_id}/delete`,
{
method: "POST",
}
).then((response) => {
// Handle the response
if (response.ok) {
location.reload();
} else {
throw new Error("Request failed");
}
});
} }
}); });
}); });
}); });
async function delete_validation(etudid, validation_type, validation_id) {
const response = await fetch(
`${SCO_URL}/../api/etudiant/${etudid}/jury/${validation_type}/${validation_id}/delete`,
{
method: "POST",
}
);
if (response.ok) {
location.reload();
} else {
const data = await response.json();
sco_error_message("erreur: " + data.message);
}
}
function update_ue_list() { function update_ue_list() {
var ue_id = $("#tf_ue_id")[0].value; var ue_id = $("#tf_ue_id")[0].value;
if (ue_id) { if (ue_id) {

View File

@ -34,6 +34,32 @@ quelle que soit leur origine.</p>
{% endif %} {% endif %}
</form> </form>
</div> </div>
<div class="sco_box">
<div class="sco_box_title">Autres actions:</div>
<ul>
<li><a class="stdlink" href="{{
url_for('notes.jury_delete_manual',
scodoc_dept=g.scodoc_dept,
etudid=etud.id
)
}}">effacer les décisions une à une</a>
</li>
{% if formsemestre_origine is not none %}
<li><a class="stdlink" href="{{
url_for('notes.formsemestre_jury_but_erase',
scodoc_dept=g.scodoc_dept, formsemestre_id=formsemestre_origine.id,
etudid=etud.id, only_one_sem=1)
}}">
effacer seulement les décisions émises par le semestre
{{formsemestre_origine.titre_formation(with_sem_idx=1)|safe}}
(efface aussi la décision annuelle)
</a>
</li>
{% endif %}
</ul>
</div>
{% endif %} {% endif %}

View File

@ -2534,21 +2534,20 @@ def formsemestre_validation_but(
</div>""" </div>"""
) )
else: else:
erase_span = f"""<a href="{ erase_span = f"""
url_for("notes.formsemestre_jury_but_erase",
scodoc_dept=g.scodoc_dept, formsemestre_id=deca.formsemestre.id,
etudid=deca.etud.id)}" class="stdlink"
title="efface décisions issues des jurys de cette année"
>effacer décisions de ce jury</a>
<a style="margin-left: 16px;" class="stdlink" <a style="margin-left: 16px;" class="stdlink"
href="{ href="{
url_for("notes.erase_decisions_annee_formation", url_for("notes.erase_decisions_annee_formation",
scodoc_dept=g.scodoc_dept, formation_id=deca.formsemestre.formation.id, scodoc_dept=g.scodoc_dept, formation_id=deca.formsemestre.formation.id,
etudid=deca.etud.id, annee=deca.annee_but)}" etudid=deca.etud.id, annee=deca.annee_but, formsemestre_id=formsemestre_id)}"
title="efface toutes décisions concernant le BUT{deca.annee_but} >effacer des décisions de jury</a>
de cet étudiant (même extérieures ou issues d'un redoublement)"
>effacer toutes ses décisions de BUT{deca.annee_but}</a> <a style="margin-left: 16px;" class="stdlink"
href="{
url_for("notes.formsemestre_validate_previous_ue",
scodoc_dept=g.scodoc_dept,
etudid=deca.etud.id, formsemestre_id=formsemestre_id)}"
>enregistrer des UEs antérieures</a>
""" """
H.append( H.append(
f"""<div class="but_settings"> f"""<div class="but_settings">
@ -2966,6 +2965,12 @@ def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
) )
) )
validations = jury.erase_decisions_annee_formation(etud, formation, annee) validations = jury.erase_decisions_annee_formation(etud, formation, annee)
formsemestre_origine_id = request.args.get("formsemestre_id")
formsemestre_origine = (
FormSemestre.query.get_or_404(formsemestre_origine_id)
if formsemestre_origine_id
else None
)
return render_template( return render_template(
"jury/erase_decisions_annee_formation.j2", "jury/erase_decisions_annee_formation.j2",
annee=annee, annee=annee,
@ -2974,6 +2979,7 @@ def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
), ),
etud=etud, etud=etud,
formation=formation, formation=formation,
formsemestre_origine=formsemestre_origine,
validations=validations, validations=validations,
sco=ScoData(), sco=ScoData(),
title=f"Effacer décisions de jury {etud.nom} - année {annee}", title=f"Effacer décisions de jury {etud.nom} - année {annee}",