Skip to content
Snippets Groups Projects
Commit 3325b416 authored by Emmanuel Viennet's avatar Emmanuel Viennet
Browse files

Interface pour UE externes et éditions des validations

parent 35fb269a
Branches
No related tags found
No related merge requests found
......@@ -114,16 +114,16 @@ def _validation_ue_delete(etudid: int, validation_id: int):
# rattachées à un formsemestre)
if not g.scodoc_dept: # accès API
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:
if validation.formsemestre:
if (
validation.formsemestre.dept_id != g.scodoc_dept_id
) 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):
# 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}")
db.session.delete(validation)
......
......@@ -158,8 +158,16 @@ class ApcValidationAnnee(db.Model):
if self.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
{self.formsemestre.html_link_status() if self.formsemestre else "-"}
{link}
: <b>{self.code}</b>
{date_str}
"""
......
......@@ -163,12 +163,12 @@ class FormSemestre(db.Model):
def __repr__(self):
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"
return f"""<a class="stdlink" href="{
url_for("notes.formsemestre_status", scodoc_dept=g.scodoc_dept,
formsemestre_id=self.id,)
}">{self.titre_mois()}</a>
}" title="{title or ''}">{label or self.titre_mois()}</a>
"""
@classmethod
......
......@@ -94,6 +94,14 @@ class ScolarFormSemestreValidation(db.Model):
if self.moy_ue is not None
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
{'<span class="redboldtext">externe</span>' if self.is_external else ""}
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]))
+ "</span>"
if self.ue.parcours else ""}
de {self.ue.formation.acronyme}
{("émise par " + self.formsemestre.html_link_status())
if self.formsemestre else "externe/antérieure"}
{("émise par " + link)}
: <b>{self.code}</b>{moyenne}
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:
"Affichage html"
return f"""Autorisation de passage vers <b>S{self.semestre_id}</b> émise par
{self.origin_formsemestre.html_link_status()
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 "-"}
else "externe/antérieure"
)
return f"""Autorisation de passage vers <b>S{self.semestre_id}</b> émise par
{link}
le {self.date.strftime("%d/%m/%Y")} à {self.date.strftime("%Hh%M")}
"""
......
......@@ -196,6 +196,8 @@ CODES_SEM_ATTENTES = {ATT, ATB, ATJ} # semestre en attente
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 = CODES_UE_VALIDES_DE_DROIT | {ADJ, ADJR, ADSUP}
"UE validée"
......
......@@ -398,7 +398,7 @@ def formsemestre_validation_etud(
selected_choice = choice
break
if not selected_choice:
raise ValueError("code choix invalide ! (%s)" % codechoice)
raise ValueError(f"code choix invalide ! ({codechoice})")
#
Se.valide_decision(selected_choice) # enregistre
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 += [
(
"date",
......@@ -1152,6 +1153,18 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
"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(
request.base_url,
......@@ -1173,17 +1186,20 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
de {etud.html_link_fiche()}
</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>
<p class="expl"><b>Les UE validées dans ScoDoc sont déjà
automatiquement prises en compte</b>. Cette page n'est utile que pour les étudiants ayant
suivi un début de cursus dans <b>un autre établissement</b>, ou bien dans un semestre géré
<b>sans ScoDoc</b> et qui <b>redouble</b> ce semestre
(<em>pour les semestres précédents gérés avec ScoDoc,
passer par la page jury normale)</em>).
<p class="expl"><b>Les UE validées dans ScoDoc sont
automatiquement prises en compte</b>.
</p>
<p>Cette page est surtout utile pour les étudiants ayant
suivi un début de cursus dans <b>un autre établissement</b>, ou qui
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>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>
{_get_etud_ue_cap_html(etud, formsemestre)}
......@@ -1221,12 +1237,16 @@ def formsemestre_validate_previous_ue(formsemestre: FormSemestre, etud: Identite
else:
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(
formsemestre,
etud.id,
tf[2]["ue_id"],
tf[2]["moy_ue"],
tf[2]["date"],
code=tf[2]["code_jury"],
semestre_id=semestre_id,
)
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()},
sur des semestres ou déclarées comme "antérieures" (externes).
</div>
<ul>"""
<ul class="liste_validations">"""
]
for validation in validations:
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()}"
if validation.semestre_id is not None:
origine += f" (<b>S{validation.semestre_id}</b>)"
H.append(f"""<li>{validation.html()}""")
if validation.formsemestre.can_edit_jury():
H.append(
f"""
<li>{validation.html()}
<form class="inline-form">
<button
data-v_id="{validation.id}" data-type="validation_ue" data-etudid="{etud.id}"
>effacer</button>
</form>
</li>
""",
)
else:
H.append(scu.icontag("lock_img", border="0", title="Semestre verrouillé"))
H.append("</li>")
H.append("</ul></div>")
return "\n".join(H)
......@@ -1300,7 +1323,7 @@ def do_formsemestre_validate_previous_ue(
ue: UniteEns = UniteEns.query.get_or_404(ue_id)
cnx = ndb.GetDBConnexion()
if ue_coefficient != None:
if ue_coefficient is not None:
sco_formsemestre.do_formsemestre_uecoef_edit_or_create(
cnx, formsemestre.id, ue_id, ue_coefficient
)
......
......@@ -59,11 +59,13 @@ from app.scodoc.sco_formsemestre_validation import formsemestre_recap_parcours_t
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.
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:
lockicon = scu.icontag("lock32_img", title="verrouillé", border="0")
return lockicon # no menu
......@@ -71,10 +73,10 @@ def _menu_scolarite(authuser, sem: dict, etudid: int):
Permission.ScoEtudInscrit
) and not authuser.has_permission(Permission.ScoEtudChangeGroups):
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_url = "scolar.form_dem"
else:
......@@ -82,14 +84,14 @@ def _menu_scolarite(authuser, sem: dict, etudid: int):
dem_url = "scolar.do_cancel_dem"
# 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_url = "scolar.form_def"
elif ins["etat"] == codes_cursus.DEF:
elif etat_inscription == codes_cursus.DEF:
def_title = "Annuler la défaillance"
def_url = "scolar.do_cancel_def"
def_enabled = (
(ins["etat"] != "D")
(etat_inscription != scu.DEMISSION)
and authuser.has_permission(Permission.ScoEtudInscrit)
and not locked
)
......@@ -128,6 +130,12 @@ def _menu_scolarite(authuser, sem: dict, etudid: int):
"enabled": authuser.has_permission(Permission.ScoEtudInscrit)
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",
"endpoint": "notes.formsemestre_inscription_with_modules_form",
......@@ -250,8 +258,8 @@ def ficheEtud(etudid=None):
info["last_formsemestre_id"] = ""
sem_info = {}
for sem in info["sems"]:
if sem["ins"]["etat"] != scu.INSCRIT:
formsemestre: FormSemestre = FormSemestre.query.get(sem["formsemestre_id"])
if sem["ins"]["etat"] != scu.INSCRIT:
descr, _ = etud_descr_situation_semestre(
etudid,
formsemestre,
......@@ -283,7 +291,7 @@ def ficheEtud(etudid=None):
)
grlink = ", ".join(grlinks)
# 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:
sem_info[sem["formsemestre_id"]] = (
"<table><tr><td>" + grlink + "</td><td>" + menu + "</td></tr></table>"
......
......@@ -7,3 +7,7 @@ div.jury_decisions_list div {
span.parcours {
color:blueviolet;
}
div.ue_list_etud_validations ul.liste_validations li {
margin-bottom: 8px;
}
\ No newline at end of file
......@@ -11,26 +11,29 @@ document.addEventListener("DOMContentLoaded", () => {
// Handle button click event here
event.preventDefault();
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;
if (confirm("Supprimer cette validation ?")) {
fetch(
`${SCO_URL}/../api/etudiant/${etudid}/jury/${validation_type}/${v_id}/delete`,
delete_validation(etudid, validation_type, validation_id);
}
});
});
});
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",
}
).then((response) => {
// Handle the response
);
if (response.ok) {
location.reload();
} else {
throw new Error("Request failed");
const data = await response.json();
sco_error_message("erreur: " + data.message);
}
});
}
});
});
});
function update_ue_list() {
var ue_id = $("#tf_ue_id")[0].value;
......
......@@ -34,6 +34,32 @@ quelle que soit leur origine.</p>
{% endif %}
</form>
</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 %}
......
......@@ -2534,21 +2534,20 @@ def formsemestre_validation_but(
</div>"""
)
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"
title="efface décisions issues des jurys de cette année"
>effacer décisions de ce jury</a>
erase_span = f"""
<a style="margin-left: 16px;" class="stdlink"
href="{
url_for("notes.erase_decisions_annee_formation",
scodoc_dept=g.scodoc_dept, formation_id=deca.formsemestre.formation.id,
etudid=deca.etud.id, annee=deca.annee_but)}"
title="efface toutes décisions concernant le BUT{deca.annee_but}
de cet étudiant (même extérieures ou issues d'un redoublement)"
>effacer toutes ses décisions de BUT{deca.annee_but}</a>
etudid=deca.etud.id, annee=deca.annee_but, formsemestre_id=formsemestre_id)}"
>effacer des décisions de jury</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(
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)
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(
"jury/erase_decisions_annee_formation.j2",
annee=annee,
......@@ -2974,6 +2979,7 @@ def erase_decisions_annee_formation(etudid: int, formation_id: int, annee: int):
),
etud=etud,
formation=formation,
formsemestre_origine=formsemestre_origine,
validations=validations,
sco=ScoData(),
title=f"Effacer décisions de jury {etud.nom} - année {annee}",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment