forked from ScoDoc/ScoDoc
Merge pull request 'Corrections de bugs / tickets Assidiutés' (#916) from iziram/ScoDoc:assiduites_fixes into master
Reviewed-on: ScoDoc/ScoDoc#916
This commit is contained in:
commit
963c09976b
@ -62,6 +62,11 @@ class AjoutAssiOrJustForm(FlaskForm):
|
|||||||
if field:
|
if field:
|
||||||
field.errors.append(err_msg)
|
field.errors.append(err_msg)
|
||||||
|
|
||||||
|
def disable_all(self):
|
||||||
|
"Disable all fields"
|
||||||
|
for field in self:
|
||||||
|
field.render_kw = {"disabled": True}
|
||||||
|
|
||||||
date_debut = StringField(
|
date_debut = StringField(
|
||||||
"Date de début",
|
"Date de début",
|
||||||
validators=[validators.Length(max=10)],
|
validators=[validators.Length(max=10)],
|
||||||
@ -175,36 +180,3 @@ class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
|||||||
validators=[DataRequired(message="This field is required.")],
|
validators=[DataRequired(message="This field is required.")],
|
||||||
)
|
)
|
||||||
fichiers = MultipleFileField(label="Ajouter des fichiers")
|
fichiers = MultipleFileField(label="Ajouter des fichiers")
|
||||||
|
|
||||||
|
|
||||||
class ChoixDateForm(FlaskForm):
|
|
||||||
"""
|
|
||||||
Formulaire de choix de date
|
|
||||||
(utilisé par la page de choix de date
|
|
||||||
si la date courante n'est pas dans le semestre)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"Init form, adding a filed for our error messages"
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.ok = True
|
|
||||||
self.error_messages: list[str] = [] # used to report our errors
|
|
||||||
|
|
||||||
def set_error(self, err_msg, field=None):
|
|
||||||
"Set error message both in form and field"
|
|
||||||
self.ok = False
|
|
||||||
self.error_messages.append(err_msg)
|
|
||||||
if field:
|
|
||||||
field.errors.append(err_msg)
|
|
||||||
|
|
||||||
date = StringField(
|
|
||||||
"Date",
|
|
||||||
validators=[validators.Length(max=10)],
|
|
||||||
render_kw={
|
|
||||||
"class": "datepicker",
|
|
||||||
"size": 10,
|
|
||||||
"id": "date",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
submit = SubmitField("Enregistrer")
|
|
||||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
|
||||||
|
122
app/forms/assiduite/edit_assiduite_etud.py
Normal file
122
app/forms/assiduite/edit_assiduite_etud.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
""" """
|
||||||
|
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import (
|
||||||
|
StringField,
|
||||||
|
SelectField,
|
||||||
|
RadioField,
|
||||||
|
TextAreaField,
|
||||||
|
validators,
|
||||||
|
SubmitField,
|
||||||
|
)
|
||||||
|
from app.scodoc.sco_utils import EtatAssiduite
|
||||||
|
|
||||||
|
|
||||||
|
class EditAssiForm(FlaskForm):
|
||||||
|
"""
|
||||||
|
Formulaire de modification d'une assiduité
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"Init form, adding a filed for our error messages"
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.ok = True
|
||||||
|
self.error_messages: list[str] = [] # used to report our errors
|
||||||
|
|
||||||
|
def set_error(self, err_msg, field=None):
|
||||||
|
"Set error message both in form and field"
|
||||||
|
self.ok = False
|
||||||
|
self.error_messages.append(err_msg)
|
||||||
|
if field:
|
||||||
|
field.errors.append(err_msg)
|
||||||
|
|
||||||
|
def disable_all(self):
|
||||||
|
"Disable all fields"
|
||||||
|
for field in self:
|
||||||
|
field.render_kw = {"disabled": True}
|
||||||
|
|
||||||
|
assi_etat = RadioField(
|
||||||
|
"État:",
|
||||||
|
choices=[
|
||||||
|
(EtatAssiduite.ABSENT.value, EtatAssiduite.ABSENT.version_lisible()),
|
||||||
|
(EtatAssiduite.RETARD.value, EtatAssiduite.RETARD.version_lisible()),
|
||||||
|
(EtatAssiduite.PRESENT.value, EtatAssiduite.PRESENT.version_lisible()),
|
||||||
|
],
|
||||||
|
default="absent",
|
||||||
|
validators=[
|
||||||
|
validators.DataRequired("spécifiez le type d'évènement à signaler"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
modimpl = SelectField(
|
||||||
|
"Module",
|
||||||
|
choices={}, # will be populated dynamically
|
||||||
|
)
|
||||||
|
description = TextAreaField(
|
||||||
|
"Description",
|
||||||
|
render_kw={
|
||||||
|
"id": "description",
|
||||||
|
"cols": 75,
|
||||||
|
"rows": 4,
|
||||||
|
"maxlength": 500,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
date_debut = StringField(
|
||||||
|
"Date de début",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "assi_date_debut",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
heure_debut = StringField(
|
||||||
|
"Heure début",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_debut",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
heure_fin = StringField(
|
||||||
|
"Heure fin",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
date_fin = StringField(
|
||||||
|
"Date de fin",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "assi_date_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry_date = StringField(
|
||||||
|
"Date de dépôt ou saisie",
|
||||||
|
validators=[validators.Length(max=10)],
|
||||||
|
render_kw={
|
||||||
|
"class": "datepicker",
|
||||||
|
"size": 10,
|
||||||
|
"id": "entry_date",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
entry_time = StringField(
|
||||||
|
"Heure dépôt",
|
||||||
|
default="",
|
||||||
|
validators=[validators.Length(max=5)],
|
||||||
|
render_kw={
|
||||||
|
"class": "timepicker",
|
||||||
|
"size": 5,
|
||||||
|
"id": "assi_heure_fin",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
submit = SubmitField("Enregistrer")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
@ -360,6 +360,16 @@ class Assiduite(ScoDocModel):
|
|||||||
|
|
||||||
return "Module non spécifié" if traduire else None
|
return "Module non spécifié" if traduire else None
|
||||||
|
|
||||||
|
def get_moduleimpl_id(self) -> int | str | None:
|
||||||
|
"""
|
||||||
|
Retourne le ModuleImpl associé à l'assiduité
|
||||||
|
"""
|
||||||
|
if self.moduleimpl_id is not None:
|
||||||
|
return self.moduleimpl_id
|
||||||
|
if self.external_data is not None and "module" in self.external_data:
|
||||||
|
return self.external_data["module"]
|
||||||
|
return None
|
||||||
|
|
||||||
def get_saisie(self) -> str:
|
def get_saisie(self) -> str:
|
||||||
"""
|
"""
|
||||||
retourne le texte "saisie le <date> par <User>"
|
retourne le texte "saisie le <date> par <User>"
|
||||||
@ -395,6 +405,14 @@ class Assiduite(ScoDocModel):
|
|||||||
if force:
|
if force:
|
||||||
raise ScoValueError("Module non renseigné")
|
raise ScoValueError("Module non renseigné")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_assiduite(cls, assiduite_id: int) -> "Assiduite":
|
||||||
|
"""Assiduité ou 404, cherche uniquement dans le département courant"""
|
||||||
|
query = Assiduite.query.filter_by(id=assiduite_id)
|
||||||
|
if g.scodoc_dept:
|
||||||
|
query = query.join(Identite).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
|
return query.first_or_404()
|
||||||
|
|
||||||
|
|
||||||
class Justificatif(ScoDocModel):
|
class Justificatif(ScoDocModel):
|
||||||
"""
|
"""
|
||||||
@ -685,10 +703,14 @@ def is_period_conflicting(
|
|||||||
date_fin: datetime,
|
date_fin: datetime,
|
||||||
collection: Query,
|
collection: Query,
|
||||||
collection_cls: Assiduite | Justificatif,
|
collection_cls: Assiduite | Justificatif,
|
||||||
|
obj_id: int = -1,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Vérifie si une date n'entre pas en collision
|
Vérifie si une date n'entre pas en collision
|
||||||
avec les justificatifs ou assiduites déjà présentes
|
avec les justificatifs ou assiduites déjà présentes
|
||||||
|
|
||||||
|
On peut donner un objet_id pour exclure un objet de la vérification
|
||||||
|
(utile pour les modifications)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# On s'assure que les dates soient avec TimeZone
|
# On s'assure que les dates soient avec TimeZone
|
||||||
@ -696,7 +718,9 @@ def is_period_conflicting(
|
|||||||
date_fin = localize_datetime(date_fin)
|
date_fin = localize_datetime(date_fin)
|
||||||
|
|
||||||
count: int = collection.filter(
|
count: int = collection.filter(
|
||||||
collection_cls.date_debut < date_fin, collection_cls.date_fin > date_debut
|
collection_cls.date_debut < date_fin,
|
||||||
|
collection_cls.date_fin > date_debut,
|
||||||
|
collection_cls.id != obj_id,
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
return count > 0
|
return count > 0
|
||||||
|
@ -427,7 +427,9 @@ class JourEval(sco_gen_cal.Jour):
|
|||||||
)
|
)
|
||||||
heure_fin_txt = e.date_fin.strftime(scu.TIME_FMT) if e.date_fin else ""
|
heure_fin_txt = e.date_fin.strftime(scu.TIME_FMT) if e.date_fin else ""
|
||||||
|
|
||||||
title = f"{e.description or e.moduleimpl.module.titre_str()}"
|
title = f"{e.moduleimpl.module.titre_str()}"
|
||||||
|
if e.description:
|
||||||
|
title += f" : {e.description}"
|
||||||
if heure_debut_txt:
|
if heure_debut_txt:
|
||||||
title += f" de {heure_debut_txt} à {heure_fin_txt}"
|
title += f" de {heure_debut_txt} à {heure_fin_txt}"
|
||||||
|
|
||||||
|
@ -978,7 +978,7 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
|
|||||||
if not authuser.has_permission(Permission.AbsChange):
|
if not authuser.has_permission(Permission.AbsChange):
|
||||||
return ""
|
return ""
|
||||||
return f"""
|
return f"""
|
||||||
<button onclick="window.location='{
|
<a class="stdlink" href="{
|
||||||
url_for(
|
url_for(
|
||||||
"assiduites.signal_assiduites_group",
|
"assiduites.signal_assiduites_group",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
@ -987,7 +987,7 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None):
|
|||||||
formsemestre_id=groups_infos.formsemestre_id,
|
formsemestre_id=groups_infos.formsemestre_id,
|
||||||
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id
|
||||||
)
|
)
|
||||||
}';">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</button>
|
}">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@ -1000,14 +1000,14 @@ def form_choix_saisie_semaine(groups_infos):
|
|||||||
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
moduleimpl_id = query_args.get("moduleimpl_id", [None])[0]
|
||||||
semaine = datetime.datetime.now().strftime("%G-W%V")
|
semaine = datetime.datetime.now().strftime("%G-W%V")
|
||||||
return f"""
|
return f"""
|
||||||
<button onclick="window.location='{url_for(
|
<a class="stdlink" href="{url_for(
|
||||||
"assiduites.signal_assiduites_hebdo",
|
"assiduites.signal_assiduites_hebdo",
|
||||||
group_ids=",".join(map(str,groups_infos.group_ids)),
|
group_ids=",".join(map(str,groups_infos.group_ids)),
|
||||||
week=semaine,
|
week=semaine,
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
formsemestre_id=groups_infos.formsemestre_id,
|
formsemestre_id=groups_infos.formsemestre_id,
|
||||||
moduleimpl_id=moduleimpl_id
|
moduleimpl_id=moduleimpl_id
|
||||||
)}';">Saisie à la semaine</button>
|
)}">Saisie à la semaine (semaine {''.join(semaine[-2:])})</a>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -118,9 +118,8 @@
|
|||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
border-width: 6px;
|
border: 10px solid transparent;
|
||||||
border-style: solid;
|
width: 100%;
|
||||||
border-color: transparent transparent #f9f9f9 transparent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.assiduite-bubble::after {
|
.assiduite-bubble::after {
|
||||||
|
@ -53,7 +53,6 @@ async function async_get(path, success, errors) {
|
|||||||
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
* @param {CallableFunction} errors fonction à effectuer en cas d'échec
|
||||||
*/
|
*/
|
||||||
async function async_post(path, data, success, errors) {
|
async function async_post(path, data, success, errors) {
|
||||||
// console.log("async_post " + path);
|
|
||||||
let response;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(path, {
|
response = await fetch(path, {
|
||||||
@ -655,9 +654,46 @@ function mettreToutLeMonde(etat, el = null) {
|
|||||||
if (!confirm("Effacer tout les évènements correspondant à cette plage ?")) {
|
if (!confirm("Effacer tout les évènements correspondant à cette plage ?")) {
|
||||||
return; // annulation
|
return; // annulation
|
||||||
}
|
}
|
||||||
const assiduites_id = lignesEtuds
|
// On récupère les lignes avec une seule assiduité
|
||||||
|
let assiduites_id = lignesEtuds
|
||||||
.filter((e) => e.getAttribute("type") == "edition")
|
.filter((e) => e.getAttribute("type") == "edition")
|
||||||
.map((e) => Number(e.getAttribute("assiduite_id")));
|
.map((e) => Number(e.getAttribute("assiduite_id")));
|
||||||
|
|
||||||
|
// On récupère les assiduités conflictuelles mais qui sont comprisent
|
||||||
|
// Dans la plage de suppression
|
||||||
|
const unDeleted = {};
|
||||||
|
lignesEtuds
|
||||||
|
.filter((e) => e.getAttribute("type") == "conflit")
|
||||||
|
.forEach((e) => {
|
||||||
|
const etud = etuds.get(Number(e.getAttribute("etudid")));
|
||||||
|
// On récupère les assiduités couvertent par la plage de suppression
|
||||||
|
etud.assiduites.forEach((a) => {
|
||||||
|
const date_debut = new Date(a.date_debut);
|
||||||
|
const date_fin = new Date(a.date_fin);
|
||||||
|
// On prend en compte uniquement les assiduités conflictuelles
|
||||||
|
// (qui intersectent la plage de suppression)
|
||||||
|
if (
|
||||||
|
Date.intersect(
|
||||||
|
{ deb: deb, fin: fin },
|
||||||
|
{ deb: date_debut, fin: date_fin }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Si l'assiduité est couverte par la plage de suppression
|
||||||
|
// On l'ajoute à la liste des assiduités à supprimer.
|
||||||
|
if (
|
||||||
|
date_debut.isBetween(deb, fin, "[]") &&
|
||||||
|
date_fin.isBetween(deb, fin, "[]")
|
||||||
|
) {
|
||||||
|
assiduites_id.push(a.assiduite_id);
|
||||||
|
}
|
||||||
|
// Sinon on ajoute l'étudiant à la liste des étudiants non gérés
|
||||||
|
else {
|
||||||
|
unDeleted[a.etudid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
afficheLoader();
|
afficheLoader();
|
||||||
|
|
||||||
async_post(
|
async_post(
|
||||||
@ -672,6 +708,28 @@ function mettreToutLeMonde(etat, el = null) {
|
|||||||
console.error(data.errors);
|
console.error(data.errors);
|
||||||
}
|
}
|
||||||
envoiToastTous("remove", assiduites_id.length);
|
envoiToastTous("remove", assiduites_id.length);
|
||||||
|
if (Object.keys(unDeleted).length == 0) return;
|
||||||
|
|
||||||
|
let unDeletedEtuds = `
|
||||||
|
<ul>
|
||||||
|
${Object.keys(unDeleted)
|
||||||
|
.map((etudid) => {
|
||||||
|
const etud = etuds.get(Number(etudid));
|
||||||
|
return `<li>${etud.civilite}. ${etud.nom.toUpperCase()} ${
|
||||||
|
etud.prenom
|
||||||
|
}</li>`;
|
||||||
|
})
|
||||||
|
.join("")}
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<p>Les assiduités des étudiants suivants n'ont pas été supprimées car elles ne sont pas incluses dans la plage de suppression :</p>
|
||||||
|
${unDeletedEtuds}
|
||||||
|
`;
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.innerHTML = html;
|
||||||
|
openAlertModal("Assiduité non supprimée", div);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error("Erreur lors de la suppression de l'assiduité", error);
|
console.error("Erreur lors de la suppression de l'assiduité", error);
|
||||||
@ -681,14 +739,10 @@ function mettreToutLeMonde(etat, el = null) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Création / édition des assiduités
|
// Création
|
||||||
const assiduitesACreer = lignesEtuds
|
const assiduitesACreer = lignesEtuds
|
||||||
.filter((e) => e.getAttribute("type") == "creation")
|
.filter((e) => e.getAttribute("type") == "creation")
|
||||||
.map((e) => Number(e.getAttribute("etudid")));
|
.map((e) => Number(e.getAttribute("etudid")));
|
||||||
const assiduitesAEditer = lignesEtuds
|
|
||||||
.filter((e) => e.getAttribute("type") == "edition")
|
|
||||||
.map((e) => Number(e.getAttribute("assiduite_id")));
|
|
||||||
|
|
||||||
// création
|
// création
|
||||||
|
|
||||||
const promiseCreate = async_post(
|
const promiseCreate = async_post(
|
||||||
@ -705,29 +759,15 @@ function mettreToutLeMonde(etat, el = null) {
|
|||||||
console.error("Erreur lors de la création de l'assiduité", error);
|
console.error("Erreur lors de la création de l'assiduité", error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const promiseEdit = async_post(
|
|
||||||
`../../api/assiduites/edit`,
|
|
||||||
assiduitesAEditer.map((assiduite_id) => {
|
|
||||||
return { ...assiduiteObjet, assiduite_id };
|
|
||||||
}),
|
|
||||||
async (data) => {
|
|
||||||
if (data.errors.length > 0) {
|
|
||||||
console.error(data.errors);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error("Erreur lors de l'édition de l'assiduité", error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Affiche un loader
|
// Affiche un loader
|
||||||
afficheLoader();
|
afficheLoader();
|
||||||
|
|
||||||
Promise.all([promiseCreate, promiseEdit]).then(async () => {
|
Promise.all([promiseCreate]).then(async () => {
|
||||||
retirerLoader();
|
retirerLoader();
|
||||||
await recupAssiduites(etuds, $("#date").datepicker("getDate"));
|
await recupAssiduites(etuds, $("#date").datepicker("getDate"));
|
||||||
creerTousLesEtudiants(etuds);
|
creerTousLesEtudiants(etuds);
|
||||||
envoiToastTous(etat, assiduitesACreer.length + assiduitesAEditer.length);
|
envoiToastTous(etat, assiduitesACreer.length);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -888,21 +928,12 @@ function setupAssiduiteBubble(el, assiduite) {
|
|||||||
const infos = document.createElement("a");
|
const infos = document.createElement("a");
|
||||||
infos.className = "";
|
infos.className = "";
|
||||||
infos.textContent = `ℹ️`;
|
infos.textContent = `ℹ️`;
|
||||||
infos.title = "Cliquez pour plus d'informations";
|
infos.title = "Détails / Modifier";
|
||||||
infos.target = "_blank";
|
infos.target = "_blank";
|
||||||
infos.href = `tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assiduite.assiduite_id}`;
|
infos.href = `edit_assiduite_etud/${assiduite.assiduite_id}`;
|
||||||
|
|
||||||
// Ajout d'un lien pour modifier l'assiduité
|
|
||||||
const modifs = document.createElement("a");
|
|
||||||
modifs.className = "";
|
|
||||||
modifs.textContent = `📝`;
|
|
||||||
modifs.title = "Cliquez pour modifier l'assiduité";
|
|
||||||
modifs.target = "_blank";
|
|
||||||
modifs.href = `tableau_assiduite_actions?type=assiduite&action=modifier&obj_id=${assiduite.assiduite_id}`;
|
|
||||||
|
|
||||||
const actionsDiv = document.createElement("div");
|
const actionsDiv = document.createElement("div");
|
||||||
actionsDiv.className = "assiduite-actions";
|
actionsDiv.className = "assiduite-actions";
|
||||||
actionsDiv.appendChild(modifs);
|
|
||||||
actionsDiv.appendChild(infos);
|
actionsDiv.appendChild(infos);
|
||||||
bubble.appendChild(actionsDiv);
|
bubble.appendChild(actionsDiv);
|
||||||
|
|
||||||
|
@ -600,33 +600,22 @@ class RowAssiJusti(tb.Row):
|
|||||||
url: str
|
url: str
|
||||||
html: list[str] = []
|
html: list[str] = []
|
||||||
|
|
||||||
# Détails
|
|
||||||
url = url_for(
|
|
||||||
"assiduites.tableau_assiduite_actions",
|
|
||||||
type=self.ligne["type"],
|
|
||||||
action="details",
|
|
||||||
obj_id=self.ligne["obj_id"],
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
|
||||||
)
|
|
||||||
html.append(f'<a title="Détails" href="{url}">ℹ️</a>')
|
|
||||||
|
|
||||||
# Modifier
|
|
||||||
if self.ligne["type"] == "justificatif":
|
if self.ligne["type"] == "justificatif":
|
||||||
|
# Détails/Modifier assiduité
|
||||||
url = url_for(
|
url = url_for(
|
||||||
"assiduites.edit_justificatif_etud",
|
"assiduites.edit_justificatif_etud",
|
||||||
justif_id=self.ligne["obj_id"],
|
justif_id=self.ligne["obj_id"],
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
back_url=request.url,
|
|
||||||
)
|
)
|
||||||
|
html.append(f'<a title="Détails/Modifier" href="{url}">ℹ️</a>')
|
||||||
else:
|
else:
|
||||||
|
# Détails/Modifier assiduité
|
||||||
url = url_for(
|
url = url_for(
|
||||||
"assiduites.tableau_assiduite_actions",
|
"assiduites.edit_assiduite_etud",
|
||||||
type=self.ligne["type"],
|
assiduite_id=self.ligne["obj_id"],
|
||||||
action="modifier",
|
|
||||||
obj_id=self.ligne["obj_id"],
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
)
|
)
|
||||||
html.append(f'<a title="Modifier" href="{url}">📝</a>')
|
html.append(f'<a title="Détails/Modifier" href="{url}">ℹ️</a>')
|
||||||
|
|
||||||
# Supprimer
|
# Supprimer
|
||||||
url = url_for(
|
url = url_for(
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from flask import g, url_for
|
from flask import g, url_for
|
||||||
|
from flask_login import current_user
|
||||||
from app import log
|
from app import log
|
||||||
from app.models import FormSemestre, Identite, Justificatif
|
from app.models import FormSemestre, Identite, Justificatif
|
||||||
from app.tables import table_builder as tb
|
from app.tables import table_builder as tb
|
||||||
@ -15,6 +16,7 @@ from app.scodoc import sco_preferences
|
|||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
import app.scodoc.sco_assiduites as scass
|
import app.scodoc.sco_assiduites as scass
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
|
from app.scodoc.sco_permissions import Permission
|
||||||
|
|
||||||
|
|
||||||
class TableAssi(tb.Table):
|
class TableAssi(tb.Table):
|
||||||
@ -171,6 +173,20 @@ class RowAssi(tb.Row):
|
|||||||
"justificatifs", "Justificatifs", fmt_num(compte_justificatifs.count())
|
"justificatifs", "Justificatifs", fmt_num(compte_justificatifs.count())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if current_user.has_permission(Permission.AbsChange):
|
||||||
|
ajout_url: str = url_for(
|
||||||
|
"assiduites.ajout_assiduite_etud",
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=etud.id,
|
||||||
|
formsemestre_id=self.table.formsemestre.id,
|
||||||
|
)
|
||||||
|
self.add_cell(
|
||||||
|
"lien_ajout",
|
||||||
|
"",
|
||||||
|
f"<a href='{ajout_url}' class='stdlink'>signaler assiduité</a>",
|
||||||
|
no_excel=True,
|
||||||
|
)
|
||||||
|
|
||||||
def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]:
|
def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]:
|
||||||
"""
|
"""
|
||||||
Renvoie le comptage (dans la métrique du département) des différents états
|
Renvoie le comptage (dans la métrique du département) des différents états
|
||||||
|
@ -44,11 +44,33 @@ div.submit > input {
|
|||||||
</style>
|
</style>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<h2>{{title|safe}}</h2>
|
<h2>{{title|safe}}</h2>
|
||||||
|
{% if readonly %}
|
||||||
|
<h3 class="rouge">Vous n'avez pas la permission de modifier ce justificatif</h3>
|
||||||
|
{% endif %}
|
||||||
{% if justif %}
|
{% if justif %}
|
||||||
|
<div class="informations">
|
||||||
|
|
||||||
<div class="info-saisie">
|
<div class="info-saisie">
|
||||||
Saisie par {{justif.user.get_prenomnom() if justif.user else "inconnu"}}
|
<span>Saisie par {{justif.saisie_par}} le {{justif.entry_date}}</span>
|
||||||
le {{justif.entry_date.strftime(scu.DATEATIME_FMT) if justif.entry_date else "?"}}
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Assiduités concernées: </span>
|
||||||
|
{% if justif.justification.assiduites %}
|
||||||
|
<ul>
|
||||||
|
{% for assi in justif.justification.assiduites %}
|
||||||
|
<li><a href="{{url_for('assiduites.edit_assiduite_etud',
|
||||||
|
assiduite_id=assi.assiduite_id, scodoc_dept=g.scodoc_dept)
|
||||||
|
}}" target="_blank">{{assi.etat}} du {{assi.date_debut}} au
|
||||||
|
{{assi.date_fin}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<span class="text">Aucune</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@ -110,7 +132,9 @@ div.submit > input {
|
|||||||
{% for filename in filenames %}
|
{% for filename in filenames %}
|
||||||
<li><span data-justif_id="{{justif.id}}" class="suppr_fichier_just"
|
<li><span data-justif_id="{{justif.id}}" class="suppr_fichier_just"
|
||||||
>{{scu.icontag("delete_img", alt="supprimer", title="Supprimer")|safe}}</span>
|
>{{scu.icontag("delete_img", alt="supprimer", title="Supprimer")|safe}}</span>
|
||||||
{{filename}}</li>
|
<a href="{{url_for('apiweb.justif_export',justif_id=justif.justif_id,
|
||||||
|
filename=filename, scodoc_dept=g.scodoc_dept)}}">{{filename}}</a>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -126,11 +150,28 @@ div.submit > input {
|
|||||||
<span class="help" style="margin-left: 12px;">laisser vide pour date courante</span>
|
<span class="help" style="margin-left: 12px;">laisser vide pour date courante</span>
|
||||||
{{ render_field_errors(form, 'entry_date') }}
|
{{ render_field_errors(form, 'entry_date') }}
|
||||||
|
|
||||||
|
{% if readonly == False %}
|
||||||
{# Submit #}
|
{# Submit #}
|
||||||
<div class="submit">
|
<div class="submit">
|
||||||
{{ form.submit }} {{ form.cancel }}
|
{{ form.submit }} {{ form.cancel }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="info-row">
|
||||||
|
<a
|
||||||
|
style="color:red;"
|
||||||
|
href="{{url_for(
|
||||||
|
'assiduites.tableau_assiduite_actions',
|
||||||
|
type='justificatif',
|
||||||
|
action='supprimer',
|
||||||
|
obj_id=justif.justif_id,
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
)}}"
|
||||||
|
>Supprimer le justificatif</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
@ -242,7 +242,7 @@ Calendrier de l'assiduité
|
|||||||
document.querySelectorAll('[assi_id]').forEach((el, i) => {
|
document.querySelectorAll('[assi_id]').forEach((el, i) => {
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', () => {
|
||||||
const assi_id = el.getAttribute('assi_id');
|
const assi_id = el.getAttribute('assi_id');
|
||||||
window.open(`${SCO_URL}Assiduites/tableau_assiduite_actions?type=assiduite&action=details&obj_id=${assi_id}`);
|
window.open(`${SCO_URL}Assiduites/edit_assiduite_etud/${assi_id}`);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
180
app/templates/assiduites/pages/edit_assiduite_etud.j2
Normal file
180
app/templates/assiduites/pages/edit_assiduite_etud.j2
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
{# Ajout d'une "assiduité" sur un étudiant #}
|
||||||
|
|
||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
{% import 'wtf.j2' as wtf %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{super()}}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.info-row {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#assi_etat{
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-etat {
|
||||||
|
font-size: 110%;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: rgb(253, 234, 210);
|
||||||
|
border: 1px solid grey;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-saisie {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<div class="tab-content">
|
||||||
|
<h2>Détails Assiduité concernant {{etud.html_link_fiche()|safe}}</h2>
|
||||||
|
|
||||||
|
<div id="informations">
|
||||||
|
<div class="info-saisie">
|
||||||
|
<span>Saisie par {{objet.saisie_par}} le {{objet.entry_date}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Période :</span> du <b>{{objet.date_debut}}</b> au <b>{{objet.date_fin}}</b>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Module :</span> {{objet.module}}
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">État de l'assiduité :</span><span class="info-etat">{{objet.etat}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Description:</span>
|
||||||
|
{% if objet.description != "" and objet.description is not None %}
|
||||||
|
<span class="text">{{objet.description}}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text fontred">Pas de description</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{# Affichage des justificatifs si assiduité justifiée #}
|
||||||
|
{% if objet.etat != "Présence" %}
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">Justifiée: </span>
|
||||||
|
{% if objet.justification.est_just %}
|
||||||
|
<span class="text">Oui</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text fontred">Non</span>
|
||||||
|
{% if not objet.justification.justificatifs %}
|
||||||
|
<a
|
||||||
|
href="{{url_for(
|
||||||
|
'assiduites.tableau_assiduite_actions',
|
||||||
|
type='assiduite',
|
||||||
|
action='justifier',
|
||||||
|
obj_id=objet.assiduite_id,
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
)}}"
|
||||||
|
>Justifier l'assiduité</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
{% if not objet.justification.justificatifs %}
|
||||||
|
<span class="text info-label">Pas de justificatif associé</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text info-label">Justificatifs associés:</span>
|
||||||
|
<ul>
|
||||||
|
{% for justi in objet.justification.justificatifs %}
|
||||||
|
<li>
|
||||||
|
<a href="{{url_for('assiduites.edit_justificatif_etud',
|
||||||
|
justif_id=justi.justif_id,scodoc_dept=g.scodoc_dept)}}"
|
||||||
|
target="_blank" rel="noopener noreferrer" style="{{'color:red;' if justi.etat != 'Valide'}}">Justificatif {{justi.etat}} du {{justi.date_debut}} au
|
||||||
|
{{justi.date_fin}}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if readonly != True %}
|
||||||
|
<h2 style="margin-top: 24px;">Modification de l'assiduité</h2>
|
||||||
|
{% for err_msg in form.error_messages %}
|
||||||
|
<div class="wtf-error-messages">
|
||||||
|
{{ err_msg }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<form id="edit-assiduite-form" method="post">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{# Type d'évènement #}
|
||||||
|
<div class="radio-assi_etat">
|
||||||
|
{{ form.assi_etat.label }}
|
||||||
|
{{ form.assi_etat() }}
|
||||||
|
</div>
|
||||||
|
<div class="dates-heures">
|
||||||
|
{{ form.date_debut.label }} : {{ form.date_debut }}
|
||||||
|
à {{ form.heure_debut }}
|
||||||
|
{{ render_field_errors(form, 'date_debut') }}
|
||||||
|
{{ render_field_errors(form, 'heure_debut') }}
|
||||||
|
<br>
|
||||||
|
{{ form.date_fin.label }} : {{ form.date_fin }}
|
||||||
|
à {{ form.heure_fin }}
|
||||||
|
{{ render_field_errors(form, 'date_fin') }}
|
||||||
|
{{ render_field_errors(form, 'heure_fin') }}
|
||||||
|
<br>
|
||||||
|
{{ form.entry_date.label }} : {{ form.entry_date }} à {{ form.entry_time }}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
{# Menu module #}
|
||||||
|
<div class="select-module">
|
||||||
|
{{ form.modimpl.label }} :
|
||||||
|
{{ form.modimpl }}
|
||||||
|
{{ render_field_errors(form, 'modimpl') }}
|
||||||
|
</div>
|
||||||
|
{# Description #}
|
||||||
|
<div>
|
||||||
|
<div>{{ form.description.label }}</div>
|
||||||
|
{{ form.description() }}
|
||||||
|
{{ render_field_errors(form, 'description') }}
|
||||||
|
</div>
|
||||||
|
{# Submit #}
|
||||||
|
<div class="submit info-row">
|
||||||
|
{{ form.submit }} {{ form.cancel }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
<div class="info-row">
|
||||||
|
<a
|
||||||
|
style="color:red;"
|
||||||
|
href="{{url_for(
|
||||||
|
'assiduites.tableau_assiduite_actions',
|
||||||
|
type='assiduite',
|
||||||
|
action='supprimer',
|
||||||
|
obj_id=objet.assiduite_id,
|
||||||
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
)}}"
|
||||||
|
>Supprimer l'assiduité</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<h3 class="rouge">Vous n'avez pas la permission de modifier cette assiduité</h3>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock app_content %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
{% include "sco_timepicker.j2" %}
|
||||||
|
{% endblock scripts %}
|
@ -571,7 +571,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let toCreate = []; // [{etudid:<int>}]
|
let toCreate = []; // [{etudid:<int>}]
|
||||||
let toEdit = [];// [{etudid:<int>, assiduite_id:<int>}]
|
|
||||||
|
|
||||||
tds.forEach((td) => {
|
tds.forEach((td) => {
|
||||||
// on ne touche pas aux conflits
|
// on ne touche pas aux conflits
|
||||||
@ -585,8 +584,6 @@
|
|||||||
const assiduite_id = td.getAttribute("assiduite_id");
|
const assiduite_id = td.getAttribute("assiduite_id");
|
||||||
if (assiduite_id == "") {
|
if (assiduite_id == "") {
|
||||||
toCreate.push({ etudid: etudid });
|
toCreate.push({ etudid: etudid });
|
||||||
} else {
|
|
||||||
toEdit.push({ etudid: etudid, assiduite_id: Number(assiduite_id) });
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -598,19 +595,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modification
|
|
||||||
toEdit = toEdit.map((el) => {
|
|
||||||
return {
|
|
||||||
...assi,
|
|
||||||
etudid: el.etudid,
|
|
||||||
assiduite_id: el.assiduite_id,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Appel API
|
// Appel API
|
||||||
let counts = {
|
let counts = {
|
||||||
create: toCreate.length,
|
create: toCreate.length,
|
||||||
edit: toEdit.length
|
|
||||||
}
|
}
|
||||||
const promiseCreate = async_post(
|
const promiseCreate = async_post(
|
||||||
`../../api/assiduites/create`,
|
`../../api/assiduites/create`,
|
||||||
@ -633,35 +620,13 @@
|
|||||||
console.error("Erreur lors de la création de l'assiduité", error);
|
console.error("Erreur lors de la création de l'assiduité", error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const promiseEdit = async_post(
|
|
||||||
`../../api/assiduites/edit`,
|
|
||||||
toEdit,
|
|
||||||
async (data) => {
|
|
||||||
if (data.errors.length > 0) {
|
|
||||||
console.error(data.errors);
|
|
||||||
data.errors.forEach((err) => {
|
|
||||||
let obj = toEdit[err.indice];
|
|
||||||
let etu = etuds.find((el) => el.id == obj.etudid);
|
|
||||||
|
|
||||||
const text = document.createTextNode(`Erreur pour ${etu.nom} ${etu.prenom} : ${err.message}`);
|
|
||||||
const toast = generateToast(text, "var(--color-error)");
|
|
||||||
pushToast(toast);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
counts.edit = data.success.length;
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.error("Erreur lors de l'édition de l'assiduité", error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Affiche un loader
|
// Affiche un loader
|
||||||
afficheLoader();
|
afficheLoader();
|
||||||
|
|
||||||
Promise.all([promiseCreate, promiseEdit]).then(async () => {
|
Promise.all([promiseCreate]).then(async () => {
|
||||||
retirerLoader();
|
retirerLoader();
|
||||||
await recupAssiduitesHebdo(updateTable);
|
await recupAssiduitesHebdo(updateTable);
|
||||||
envoiToastTous("present", counts.create + counts.edit);
|
envoiToastTous("present", counts.create);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ function getWidth(start, end) {
|
|||||||
|
|
||||||
const duration = (endTime - startTime) / 1000 / 60;
|
const duration = (endTime - startTime) / 1000 / 60;
|
||||||
|
|
||||||
const percent = (duration / (t_end * 60 - t_start * 60)) * 100;
|
const percent = Math.min((duration / (t_end * 60 - t_start * 60)) * 100, 100);
|
||||||
return percent + "%";
|
return percent + "%";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ from flask_login import current_user
|
|||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
|
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
from app import db, log
|
from app import db, log
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
@ -48,8 +49,8 @@ from app.forms.assiduite.ajout_assiduite_etud import (
|
|||||||
AjoutAssiOrJustForm,
|
AjoutAssiOrJustForm,
|
||||||
AjoutAssiduiteEtudForm,
|
AjoutAssiduiteEtudForm,
|
||||||
AjoutJustificatifEtudForm,
|
AjoutJustificatifEtudForm,
|
||||||
ChoixDateForm,
|
|
||||||
)
|
)
|
||||||
|
from app.forms.assiduite.edit_assiduite_etud import EditAssiForm
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Assiduite,
|
Assiduite,
|
||||||
Departement,
|
Departement,
|
||||||
@ -65,7 +66,7 @@ from app.models import (
|
|||||||
from app.scodoc.codes_cursus import UE_STANDARD
|
from app.scodoc.codes_cursus import UE_STANDARD
|
||||||
|
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models.assiduites import get_assiduites_justif
|
from app.models.assiduites import get_assiduites_justif, is_period_conflicting
|
||||||
from app.tables.list_etuds import RowEtud, TableEtud
|
from app.tables.list_etuds import RowEtud, TableEtud
|
||||||
import app.tables.liste_assiduites as liste_assi
|
import app.tables.liste_assiduites as liste_assi
|
||||||
|
|
||||||
@ -225,6 +226,18 @@ def ajout_assiduite_etud() -> str | Response:
|
|||||||
etudid: int = request.args.get("etudid", -1)
|
etudid: int = request.args.get("etudid", -1)
|
||||||
etud = Identite.get_etud(etudid)
|
etud = Identite.get_etud(etudid)
|
||||||
|
|
||||||
|
formsemestre_id = request.args.get("formsemestre_id", None)
|
||||||
|
|
||||||
|
# Gestion du semestre
|
||||||
|
formsemestre: FormSemestre | None = None
|
||||||
|
sems_etud: list[FormSemestre] = etud.get_formsemestres()
|
||||||
|
if formsemestre_id:
|
||||||
|
formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||||
|
formsemestre = formsemestre if formsemestre in sems_etud else None
|
||||||
|
else:
|
||||||
|
formsemestre = [sem for sem in sems_etud if sem.est_courant()]
|
||||||
|
formsemestre = formsemestre[0] if formsemestre else None
|
||||||
|
|
||||||
# Gestion évaluations (appel à la page depuis les évaluations)
|
# Gestion évaluations (appel à la page depuis les évaluations)
|
||||||
evaluation_id: int | None = request.args.get("evaluation_id")
|
evaluation_id: int | None = request.args.get("evaluation_id")
|
||||||
saisie_eval = evaluation_id is not None
|
saisie_eval = evaluation_id is not None
|
||||||
@ -246,26 +259,22 @@ def ajout_assiduite_etud() -> str | Response:
|
|||||||
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
||||||
choices: OrderedDict = OrderedDict()
|
choices: OrderedDict = OrderedDict()
|
||||||
choices[""] = [("", "Non spécifié"), ("autre", "Autre module (pas dans la liste)")]
|
choices[""] = [("", "Non spécifié"), ("autre", "Autre module (pas dans la liste)")]
|
||||||
for formsemestre_id in modimpls_by_formsemestre:
|
|
||||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
|
||||||
|
|
||||||
# indique le nom du semestre dans le menu (optgroup)
|
# indique le nom du semestre dans le menu (optgroup)
|
||||||
group_name: str = formsemestre.titre_annee()
|
group_name: str = formsemestre.titre_annee()
|
||||||
choices[group_name] = [
|
choices[group_name] = [
|
||||||
(m.id, f"{m.module.code} {m.module.abbrev or m.module.titre or ''}")
|
(m.id, f"{m.module.code} {m.module.abbrev or m.module.titre or ''}")
|
||||||
for m in modimpls_by_formsemestre[formsemestre_id]
|
for m in modimpls_by_formsemestre[formsemestre.id]
|
||||||
if m.module.ue.type == UE_STANDARD
|
if m.module.ue.type == UE_STANDARD
|
||||||
]
|
]
|
||||||
|
|
||||||
if formsemestre.est_courant():
|
|
||||||
choices.move_to_end(group_name, last=False)
|
|
||||||
choices.move_to_end("", last=False)
|
choices.move_to_end("", last=False)
|
||||||
form.modimpl.choices = choices
|
form.modimpl.choices = choices
|
||||||
force_options: dict = None
|
force_options: dict = None
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if form.cancel.data: # cancel button
|
if form.cancel.data: # cancel button
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
ok = _record_assiduite_etud(etud, form)
|
ok = _record_assiduite_etud(etud, form, formsemestre=formsemestre)
|
||||||
if ok:
|
if ok:
|
||||||
flash("enregistré")
|
flash("enregistré")
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
@ -293,7 +302,7 @@ def ajout_assiduite_etud() -> str | Response:
|
|||||||
form=form,
|
form=form,
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
redirect_url=redirect_url,
|
redirect_url=redirect_url,
|
||||||
sco=ScoData(etud),
|
sco=ScoData(etud, formsemestre=formsemestre),
|
||||||
tableau=tableau,
|
tableau=tableau,
|
||||||
scu=scu,
|
scu=scu,
|
||||||
)
|
)
|
||||||
@ -301,7 +310,9 @@ def ajout_assiduite_etud() -> str | Response:
|
|||||||
|
|
||||||
def _get_dates_from_assi_form(
|
def _get_dates_from_assi_form(
|
||||||
form: AjoutAssiOrJustForm,
|
form: AjoutAssiOrJustForm,
|
||||||
|
etud: Identite,
|
||||||
from_justif: bool = False,
|
from_justif: bool = False,
|
||||||
|
formsemestre: FormSemestre | None = None,
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
bool, datetime.datetime | None, datetime.datetime | None, datetime.datetime | None
|
bool, datetime.datetime | None, datetime.datetime | None, datetime.datetime | None
|
||||||
]:
|
]:
|
||||||
@ -393,6 +404,41 @@ def _get_dates_from_assi_form(
|
|||||||
dt_debut_tz_server = dt_debut_tz_server.replace(hour=0, minute=0)
|
dt_debut_tz_server = dt_debut_tz_server.replace(hour=0, minute=0)
|
||||||
dt_fin_tz_server = dt_fin_tz_server.replace(hour=23, minute=59)
|
dt_fin_tz_server = dt_fin_tz_server.replace(hour=23, minute=59)
|
||||||
|
|
||||||
|
# Vérification dates contenu dans un semestre de l'étudiant
|
||||||
|
dates_semestres: list[tuple[datetime.date, datetime.date]] = (
|
||||||
|
[(sem.date_debut, sem.date_fin) for sem in etud.get_formsemestres()]
|
||||||
|
if formsemestre is None
|
||||||
|
else [(formsemestre.date_debut, formsemestre.date_fin)]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Vérification date début
|
||||||
|
if not any(
|
||||||
|
[
|
||||||
|
dt_debut_tz_server.date() >= deb and dt_debut_tz_server.date() <= fin
|
||||||
|
for deb, fin in dates_semestres
|
||||||
|
]
|
||||||
|
):
|
||||||
|
form.set_error(
|
||||||
|
"La date de début n'appartient à aucun semestre de l'étudiant"
|
||||||
|
if formsemestre is None
|
||||||
|
else "La date de début n'appartient pas au semestre",
|
||||||
|
form.date_debut,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Vérification date fin
|
||||||
|
if form.date_fin.data and not any(
|
||||||
|
[
|
||||||
|
dt_fin_tz_server.date() >= deb and dt_fin_tz_server.date() <= fin
|
||||||
|
for deb, fin in dates_semestres
|
||||||
|
]
|
||||||
|
):
|
||||||
|
form.set_error(
|
||||||
|
"La date de fin n'appartient à aucun semestre de l'étudiant"
|
||||||
|
if not formsemestre
|
||||||
|
else "La date de fin n'appartient pas au semestre",
|
||||||
|
form.date_fin,
|
||||||
|
)
|
||||||
|
|
||||||
dt_entry_date_tz_server = (
|
dt_entry_date_tz_server = (
|
||||||
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
||||||
)
|
)
|
||||||
@ -402,6 +448,7 @@ def _get_dates_from_assi_form(
|
|||||||
def _record_assiduite_etud(
|
def _record_assiduite_etud(
|
||||||
etud: Identite,
|
etud: Identite,
|
||||||
form: AjoutAssiduiteEtudForm,
|
form: AjoutAssiduiteEtudForm,
|
||||||
|
formsemestre: FormSemestre | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Enregistre les données du formulaire de saisie assiduité.
|
"""Enregistre les données du formulaire de saisie assiduité.
|
||||||
Returns ok if successfully recorded, else put error info in the form.
|
Returns ok if successfully recorded, else put error info in the form.
|
||||||
@ -415,7 +462,7 @@ def _record_assiduite_etud(
|
|||||||
dt_debut_tz_server,
|
dt_debut_tz_server,
|
||||||
dt_fin_tz_server,
|
dt_fin_tz_server,
|
||||||
dt_entry_date_tz_server,
|
dt_entry_date_tz_server,
|
||||||
) = _get_dates_from_assi_form(form)
|
) = _get_dates_from_assi_form(form, etud, formsemestre=formsemestre)
|
||||||
# Le module (avec "autre")
|
# Le module (avec "autre")
|
||||||
mod_data = form.modimpl.data
|
mod_data = form.modimpl.data
|
||||||
if mod_data:
|
if mod_data:
|
||||||
@ -492,10 +539,8 @@ def _record_assiduite_etud(
|
|||||||
assi: Assiduite = conflits.first()
|
assi: Assiduite = conflits.first()
|
||||||
|
|
||||||
lien: str = url_for(
|
lien: str = url_for(
|
||||||
"assiduites.tableau_assiduite_actions",
|
"assiduites.edit_assiduite_etud",
|
||||||
type="assiduite",
|
assiuite_id=assi.assiduite_id,
|
||||||
action="details",
|
|
||||||
obj_id=assi.assiduite_id,
|
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -566,7 +611,7 @@ def bilan_etud():
|
|||||||
|
|
||||||
@bp.route("/edit_justificatif_etud/<int:justif_id>", methods=["GET", "POST"])
|
@bp.route("/edit_justificatif_etud/<int:justif_id>", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.ScoView)
|
||||||
def edit_justificatif_etud(justif_id: int):
|
def edit_justificatif_etud(justif_id: int):
|
||||||
"""
|
"""
|
||||||
Edition d'un justificatif.
|
Edition d'un justificatif.
|
||||||
@ -578,8 +623,19 @@ def edit_justificatif_etud(justif_id: int):
|
|||||||
Returns:
|
Returns:
|
||||||
str: l'html généré
|
str: l'html généré
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
justif = Justificatif.get_justificatif(justif_id)
|
justif = Justificatif.get_justificatif(justif_id)
|
||||||
|
except HTTPException:
|
||||||
|
flash("Justificatif invalide")
|
||||||
|
return redirect(url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept))
|
||||||
|
|
||||||
|
readonly = not current_user.has_permission(Permission.AbsChange)
|
||||||
|
|
||||||
form = AjoutJustificatifEtudForm(obj=justif)
|
form = AjoutJustificatifEtudForm(obj=justif)
|
||||||
|
|
||||||
|
if readonly:
|
||||||
|
form.disable_all()
|
||||||
|
|
||||||
# Set the default value for the etat field
|
# Set the default value for the etat field
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
form.date_debut.data = justif.date_debut.strftime(scu.DATE_FMT)
|
form.date_debut.data = justif.date_debut.strftime(scu.DATE_FMT)
|
||||||
@ -606,7 +662,9 @@ def edit_justificatif_etud(justif_id: int):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if form.cancel.data: # cancel button
|
if form.cancel.data or not current_user.has_permission(
|
||||||
|
Permission.AbsChange
|
||||||
|
): # cancel button
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
if _record_justificatif_etud(justif.etudiant, form, justif):
|
if _record_justificatif_etud(justif.etudiant, form, justif):
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
@ -621,12 +679,13 @@ def edit_justificatif_etud(justif_id: int):
|
|||||||
etud=justif.etudiant,
|
etud=justif.etudiant,
|
||||||
filenames=filenames,
|
filenames=filenames,
|
||||||
form=form,
|
form=form,
|
||||||
justif=justif,
|
justif=_preparer_objet("justificatif", justif),
|
||||||
nb_files=nb_files,
|
nb_files=nb_files,
|
||||||
title=f"Modification justificatif absence de {justif.etudiant.html_link_fiche()}",
|
title=f"Modification justificatif absence de {justif.etudiant.html_link_fiche()}",
|
||||||
redirect_url=redirect_url,
|
redirect_url=redirect_url,
|
||||||
sco=ScoData(justif.etudiant),
|
sco=ScoData(justif.etudiant),
|
||||||
scu=scu,
|
scu=scu,
|
||||||
|
readonly=not current_user.has_permission(Permission.AbsChange),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -682,32 +741,6 @@ def ajout_justificatif_etud():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _verif_date_form_justif(
|
|
||||||
form: AjoutJustificatifEtudForm, deb: datetime.datetime, fin: datetime.datetime
|
|
||||||
) -> tuple[datetime.datetime, datetime.datetime]:
|
|
||||||
"""Gère les cas suivants :
|
|
||||||
- si on indique seulement une date de debut : journée 0h-23h59
|
|
||||||
- si on indique date de debut et heures : journée +heure deb/fin
|
|
||||||
(déjà géré par _get_dates_from_assi_form)
|
|
||||||
- Si on indique une date de début et de fin sans heures : Journées 0h-23h59
|
|
||||||
- Si on indique une date de début et de fin avec heures : On fait un objet avec
|
|
||||||
datedeb/heuredeb + datefin/heurefin (déjà géré par _get_dates_from_assi_form)
|
|
||||||
"""
|
|
||||||
|
|
||||||
cas: list[bool] = [
|
|
||||||
# cas 1
|
|
||||||
not form.date_fin.data and not form.heure_debut.data,
|
|
||||||
# cas 3
|
|
||||||
form.date_fin.data != "" and not form.heure_debut.data,
|
|
||||||
]
|
|
||||||
|
|
||||||
if any(cas):
|
|
||||||
deb = deb.replace(hour=0, minute=0)
|
|
||||||
fin = fin.replace(hour=23, minute=59)
|
|
||||||
|
|
||||||
return deb, fin
|
|
||||||
|
|
||||||
|
|
||||||
def _record_justificatif_etud(
|
def _record_justificatif_etud(
|
||||||
etud: Identite, form: AjoutJustificatifEtudForm, justif: Justificatif | None = None
|
etud: Identite, form: AjoutJustificatifEtudForm, justif: Justificatif | None = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@ -724,7 +757,7 @@ def _record_justificatif_etud(
|
|||||||
dt_debut_tz_server,
|
dt_debut_tz_server,
|
||||||
dt_fin_tz_server,
|
dt_fin_tz_server,
|
||||||
dt_entry_date_tz_server,
|
dt_entry_date_tz_server,
|
||||||
) = _get_dates_from_assi_form(form, from_justif=True)
|
) = _get_dates_from_assi_form(form, etud, from_justif=True)
|
||||||
if not ok:
|
if not ok:
|
||||||
log("_record_justificatif_etud: dates invalides")
|
log("_record_justificatif_etud: dates invalides")
|
||||||
form.set_error("Erreur: dates invalides")
|
form.set_error("Erreur: dates invalides")
|
||||||
@ -1626,15 +1659,13 @@ def _preparer_objet(
|
|||||||
|
|
||||||
# Gestion justification
|
# Gestion justification
|
||||||
|
|
||||||
if not objet.est_just:
|
objet_prepare["justification"] = {
|
||||||
objet_prepare["justification"] = {"est_just": False}
|
"est_just": objet.est_just,
|
||||||
else:
|
"justificatifs": [],
|
||||||
objet_prepare["justification"] = {"est_just": True, "justificatifs": []}
|
}
|
||||||
|
|
||||||
if not sans_gros_objet:
|
if not sans_gros_objet:
|
||||||
justificatifs: list[int] = get_assiduites_justif(
|
justificatifs: list[int] = get_assiduites_justif(objet.assiduite_id, False)
|
||||||
objet.assiduite_id, False
|
|
||||||
)
|
|
||||||
for justi_id in justificatifs:
|
for justi_id in justificatifs:
|
||||||
justi: Justificatif = Justificatif.query.get(justi_id)
|
justi: Justificatif = Justificatif.query.get(justi_id)
|
||||||
objet_prepare["justification"]["justificatifs"].append(
|
objet_prepare["justification"]["justificatifs"].append(
|
||||||
@ -1652,9 +1683,8 @@ def _preparer_objet(
|
|||||||
|
|
||||||
objet_prepare["justification"] = {"assiduites": [], "fichiers": {}}
|
objet_prepare["justification"] = {"assiduites": [], "fichiers": {}}
|
||||||
if not sans_gros_objet:
|
if not sans_gros_objet:
|
||||||
assiduites: list[int] = scass.justifies(justif)
|
assiduites: list[Assiduite] = justif.get_assiduites()
|
||||||
for assi_id in assiduites:
|
for assi in assiduites:
|
||||||
assi: Assiduite = Assiduite.query.get(assi_id)
|
|
||||||
objet_prepare["justification"]["assiduites"].append(
|
objet_prepare["justification"]["assiduites"].append(
|
||||||
_preparer_objet("assiduite", assi, sans_gros_objet=True)
|
_preparer_objet("assiduite", assi, sans_gros_objet=True)
|
||||||
)
|
)
|
||||||
@ -2106,6 +2136,121 @@ def signal_assiduites_hebdo():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("edit_assiduite_etud/<int:assiduite_id>", methods=["GET", "POST"])
|
||||||
|
@scodoc
|
||||||
|
@permission_required(Permission.ScoView)
|
||||||
|
def edit_assiduite_etud(assiduite_id: int):
|
||||||
|
"""
|
||||||
|
Page affichant les détails d'une assiduité
|
||||||
|
Si le current_user alors la page propose un formulaire de modification
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
assi: Assiduite = Assiduite.get_assiduite(assiduite_id=assiduite_id)
|
||||||
|
except HTTPException:
|
||||||
|
flash("Assiduité invalide")
|
||||||
|
return redirect(url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept))
|
||||||
|
|
||||||
|
etud: Identite = assi.etudiant
|
||||||
|
formsemestre: FormSemestre = assi.get_formsemestre()
|
||||||
|
|
||||||
|
readonly: bool = not current_user.has_permission(Permission.AbsChange)
|
||||||
|
|
||||||
|
form: EditAssiForm = EditAssiForm(request.form)
|
||||||
|
if readonly:
|
||||||
|
form.disable_all()
|
||||||
|
|
||||||
|
# peuplement moduleimpl_select
|
||||||
|
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
||||||
|
choices: OrderedDict = OrderedDict()
|
||||||
|
choices[""] = [("", "Non spécifié"), ("autre", "Autre module (pas dans la liste)")]
|
||||||
|
|
||||||
|
# indique le nom du semestre dans le menu (optgroup)
|
||||||
|
group_name: str = formsemestre.titre_annee()
|
||||||
|
choices[group_name] = [
|
||||||
|
(m.id, f"{m.module.code} {m.module.abbrev or m.module.titre or ''}")
|
||||||
|
for m in modimpls_by_formsemestre[formsemestre.id]
|
||||||
|
if m.module.ue.type == UE_STANDARD
|
||||||
|
]
|
||||||
|
|
||||||
|
choices.move_to_end("", last=False)
|
||||||
|
form.modimpl.choices = choices
|
||||||
|
|
||||||
|
# Vérification formulaire
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if form.cancel.data: # cancel button
|
||||||
|
return redirect(request.referrer)
|
||||||
|
|
||||||
|
# vérification des valeurs
|
||||||
|
|
||||||
|
# Gestion de l'état
|
||||||
|
etat = form.assi_etat.data
|
||||||
|
try:
|
||||||
|
etat = int(etat)
|
||||||
|
etat = scu.EtatAssiduite.inverse().get(etat, None)
|
||||||
|
except ValueError:
|
||||||
|
etat = None
|
||||||
|
|
||||||
|
if etat is None:
|
||||||
|
form.error_messages.append("État invalide")
|
||||||
|
form.ok = False
|
||||||
|
|
||||||
|
description = form.description.data or ""
|
||||||
|
description = description.strip()
|
||||||
|
moduleimpl_id = form.modimpl.data if form.modimpl.data is not None else -1
|
||||||
|
# Vérifications des dates / horaires
|
||||||
|
|
||||||
|
ok, dt_deb, dt_fin, dt_entry = _get_dates_from_assi_form(
|
||||||
|
form, etud, from_justif=True, formsemestre=formsemestre
|
||||||
|
)
|
||||||
|
if ok:
|
||||||
|
if is_period_conflicting(
|
||||||
|
dt_deb, dt_fin, etud.assiduites, Assiduite, assi.id
|
||||||
|
):
|
||||||
|
form.set_error("La période est en conflit avec une autre assiduité")
|
||||||
|
form.ok = False
|
||||||
|
|
||||||
|
if form.ok:
|
||||||
|
assi.etat = etat
|
||||||
|
assi.description = description
|
||||||
|
if moduleimpl_id != -1:
|
||||||
|
assi.set_moduleimpl(moduleimpl_id)
|
||||||
|
|
||||||
|
assi.date_debut = dt_deb
|
||||||
|
assi.date_fin = dt_fin
|
||||||
|
assi.entry_date = dt_entry
|
||||||
|
|
||||||
|
db.session.add(assi)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
scass.simple_invalidate_cache(assi.to_dict(format_api=True), assi.etudid)
|
||||||
|
|
||||||
|
flash("enregistré")
|
||||||
|
return redirect(request.referrer)
|
||||||
|
|
||||||
|
# Remplissage du formulaire
|
||||||
|
form.assi_etat.data = str(assi.etat)
|
||||||
|
form.description.data = assi.description
|
||||||
|
moduleimpl_id: int | str | None = assi.get_moduleimpl_id() or ""
|
||||||
|
form.modimpl.data = str(moduleimpl_id)
|
||||||
|
|
||||||
|
form.date_debut.data = assi.date_debut.strftime(scu.DATE_FMT)
|
||||||
|
form.heure_debut.data = assi.date_debut.strftime(scu.TIME_FMT)
|
||||||
|
form.date_fin.data = assi.date_fin.strftime(scu.DATE_FMT)
|
||||||
|
form.heure_fin.data = assi.date_fin.strftime(scu.TIME_FMT)
|
||||||
|
form.entry_date.data = assi.entry_date.strftime(scu.DATE_FMT)
|
||||||
|
form.entry_time.data = assi.entry_date.strftime(scu.TIME_FMT)
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"assiduites/pages/edit_assiduite_etud.j2",
|
||||||
|
etud=etud,
|
||||||
|
sco=ScoData(etud, formsemestre=formsemestre),
|
||||||
|
form=form,
|
||||||
|
readonly=True,
|
||||||
|
objet=_preparer_objet("assiduite", assi),
|
||||||
|
title=f"Assiduité {etud.nom_short}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
def generate_bul_list(etud: Identite, semestre: FormSemestre) -> str:
|
||||||
"""Génère la liste des assiduités d'un étudiant pour le bulletin mail"""
|
"""Génère la liste des assiduités d'un étudiant pour le bulletin mail"""
|
||||||
|
|
||||||
@ -2463,12 +2608,12 @@ class JourAssi(sco_gen_cal.Jour):
|
|||||||
(version journee divisée en demi-journees)
|
(version journee divisée en demi-journees)
|
||||||
"""
|
"""
|
||||||
heure_midi = scass.str_to_time(ScoDocSiteConfig.get("assi_lunch_time", "13:00"))
|
heure_midi = scass.str_to_time(ScoDocSiteConfig.get("assi_lunch_time", "13:00"))
|
||||||
|
plage: tuple[datetime.datetime, datetime.datetime] = ()
|
||||||
if matin:
|
if matin:
|
||||||
heure_matin = scass.str_to_time(
|
heure_matin = scass.str_to_time(
|
||||||
ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
||||||
)
|
)
|
||||||
matin = (
|
plage = (
|
||||||
# date debut
|
# date debut
|
||||||
scu.localize_datetime(
|
scu.localize_datetime(
|
||||||
datetime.datetime.combine(self.date, heure_matin)
|
datetime.datetime.combine(self.date, heure_matin)
|
||||||
@ -2476,71 +2621,52 @@ class JourAssi(sco_gen_cal.Jour):
|
|||||||
# date fin
|
# date fin
|
||||||
scu.localize_datetime(datetime.datetime.combine(self.date, heure_midi)),
|
scu.localize_datetime(datetime.datetime.combine(self.date, heure_midi)),
|
||||||
)
|
)
|
||||||
assiduites_matin = [
|
else:
|
||||||
assi
|
|
||||||
for assi in self.assiduites
|
|
||||||
if scu.is_period_overlapping(
|
|
||||||
(assi.date_debut, assi.date_fin), matin, bornes=False
|
|
||||||
)
|
|
||||||
]
|
|
||||||
justificatifs_matin = [
|
|
||||||
justi
|
|
||||||
for justi in self.justificatifs
|
|
||||||
if scu.is_period_overlapping(
|
|
||||||
(justi.date_debut, justi.date_fin), matin, bornes=False
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
etat = self._get_color_assiduites_cascade(
|
|
||||||
self._get_etats_from_assiduites(assiduites_matin),
|
|
||||||
show_pres=self.parent.show_pres,
|
|
||||||
show_reta=self.parent.show_reta,
|
|
||||||
)
|
|
||||||
|
|
||||||
est_just = self._get_color_justificatifs_cascade(
|
|
||||||
self._get_etats_from_justificatifs(justificatifs_matin),
|
|
||||||
)
|
|
||||||
|
|
||||||
return f"color {etat} {est_just}"
|
|
||||||
|
|
||||||
heure_soir = scass.str_to_time(
|
heure_soir = scass.str_to_time(
|
||||||
ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
||||||
)
|
)
|
||||||
|
|
||||||
# séparation en demi journées
|
# séparation en demi journées
|
||||||
aprem = (
|
plage = (
|
||||||
# date debut
|
# date debut
|
||||||
scu.localize_datetime(datetime.datetime.combine(self.date, heure_midi)),
|
scu.localize_datetime(datetime.datetime.combine(self.date, heure_midi)),
|
||||||
# date fin
|
# date fin
|
||||||
scu.localize_datetime(datetime.datetime.combine(self.date, heure_soir)),
|
scu.localize_datetime(datetime.datetime.combine(self.date, heure_soir)),
|
||||||
)
|
)
|
||||||
|
|
||||||
assiduites_aprem = [
|
assiduites = [
|
||||||
assi
|
assi
|
||||||
for assi in self.assiduites
|
for assi in self.assiduites
|
||||||
if scu.is_period_overlapping(
|
if scu.is_period_overlapping(
|
||||||
(assi.date_debut, assi.date_fin), aprem, bornes=False
|
(assi.date_debut, assi.date_fin), plage, bornes=False
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
justificatifs_aprem = [
|
justificatifs = [
|
||||||
justi
|
justi
|
||||||
for justi in self.justificatifs
|
for justi in self.justificatifs
|
||||||
if scu.is_period_overlapping(
|
if scu.is_period_overlapping(
|
||||||
(justi.date_debut, justi.date_fin), aprem, bornes=False
|
(justi.date_debut, justi.date_fin), plage, bornes=False
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
etat = self._get_color_assiduites_cascade(
|
etat = self._get_color_assiduites_cascade(
|
||||||
self._get_etats_from_assiduites(assiduites_aprem),
|
self._get_etats_from_assiduites(assiduites),
|
||||||
show_pres=self.parent.show_pres,
|
show_pres=self.parent.show_pres,
|
||||||
show_reta=self.parent.show_reta,
|
show_reta=self.parent.show_reta,
|
||||||
)
|
)
|
||||||
|
|
||||||
est_just = self._get_color_justificatifs_cascade(
|
est_just = self._get_color_justificatifs_cascade(
|
||||||
self._get_etats_from_justificatifs(justificatifs_aprem),
|
self._get_etats_from_justificatifs(justificatifs),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if est_just == "est_just" and any(
|
||||||
|
not assi.est_just
|
||||||
|
for assi in assiduites
|
||||||
|
if assi.etat != scu.EtatAssiduite.PRESENT
|
||||||
|
):
|
||||||
|
est_just = ""
|
||||||
|
|
||||||
return f"color {etat} {est_just}"
|
return f"color {etat} {est_just}"
|
||||||
|
|
||||||
def _get_etats_from_assiduites(self, assiduites: Query) -> list[scu.EtatAssiduite]:
|
def _get_etats_from_assiduites(self, assiduites: Query) -> list[scu.EtatAssiduite]:
|
||||||
|
Loading…
Reference in New Issue
Block a user