forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -22,7 +22,6 @@ from app.api import get_model_api_object, tools
|
|||||||
from app.decorators import permission_required, scodoc
|
from app.decorators import permission_required, scodoc
|
||||||
from app.models import Identite, Justificatif, Departement, FormSemestre, Scolog
|
from app.models import Identite, Justificatif, Departement, FormSemestre, Scolog
|
||||||
from app.models.assiduites import (
|
from app.models.assiduites import (
|
||||||
compute_assiduites_justified,
|
|
||||||
get_formsemestre_from_data,
|
get_formsemestre_from_data,
|
||||||
)
|
)
|
||||||
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
|
||||||
@ -310,7 +309,6 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
|||||||
|
|
||||||
errors: list[dict] = []
|
errors: list[dict] = []
|
||||||
success: list[dict] = []
|
success: list[dict] = []
|
||||||
justifs: list[Justificatif] = []
|
|
||||||
|
|
||||||
# énumération des justificatifs
|
# énumération des justificatifs
|
||||||
for i, data in enumerate(create_list):
|
for i, data in enumerate(create_list):
|
||||||
@ -322,11 +320,9 @@ def justif_create(etudid: int = None, nip=None, ine=None):
|
|||||||
errors.append({"indice": i, "message": obj})
|
errors.append({"indice": i, "message": obj})
|
||||||
else:
|
else:
|
||||||
success.append({"indice": i, "message": obj})
|
success.append({"indice": i, "message": obj})
|
||||||
justifs.append(justi)
|
justi.justifier_assiduites()
|
||||||
scass.simple_invalidate_cache(data, etud.id)
|
scass.simple_invalidate_cache(data, etud.id)
|
||||||
|
|
||||||
# Actualisation des assiduités justifiées en fonction de tous les nouveaux justificatifs
|
|
||||||
compute_assiduites_justified(etud.etudid, justifs)
|
|
||||||
return {"errors": errors, "success": success}
|
return {"errors": errors, "success": success}
|
||||||
|
|
||||||
|
|
||||||
@ -495,6 +491,7 @@ def justif_edit(justif_id: int):
|
|||||||
return json_error(404, err)
|
return json_error(404, err)
|
||||||
|
|
||||||
# Mise à jour du justificatif
|
# Mise à jour du justificatif
|
||||||
|
justificatif_unique.dejustifier_assiduites()
|
||||||
db.session.add(justificatif_unique)
|
db.session.add(justificatif_unique)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
@ -511,11 +508,7 @@ def justif_edit(justif_id: int):
|
|||||||
retour = {
|
retour = {
|
||||||
"couverture": {
|
"couverture": {
|
||||||
"avant": avant_ids,
|
"avant": avant_ids,
|
||||||
"apres": compute_assiduites_justified(
|
"apres": justificatif_unique.justifier_assiduites(),
|
||||||
justificatif_unique.etudid,
|
|
||||||
[justificatif_unique],
|
|
||||||
True,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# Invalide le cache
|
# Invalide le cache
|
||||||
@ -592,14 +585,10 @@ def _delete_one(justif_id: int) -> tuple[int, str]:
|
|||||||
|
|
||||||
# On invalide le cache
|
# On invalide le cache
|
||||||
scass.simple_invalidate_cache(justificatif_unique.to_dict())
|
scass.simple_invalidate_cache(justificatif_unique.to_dict())
|
||||||
|
# On actualise les assiduités justifiées de l'étudiant concerné
|
||||||
|
justificatif_unique.dejustifier_assiduites()
|
||||||
# On supprime le justificatif
|
# On supprime le justificatif
|
||||||
db.session.delete(justificatif_unique)
|
db.session.delete(justificatif_unique)
|
||||||
# On actualise les assiduités justifiées de l'étudiant concerné
|
|
||||||
compute_assiduites_justified(
|
|
||||||
justificatif_unique.etudid,
|
|
||||||
Justificatif.query.filter_by(etudid=justificatif_unique.etudid).all(),
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return (200, "OK")
|
return (200, "OK")
|
||||||
|
|
||||||
@ -700,7 +689,6 @@ def justif_export(justif_id: int | None = None, filename: str | None = None):
|
|||||||
@as_json
|
@as_json
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
def justif_remove(justif_id: int = None):
|
def justif_remove(justif_id: int = None):
|
||||||
# XXX TODO pas de test unitaire
|
|
||||||
"""
|
"""
|
||||||
Supression d'un fichier ou d'une archive
|
Supression d'un fichier ou d'une archive
|
||||||
{
|
{
|
||||||
|
@ -393,7 +393,7 @@ class BulletinBUT:
|
|||||||
else:
|
else:
|
||||||
etud_ues_ids = res.etud_ues_ids(etud.id)
|
etud_ues_ids = res.etud_ues_ids(etud.id)
|
||||||
|
|
||||||
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
nbabsnj, nbabsjust, nbabs = formsemestre.get_abs_count(etud.id)
|
||||||
etud_groups = sco_groups.get_etud_formsemestre_groups(
|
etud_groups = sco_groups.get_etud_formsemestre_groups(
|
||||||
etud, formsemestre, only_to_show=True
|
etud, formsemestre, only_to_show=True
|
||||||
)
|
)
|
||||||
@ -408,7 +408,7 @@ class BulletinBUT:
|
|||||||
}
|
}
|
||||||
if self.prefs["bul_show_abs"]:
|
if self.prefs["bul_show_abs"]:
|
||||||
semestre_infos["absences"] = {
|
semestre_infos["absences"] = {
|
||||||
"injustifie": nbabs - nbabsjust,
|
"injustifie": nbabsnj,
|
||||||
"total": nbabs,
|
"total": nbabs,
|
||||||
"metrique": {
|
"metrique": {
|
||||||
"H.": "Heure(s)",
|
"H.": "Heure(s)",
|
||||||
@ -525,7 +525,7 @@ class BulletinBUT:
|
|||||||
d["demission"] = ""
|
d["demission"] = ""
|
||||||
|
|
||||||
# --- Absences
|
# --- Absences
|
||||||
d["nbabs"], d["nbabsjust"] = self.res.formsemestre.get_abs_count(etud.id)
|
_, d["nbabsjust"], d["nbabs"] = self.res.formsemestre.get_abs_count(etud.id)
|
||||||
|
|
||||||
# --- Decision Jury
|
# --- Decision Jury
|
||||||
infos, _ = sco_bulletins.etud_descr_situation_semestre(
|
infos, _ = sco_bulletins.etud_descr_situation_semestre(
|
||||||
@ -540,9 +540,9 @@ class BulletinBUT:
|
|||||||
|
|
||||||
d.update(infos)
|
d.update(infos)
|
||||||
# --- Rangs
|
# --- Rangs
|
||||||
d["rang_nt"] = (
|
d[
|
||||||
f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
"rang_nt"
|
||||||
)
|
] = f"{d['semestre']['rang']['value']} / {d['semestre']['rang']['total']}"
|
||||||
d["rang_txt"] = "Rang " + d["rang_nt"]
|
d["rang_txt"] = "Rang " + d["rang_nt"]
|
||||||
|
|
||||||
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
d.update(sco_bulletins.make_context_dict(self.res.formsemestre, d["etud"]))
|
||||||
|
@ -241,7 +241,7 @@ def bulletin_but_xml_compat(
|
|||||||
|
|
||||||
# --- Absences
|
# --- Absences
|
||||||
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
||||||
nbabs, nbabsjust = formsemestre.get_abs_count(etud.id)
|
_, nbabsjust, nbabs = formsemestre.get_abs_count(etud.id)
|
||||||
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
||||||
|
|
||||||
# -------- LA SUITE EST COPIEE SANS MODIF DE sco_bulletins_xml.py ---------
|
# -------- LA SUITE EST COPIEE SANS MODIF DE sco_bulletins_xml.py ---------
|
||||||
|
@ -574,11 +574,7 @@ class Justificatif(ScoDocModel):
|
|||||||
db.session.delete(self)
|
db.session.delete(self)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
# On actualise les assiduités justifiées de l'étudiant concerné
|
# On actualise les assiduités justifiées de l'étudiant concerné
|
||||||
compute_assiduites_justified(
|
self.dejustifier_assiduites()
|
||||||
self.etudid,
|
|
||||||
Justificatif.query.filter_by(etudid=self.etudid).all(),
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_fichiers(self) -> tuple[list[str], int]:
|
def get_fichiers(self) -> tuple[list[str], int]:
|
||||||
"""Renvoie la liste des noms de fichiers justicatifs
|
"""Renvoie la liste des noms de fichiers justicatifs
|
||||||
@ -600,6 +596,64 @@ class Justificatif(ScoDocModel):
|
|||||||
accessible_filenames.append(filename[0])
|
accessible_filenames.append(filename[0])
|
||||||
return accessible_filenames, len(filenames)
|
return accessible_filenames, len(filenames)
|
||||||
|
|
||||||
|
def justifier_assiduites(
|
||||||
|
self,
|
||||||
|
) -> list[int]:
|
||||||
|
"""Justifie les assiduités sur la période de validité du justificatif"""
|
||||||
|
log(f"justifier_assiduites: {self}")
|
||||||
|
assiduites_justifiees: list[int] = []
|
||||||
|
if self.etat != EtatJustificatif.VALIDE:
|
||||||
|
return []
|
||||||
|
# On récupère les assiduités de l'étudiant sur la période donnée
|
||||||
|
assiduites: Query = self.etudiant.assiduites.filter(
|
||||||
|
Assiduite.date_debut >= self.date_debut,
|
||||||
|
Assiduite.date_fin <= self.date_fin,
|
||||||
|
Assiduite.etat != EtatAssiduite.PRESENT,
|
||||||
|
)
|
||||||
|
# Pour chaque assiduité, on la justifie
|
||||||
|
for assi in assiduites:
|
||||||
|
assi.est_just = True
|
||||||
|
assiduites_justifiees.append(assi.assiduite_id)
|
||||||
|
db.session.add(assi)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return assiduites_justifiees
|
||||||
|
|
||||||
|
def dejustifier_assiduites(self) -> list[int]:
|
||||||
|
"""
|
||||||
|
Déjustifie les assiduités sur la période du justificatif
|
||||||
|
"""
|
||||||
|
assiduites_dejustifiees: list[int] = []
|
||||||
|
|
||||||
|
# On récupère les assiduités de l'étudiant sur la période donnée
|
||||||
|
assiduites: Query = self.etudiant.assiduites.filter(
|
||||||
|
Assiduite.date_debut >= self.date_debut,
|
||||||
|
Assiduite.date_fin <= self.date_fin,
|
||||||
|
Assiduite.etat != EtatAssiduite.PRESENT,
|
||||||
|
)
|
||||||
|
assi: Assiduite
|
||||||
|
for assi in assiduites:
|
||||||
|
# On récupère les justificatifs qui justifient l'assiduité `assi`
|
||||||
|
assi_justifs: list[int] = get_justifs_from_date(
|
||||||
|
self.etudiant.etudid,
|
||||||
|
assi.date_debut,
|
||||||
|
assi.date_fin,
|
||||||
|
long=False,
|
||||||
|
valid=True,
|
||||||
|
)
|
||||||
|
# Si il n'y a pas d'autre justificatif valide, on déjustifie l'assiduité
|
||||||
|
if len(assi_justifs) == 0 or (
|
||||||
|
len(assi_justifs) == 1 and assi_justifs[0] == self.justif_id
|
||||||
|
):
|
||||||
|
assi.est_just = False
|
||||||
|
assiduites_dejustifiees.append(assi.assiduite_id)
|
||||||
|
db.session.add(assi)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return assiduites_dejustifiees
|
||||||
|
|
||||||
|
|
||||||
def is_period_conflicting(
|
def is_period_conflicting(
|
||||||
date_debut: datetime,
|
date_debut: datetime,
|
||||||
@ -623,72 +677,6 @@ def is_period_conflicting(
|
|||||||
return count > 0
|
return count > 0
|
||||||
|
|
||||||
|
|
||||||
def compute_assiduites_justified(
|
|
||||||
etudid: int, justificatifs: list[Justificatif] = None, reset: bool = False
|
|
||||||
) -> list[int]:
|
|
||||||
"""
|
|
||||||
Args:
|
|
||||||
etudid (int): l'identifiant de l'étudiant
|
|
||||||
justificatifs (list[Justificatif]): La liste des justificatifs qui seront utilisés
|
|
||||||
reset (bool, optional): remet à false les assiduites non justifiés. Defaults to False.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[int]: la liste des assiduités qui ont été justifiées.
|
|
||||||
"""
|
|
||||||
# TODO à optimiser (car très long avec 40000 assiduités)
|
|
||||||
# On devrait :
|
|
||||||
# - récupérer uniquement les assiduités qui sont sur la période des justificatifs donnés
|
|
||||||
# - Pour chaque assiduité trouvée, il faut récupérer les justificatifs qui la justifie
|
|
||||||
# - Si au moins un justificatif valide couvre la période de l'assiduité alors on la justifie
|
|
||||||
|
|
||||||
# Si on ne donne pas de justificatifs on prendra par défaut tous les justificatifs de l'étudiant
|
|
||||||
if justificatifs is None:
|
|
||||||
justificatifs: list[Justificatif] = Justificatif.query.filter_by(
|
|
||||||
etudid=etudid
|
|
||||||
).all()
|
|
||||||
|
|
||||||
# On ne prend que les justificatifs valides
|
|
||||||
justificatifs = [j for j in justificatifs if j.etat == EtatJustificatif.VALIDE]
|
|
||||||
|
|
||||||
# On récupère les assiduités de l'étudiant
|
|
||||||
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
|
|
||||||
|
|
||||||
assiduites_justifiees: list[int] = []
|
|
||||||
|
|
||||||
for assi in assiduites:
|
|
||||||
# On ne justifie pas les Présences
|
|
||||||
if assi.etat == EtatAssiduite.PRESENT:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# On récupère les justificatifs qui justifient l'assiduité `assi`
|
|
||||||
assi_justificatifs = Justificatif.query.filter(
|
|
||||||
Justificatif.etudid == assi.etudid,
|
|
||||||
Justificatif.date_debut <= assi.date_debut,
|
|
||||||
Justificatif.date_fin >= assi.date_fin,
|
|
||||||
Justificatif.etat == EtatJustificatif.VALIDE,
|
|
||||||
).all()
|
|
||||||
|
|
||||||
# Si au moins un justificatif possède une période qui couvre l'assiduité
|
|
||||||
if any(
|
|
||||||
assi.date_debut >= j.date_debut and assi.date_fin <= j.date_fin
|
|
||||||
for j in justificatifs + assi_justificatifs
|
|
||||||
):
|
|
||||||
# On justifie l'assiduité
|
|
||||||
# On ajoute l'id de l'assiduité à la liste des assiduités justifiées
|
|
||||||
assi.est_just = True
|
|
||||||
assiduites_justifiees.append(assi.assiduite_id)
|
|
||||||
db.session.add(assi)
|
|
||||||
elif reset:
|
|
||||||
# Si le paramètre reset est Vrai alors les assiduités non justifiées
|
|
||||||
# sont remise en "non justifiée"
|
|
||||||
assi.est_just = False
|
|
||||||
db.session.add(assi)
|
|
||||||
# On valide la session
|
|
||||||
db.session.commit()
|
|
||||||
# On renvoie la liste des assiduite_id des assiduités justifiées
|
|
||||||
return assiduites_justifiees
|
|
||||||
|
|
||||||
|
|
||||||
def get_assiduites_justif(assiduite_id: int, long: bool) -> list[int | dict]:
|
def get_assiduites_justif(assiduite_id: int, long: bool) -> list[int | dict]:
|
||||||
"""
|
"""
|
||||||
get_assiduites_justif Récupération des justificatifs d'une assiduité
|
get_assiduites_justif Récupération des justificatifs d'une assiduité
|
||||||
|
@ -875,7 +875,7 @@ class FormSemestre(db.Model):
|
|||||||
|
|
||||||
def get_abs_count(self, etudid):
|
def get_abs_count(self, etudid):
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
tuple (nb abs, nb abs justifiées)
|
tuple (nb abs non just, nb abs justifiées, nb abs total)
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
"""
|
"""
|
||||||
from app.scodoc import sco_assiduites
|
from app.scodoc import sco_assiduites
|
||||||
|
@ -175,10 +175,9 @@ def sidebar(etudid: int = None):
|
|||||||
inscription = etud.inscription_courante()
|
inscription = etud.inscription_courante()
|
||||||
if inscription:
|
if inscription:
|
||||||
formsemestre = inscription.formsemestre
|
formsemestre = inscription.formsemestre
|
||||||
nbabs, nbabsjust = sco_assiduites.formsemestre_get_assiduites_count(
|
nbabsnj, nbabsjust, _ = sco_assiduites.formsemestre_get_assiduites_count(
|
||||||
etudid, formsemestre
|
etudid, formsemestre
|
||||||
)
|
)
|
||||||
nbabsnj = nbabs - nbabsjust
|
|
||||||
H.append(
|
H.append(
|
||||||
f"""<span title="absences du {
|
f"""<span title="absences du {
|
||||||
formsemestre.date_debut.strftime("%d/%m/%Y")
|
formsemestre.date_debut.strftime("%d/%m/%Y")
|
||||||
|
@ -67,7 +67,7 @@ def abs_notify(etudid: int, date: str | datetime.datetime):
|
|||||||
if not formsemestre:
|
if not formsemestre:
|
||||||
return # non inscrit a la date, pas de notification
|
return # non inscrit a la date, pas de notification
|
||||||
|
|
||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count_in_interval(
|
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count_in_interval(
|
||||||
etudid,
|
etudid,
|
||||||
metrique=scu.translate_assiduites_metric(
|
metrique=scu.translate_assiduites_metric(
|
||||||
sco_preferences.get_preference(
|
sco_preferences.get_preference(
|
||||||
|
@ -17,7 +17,7 @@ from app.models import (
|
|||||||
ModuleImplInscription,
|
ModuleImplInscription,
|
||||||
ScoDocSiteConfig,
|
ScoDocSiteConfig,
|
||||||
)
|
)
|
||||||
from app.models.assiduites import Assiduite, Justificatif, compute_assiduites_justified
|
from app.models.assiduites import Assiduite, Justificatif
|
||||||
from app.scodoc import sco_formsemestre_inscriptions
|
from app.scodoc import sco_formsemestre_inscriptions
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_cache
|
from app.scodoc import sco_cache
|
||||||
@ -111,9 +111,9 @@ class CountCalculator:
|
|||||||
evening if evening else ScoDocSiteConfig.get("assi_afternoon_time", "18:00")
|
evening if evening else ScoDocSiteConfig.get("assi_afternoon_time", "18:00")
|
||||||
)
|
)
|
||||||
|
|
||||||
self.non_work_days: list[scu.NonWorkDays] = (
|
self.non_work_days: list[
|
||||||
scu.NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
scu.NonWorkDays
|
||||||
)
|
] = scu.NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
# Sera utilisé pour les assiduités longues (> 1 journée)
|
# Sera utilisé pour les assiduités longues (> 1 journée)
|
||||||
self.nb_heures_par_jour = (
|
self.nb_heures_par_jour = (
|
||||||
@ -661,7 +661,7 @@ def create_absence_billet(
|
|||||||
db.session.add(justi)
|
db.session.add(justi)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
compute_assiduites_justified(etud.id, [justi])
|
justi.justifier_assiduites()
|
||||||
|
|
||||||
calculator: CountCalculator = CountCalculator()
|
calculator: CountCalculator = CountCalculator()
|
||||||
calculator.compute_assiduites([assiduite_unique])
|
calculator.compute_assiduites([assiduite_unique])
|
||||||
@ -671,7 +671,7 @@ def create_absence_billet(
|
|||||||
# Gestion du cache
|
# Gestion du cache
|
||||||
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
def get_assiduites_count(etudid: int, sem: dict) -> tuple[int, int]:
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées)
|
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
"""
|
"""
|
||||||
metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"])
|
metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"])
|
||||||
@ -687,17 +687,17 @@ def formsemestre_get_assiduites_count(
|
|||||||
etudid: int, formsemestre: FormSemestre, moduleimpl_id: int = None
|
etudid: int, formsemestre: FormSemestre, moduleimpl_id: int = None
|
||||||
) -> tuple[int, int]:
|
) -> tuple[int, int]:
|
||||||
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
"""Les comptes d'absences de cet étudiant dans ce semestre:
|
||||||
tuple (nb abs non justifiées, nb abs justifiées)
|
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
"""
|
"""
|
||||||
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
metrique = sco_preferences.get_preference("assi_metrique", formsemestre.id)
|
||||||
return get_assiduites_count_in_interval(
|
return get_assiduites_count_in_interval(
|
||||||
etudid,
|
etudid,
|
||||||
date_debut=scu.localize_datetime(
|
date_debut=scu.localize_datetime(
|
||||||
datetime.combine(formsemestre.date_debut, time(8, 0))
|
datetime.combine(formsemestre.date_debut, time(0, 0))
|
||||||
),
|
),
|
||||||
date_fin=scu.localize_datetime(
|
date_fin=scu.localize_datetime(
|
||||||
datetime.combine(formsemestre.date_fin, time(18, 0))
|
datetime.combine(formsemestre.date_fin, time(23, 0))
|
||||||
),
|
),
|
||||||
metrique=scu.translate_assiduites_metric(metrique),
|
metrique=scu.translate_assiduites_metric(metrique),
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
@ -714,12 +714,12 @@ def get_assiduites_count_in_interval(
|
|||||||
moduleimpl_id: int = None,
|
moduleimpl_id: int = None,
|
||||||
):
|
):
|
||||||
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
|
||||||
tuple (nb abs, nb abs justifiées)
|
tuple (nb abs non justifiées, nb abs justifiées, nb abs total)
|
||||||
On peut spécifier les dates comme datetime ou iso.
|
On peut spécifier les dates comme datetime ou iso.
|
||||||
Utilise un cache.
|
Utilise un cache.
|
||||||
"""
|
"""
|
||||||
date_debut_iso = date_debut_iso or date_debut.isoformat()
|
date_debut_iso = date_debut_iso or date_debut.strftime("%Y-%m-%d")
|
||||||
date_fin_iso = date_fin_iso or date_fin.isoformat()
|
date_fin_iso = date_fin_iso or date_fin.strftime("%Y-%m-%d")
|
||||||
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}_assiduites"
|
key = f"{etudid}_{date_debut_iso}_{date_fin_iso}_assiduites"
|
||||||
|
|
||||||
r = sco_cache.AbsSemEtudCache.get(key)
|
r = sco_cache.AbsSemEtudCache.get(key)
|
||||||
@ -744,9 +744,10 @@ def get_assiduites_count_in_interval(
|
|||||||
if not ans:
|
if not ans:
|
||||||
log("warning: get_assiduites_count failed to cache")
|
log("warning: get_assiduites_count failed to cache")
|
||||||
|
|
||||||
nb_abs: dict = r["absent"][metrique]
|
nb_abs: int = r["absent"][metrique]
|
||||||
nb_abs_just: dict = r["absent_just"][metrique]
|
nb_abs_nj: int = r["absent_non_just"][metrique]
|
||||||
return (nb_abs, nb_abs_just)
|
nb_abs_just: int = r["absent_just"][metrique]
|
||||||
|
return (nb_abs_nj, nb_abs_just, nb_abs)
|
||||||
|
|
||||||
|
|
||||||
def invalidate_assiduites_count(etudid: int, sem: dict):
|
def invalidate_assiduites_count(etudid: int, sem: dict):
|
||||||
|
@ -196,7 +196,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
|
|||||||
pid = partition["partition_id"]
|
pid = partition["partition_id"]
|
||||||
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
|
||||||
# --- Absences
|
# --- Absences
|
||||||
I["nbabs"], I["nbabsjust"] = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
_, I["nbabsjust"], I["nbabs"] = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
||||||
|
|
||||||
# --- Decision Jury
|
# --- Decision Jury
|
||||||
infos, dpv = etud_descr_situation_semestre(
|
infos, dpv = etud_descr_situation_semestre(
|
||||||
@ -471,7 +471,7 @@ def _ue_mod_bulletin(
|
|||||||
) # peut etre 'NI'
|
) # peut etre 'NI'
|
||||||
is_malus = mod["module"]["module_type"] == ModuleType.MALUS
|
is_malus = mod["module"]["module_type"] == ModuleType.MALUS
|
||||||
if bul_show_abs_modules:
|
if bul_show_abs_modules:
|
||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||||
mod_abs = [nbabs, nbabsjust]
|
mod_abs = [nbabs, nbabsjust]
|
||||||
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
|
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
|
||||||
else:
|
else:
|
||||||
|
@ -296,7 +296,7 @@ def formsemestre_bulletinetud_published_dict(
|
|||||||
|
|
||||||
# --- Absences
|
# --- Absences
|
||||||
if prefs["bul_show_abs"]:
|
if prefs["bul_show_abs"]:
|
||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||||
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
|
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
|
||||||
|
|
||||||
# --- Décision Jury
|
# --- Décision Jury
|
||||||
|
@ -260,7 +260,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
numero=str(mod["numero"]),
|
numero=str(mod["numero"]),
|
||||||
titre=quote_xml_attr(mod["titre"]),
|
titre=quote_xml_attr(mod["titre"]),
|
||||||
abbrev=quote_xml_attr(mod["abbrev"]),
|
abbrev=quote_xml_attr(mod["abbrev"]),
|
||||||
code_apogee=quote_xml_attr(mod["code_apogee"])
|
code_apogee=quote_xml_attr(mod["code_apogee"]),
|
||||||
# ects=ects ects des modules maintenant inutilisés
|
# ects=ects ects des modules maintenant inutilisés
|
||||||
)
|
)
|
||||||
x_ue.append(x_mod)
|
x_ue.append(x_mod)
|
||||||
@ -347,7 +347,7 @@ def make_xml_formsemestre_bulletinetud(
|
|||||||
|
|
||||||
# --- Absences
|
# --- Absences
|
||||||
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
|
||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
_, nbabsjust, nbabs = sco_assiduites.get_assiduites_count(etudid, sem)
|
||||||
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
|
||||||
# --- Decision Jury
|
# --- Decision Jury
|
||||||
if (
|
if (
|
||||||
|
@ -722,8 +722,8 @@ def formsemestre_recap_parcours_table(
|
|||||||
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
|
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
|
||||||
)
|
)
|
||||||
# Absences (nb d'abs non just. dans ce semestre)
|
# Absences (nb d'abs non just. dans ce semestre)
|
||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, sem)
|
nbabsnj = sco_assiduites.get_assiduites_count(etudid, sem)[0]
|
||||||
H.append(f"""<td class="rcp_abs">{nbabs - nbabsjust}</td>""")
|
H.append(f"""<td class="rcp_abs">{nbabsnj}</td>""")
|
||||||
|
|
||||||
# UEs
|
# UEs
|
||||||
for ue in ues:
|
for ue in ues:
|
||||||
|
@ -105,7 +105,9 @@ def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
|
|||||||
rangs.append(["rang_" + code_module, rang_module])
|
rangs.append(["rang_" + code_module, rang_module])
|
||||||
|
|
||||||
# Absences
|
# Absences
|
||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, nt.sem)
|
nbabsnj, nbabsjust, _ = sco_assiduites.get_assiduites_count(
|
||||||
|
etudid, nt.sem
|
||||||
|
)
|
||||||
# En BUT, prend tout, sinon ne prend que les semestre validés par le jury
|
# En BUT, prend tout, sinon ne prend que les semestre validés par le jury
|
||||||
if nt.is_apc or (
|
if nt.is_apc or (
|
||||||
dec
|
dec
|
||||||
@ -125,7 +127,7 @@ def etud_get_poursuite_info(sem: dict, etud: dict) -> dict:
|
|||||||
("date_debut", s["date_debut"]),
|
("date_debut", s["date_debut"]),
|
||||||
("date_fin", s["date_fin"]),
|
("date_fin", s["date_fin"]),
|
||||||
("periode", "%s - %s" % (s["mois_debut"], s["mois_fin"])),
|
("periode", "%s - %s" % (s["mois_debut"], s["mois_fin"])),
|
||||||
("AbsNonJust", nbabs - nbabsjust),
|
("AbsNonJust", nbabsnj),
|
||||||
("AbsJust", nbabsjust),
|
("AbsJust", nbabsjust),
|
||||||
]
|
]
|
||||||
# ajout des 2 champs notes des modules et classement dans chaque module
|
# ajout des 2 champs notes des modules et classement dans chaque module
|
||||||
|
@ -620,7 +620,7 @@ class RowRecap(tb.Row):
|
|||||||
def add_abs(self):
|
def add_abs(self):
|
||||||
"Ajoute les colonnes absences"
|
"Ajoute les colonnes absences"
|
||||||
# Absences (nb d'abs non just. dans ce semestre)
|
# Absences (nb d'abs non just. dans ce semestre)
|
||||||
nbabs, nbabsjust = self.table.res.formsemestre.get_abs_count(self.etud.id)
|
_, nbabsjust, nbabs = self.table.res.formsemestre.get_abs_count(self.etud.id)
|
||||||
self.add_cell("nbabs", "Abs", f"{nbabs:1.0f}", "abs", raw_content=nbabs)
|
self.add_cell("nbabs", "Abs", f"{nbabs:1.0f}", "abs", raw_content=nbabs)
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
"nbabsjust", "Just.", f"{nbabsjust:1.0f}", "abs", raw_content=nbabsjust
|
"nbabsjust", "Just.", f"{nbabsjust:1.0f}", "abs", raw_content=nbabsjust
|
||||||
@ -691,9 +691,9 @@ class RowRecap(tb.Row):
|
|||||||
self.add_ue_modimpls_cols(ue, ue_status["is_capitalized"])
|
self.add_ue_modimpls_cols(ue, ue_status["is_capitalized"])
|
||||||
|
|
||||||
self.nb_ues_etud_parcours = len(res.etud_parcours_ues_ids(etud.id))
|
self.nb_ues_etud_parcours = len(res.etud_parcours_ues_ids(etud.id))
|
||||||
ue_valid_txt = ue_valid_txt_html = (
|
ue_valid_txt = (
|
||||||
f"{self.nb_ues_validables}/{self.nb_ues_etud_parcours}"
|
ue_valid_txt_html
|
||||||
)
|
) = f"{self.nb_ues_validables}/{self.nb_ues_etud_parcours}"
|
||||||
if self.nb_ues_warning:
|
if self.nb_ues_warning:
|
||||||
ue_valid_txt_html += " " + scu.EMO_WARNING
|
ue_valid_txt_html += " " + scu.EMO_WARNING
|
||||||
cell_class = ""
|
cell_class = ""
|
||||||
@ -717,9 +717,9 @@ class RowRecap(tb.Row):
|
|||||||
# sous-classé par JuryRow pour ajouter les codes
|
# sous-classé par JuryRow pour ajouter les codes
|
||||||
table: TableRecap = self.table
|
table: TableRecap = self.table
|
||||||
formsemestre: FormSemestre = table.res.formsemestre
|
formsemestre: FormSemestre = table.res.formsemestre
|
||||||
table.group_titles["col_ue"] = (
|
table.group_titles[
|
||||||
f"UEs du S{formsemestre.semestre_id} {formsemestre.annee_scolaire()}"
|
"col_ue"
|
||||||
)
|
] = f"UEs du S{formsemestre.semestre_id} {formsemestre.annee_scolaire()}"
|
||||||
col_id = f"moy_ue_{ue.id}"
|
col_id = f"moy_ue_{ue.id}"
|
||||||
val = (
|
val = (
|
||||||
ue_status["moy"]
|
ue_status["moy"]
|
||||||
|
@ -66,7 +66,6 @@ Bilan assiduité de {{sco.etud.nomprenom}}
|
|||||||
{% endblock styles %}
|
{% endblock styles %}
|
||||||
|
|
||||||
{% block app_content %}
|
{% block app_content %}
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
|
||||||
<div class="pageContent">
|
<div class="pageContent">
|
||||||
|
|
||||||
<h2>Bilan de l'assiduité de {{sco.etud.html_link_fiche()|safe}}</span></h2>
|
<h2>Bilan de l'assiduité de {{sco.etud.html_link_fiche()|safe}}</span></h2>
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
{% extends "sco_page.j2" %}
|
{% extends "sco_page.j2" %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{title}}
|
{{title}}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script>
|
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
|
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/js/groups_view.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/groups_view.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/date_utils.js"></script>
|
||||||
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
{% if readonly != "false" %}
|
{% if readonly != "false" %}
|
||||||
function getPeriodValues(){
|
function getPeriodValues() {
|
||||||
return [0, 23]
|
return [0, 23]
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -30,9 +30,9 @@
|
|||||||
|
|
||||||
setupDate();
|
setupDate();
|
||||||
updateDate();
|
updateDate();
|
||||||
if (!readOnly){
|
if (!readOnly) {
|
||||||
setupTimeLine(()=>{
|
setupTimeLine(() => {
|
||||||
generateAllEtudRow();
|
generateAllEtudRow();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,16 +63,16 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock scripts %}
|
{% endblock scripts %}
|
||||||
|
|
||||||
{% block styles %}
|
{% block styles %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap.min.css">
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap-theme.min.css">
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap-theme.min.css">
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
|
||||||
{% endblock styles %}
|
{% endblock styles %}
|
||||||
|
|
||||||
|
|
||||||
@ -80,7 +80,11 @@
|
|||||||
{% include "assiduites/widgets/toast.j2" %}
|
{% include "assiduites/widgets/toast.j2" %}
|
||||||
|
|
||||||
{{ minitimeline|safe }}
|
{{ minitimeline|safe }}
|
||||||
|
<style>
|
||||||
|
#moduleimpl_select {
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<section id="content">
|
<section id="content">
|
||||||
|
|
||||||
<div class="no-display">
|
<div class="no-display">
|
||||||
@ -102,8 +106,7 @@
|
|||||||
<div class="infos-button">Groupes : {{grp|safe}}</div>
|
<div class="infos-button">Groupes : {{grp|safe}}</div>
|
||||||
<div class="infos-button" style="margin-left: 24px;">Date : <span style="margin-left: 8px;"
|
<div class="infos-button" style="margin-left: 24px;">Date : <span style="margin-left: 8px;"
|
||||||
id="datestr"></span>
|
id="datestr"></span>
|
||||||
<input type="text" name="tl_date" id="tl_date" value="{{ date }}"
|
<input type="text" name="tl_date" id="tl_date" value="{{ date }}" onchange="updateDate()">
|
||||||
onchange="updateDate()">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
@ -174,7 +174,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', ()=>{
|
window.addEventListener('load', ()=>{
|
||||||
const table_columns = [...document.querySelectorAll('.external-sort')];
|
const table_columns = [...document.querySelectorAll('th.external-sort')];
|
||||||
table_columns.forEach((e)=>e.addEventListener('click', ()=>{
|
table_columns.forEach((e)=>e.addEventListener('click', ()=>{
|
||||||
|
|
||||||
// récupération de l'ordre "ascending" / "descending"
|
// récupération de l'ordre "ascending" / "descending"
|
||||||
|
@ -1,624 +0,0 @@
|
|||||||
<ul id="contextMenu" class="context-menu">
|
|
||||||
<li id="detailOption">Détails</li>
|
|
||||||
<li id="editOption">Éditer</li>
|
|
||||||
<li id="deleteOption">Supprimer</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
|
||||||
{% include "assiduites/widgets/prompt.j2" %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const itemsPerPage = 10;
|
|
||||||
const contextMenu = document.getElementById("contextMenu");
|
|
||||||
const editOption = document.getElementById("editOption");
|
|
||||||
const detailOption = document.getElementById("detailOption");
|
|
||||||
const deleteOption = document.getElementById("deleteOption");
|
|
||||||
|
|
||||||
let selectedRow;
|
|
||||||
|
|
||||||
document.addEventListener("click", () => {
|
|
||||||
contextMenu.style.display = "none";
|
|
||||||
if (contextMenu.childElementCount > 3) {
|
|
||||||
contextMenu.removeChild(contextMenu.lastElementChild)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
editOption.addEventListener("click", () => {
|
|
||||||
if (selectedRow) {
|
|
||||||
const type = selectedRow.getAttribute('type');
|
|
||||||
const obj_id = selectedRow.getAttribute('obj_id');
|
|
||||||
|
|
||||||
if (type == "assiduite") {
|
|
||||||
editionAssiduites(obj_id);
|
|
||||||
} else {
|
|
||||||
editionJustificatifs(obj_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
detailOption.addEventListener("click", () => {
|
|
||||||
if (selectedRow) {
|
|
||||||
const type = selectedRow.getAttribute('type');
|
|
||||||
const obj_id = selectedRow.getAttribute('obj_id');
|
|
||||||
|
|
||||||
if (type == "assiduite") {
|
|
||||||
detailAssiduites(obj_id);
|
|
||||||
} else {
|
|
||||||
detailJustificatifs(obj_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
deleteOption.addEventListener("click", () => {
|
|
||||||
if (selectedRow) {
|
|
||||||
const type = selectedRow.getAttribute('type');
|
|
||||||
const obj_id = selectedRow.getAttribute('obj_id');
|
|
||||||
if (type == "assiduite") {
|
|
||||||
deleteAssiduite(obj_id);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
deleteJustificatif(obj_id);
|
|
||||||
}
|
|
||||||
loadAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function filterArray(array, f) {
|
|
||||||
return array.filter((el) => {
|
|
||||||
let t = Object.keys(f).every((k) => {
|
|
||||||
if (k == "etat") {
|
|
||||||
return f.etat.includes(el.etat.toLowerCase())
|
|
||||||
};
|
|
||||||
if (k == "est_just") {
|
|
||||||
if (f.est_just != "") {
|
|
||||||
return `${el.est_just}` == f.est_just;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (k.indexOf('date') != -1) {
|
|
||||||
const assi_time = new Date(Date.removeUTC(el[k]));
|
|
||||||
const filter_time = f[k].time;
|
|
||||||
switch (f[k].pref) {
|
|
||||||
|
|
||||||
case "0":
|
|
||||||
return assi_time.isSame(filter_time, 'minute');
|
|
||||||
case "-1":
|
|
||||||
return assi_time.isBefore(filter_time, 'minutes');
|
|
||||||
case "1":
|
|
||||||
return assi_time.isAfter(filter_time, 'minutes');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k == "moduleimpl_id") {
|
|
||||||
const m = el[k] == undefined || el[k] == null ? "null" : el[k];
|
|
||||||
if (f.moduleimpl_id != '') {
|
|
||||||
return m == f.moduleimpl_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k == "obj_id") {
|
|
||||||
const obj_id = el.assiduite_id || el.justif_id;
|
|
||||||
return f.obj_id.includes(obj_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (k == "formsemestre") {
|
|
||||||
return f.formsemestre === "" || (el.hasOwnProperty("formsemestre") && el.formsemestre.title.replaceAll('-', ' ').indexOf(f.formsemestre) != -1);
|
|
||||||
}
|
|
||||||
if (k == "etud") {
|
|
||||||
|
|
||||||
const e = getEtudiant(el.etudid);
|
|
||||||
const str = `${e.prenom.capitalize()} ${e.nom.toUpperCase()}`
|
|
||||||
|
|
||||||
return f.etud === "" || str.indexOf(f.etud) != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
|
|
||||||
return t;
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateTableHead(columns, assi = true) {
|
|
||||||
const table = assi ? "#assiduiteTable" : "#justificatifTable"
|
|
||||||
const call = assi ? [assiduiteCallBack, true] : [justificatifCallBack, false]
|
|
||||||
const tr = document.querySelector(`${table} thead tr`);
|
|
||||||
|
|
||||||
tr.innerHTML = ""
|
|
||||||
|
|
||||||
columns.forEach((c) => {
|
|
||||||
const th = document.createElement('th');
|
|
||||||
const div = document.createElement('div');
|
|
||||||
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.textContent = columnTranslator(c);
|
|
||||||
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.classList.add('icon', "order");
|
|
||||||
a.onclick = () => { order(c, call[0], a, call[1]) }
|
|
||||||
|
|
||||||
div.appendChild(span)
|
|
||||||
div.appendChild(a)
|
|
||||||
|
|
||||||
th.appendChild(div);
|
|
||||||
|
|
||||||
tr.appendChild(th);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPaginationButtons(array, assi = true) {
|
|
||||||
const totalPages = Math.ceil(array.length / itemsPerPage);
|
|
||||||
if (totalPages <= 1) {
|
|
||||||
if (assi) {
|
|
||||||
paginationContainerAssiduites.innerHTML = ""
|
|
||||||
} else {
|
|
||||||
paginationContainerJustificatifs.innerHTML = ""
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assi) {
|
|
||||||
paginationContainerAssiduites.innerHTML = "<span class='liste_pagination'><button class='pagination_moins'><</button><select id='paginationAssi'></select><button class='pagination_plus'>></button></span>"
|
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi')?.addEventListener('change', (e) => {
|
|
||||||
currentPageAssiduites = e.target.value;
|
|
||||||
assiduiteCallBack(array);
|
|
||||||
})
|
|
||||||
|
|
||||||
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
|
|
||||||
if (currentPageAssiduites > 1) {
|
|
||||||
currentPageAssiduites--;
|
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
|
||||||
assiduiteCallBack(array);
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
|
|
||||||
if (currentPageAssiduites < totalPages) {
|
|
||||||
currentPageAssiduites++;
|
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
|
|
||||||
assiduiteCallBack(array);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
paginationContainerJustificatifs.innerHTML = "<span class='liste_pagination'><button class='pagination_moins'><</button><select id='paginationJusti'></select><button class='pagination_plus'>></button></span>"
|
|
||||||
paginationContainerJustificatifs.querySelector('#paginationJusti')?.addEventListener('change', (e) => {
|
|
||||||
currentPageJustificatifs = e.target.value;
|
|
||||||
justificatifCallBack(array);
|
|
||||||
})
|
|
||||||
|
|
||||||
paginationContainerJustificatifs.querySelector('.pagination_moins').addEventListener('click', () => {
|
|
||||||
if (currentPageJustificatifs > 1) {
|
|
||||||
currentPageJustificatifs--;
|
|
||||||
paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageJustificatifs
|
|
||||||
justificatifCallBack(array);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
paginationContainerJustificatifs.querySelector('.pagination_plus').addEventListener('click', () => {
|
|
||||||
if (currentPageJustificatifs < totalPages) {
|
|
||||||
currentPageJustificatifs++;
|
|
||||||
paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageJustificatifs
|
|
||||||
justificatifCallBack(array);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for (let i = 1; i <= totalPages; i++) {
|
|
||||||
const paginationButton = document.createElement("option");
|
|
||||||
paginationButton.textContent = i;
|
|
||||||
paginationButton.value = i;
|
|
||||||
|
|
||||||
if (assi) {
|
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton)
|
|
||||||
if (i == currentPageAssiduites)
|
|
||||||
paginationContainerAssiduites.querySelector('#paginationAssi').value = i + "";
|
|
||||||
} else {
|
|
||||||
paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
|
|
||||||
if (i == currentPageJustificatifs)
|
|
||||||
paginationContainerJustificatifs.querySelector('#paginationJusti').value = i + "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateActivePaginationButton(assi);
|
|
||||||
}
|
|
||||||
function updateActivePaginationButton(assi = true) {
|
|
||||||
if (assi) {
|
|
||||||
const paginationButtons =
|
|
||||||
paginationContainerAssiduites.querySelectorAll("#paginationContainerAssiduites .pagination-button");
|
|
||||||
paginationButtons.forEach((button) => {
|
|
||||||
if (parseInt(button.textContent) === currentPageAssiduites) {
|
|
||||||
button.classList.add("active");
|
|
||||||
} else {
|
|
||||||
button.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const paginationButtons =
|
|
||||||
paginationContainerJustificatifs.querySelectorAll("#paginationContainerJustificatifs .pagination-button");
|
|
||||||
paginationButtons.forEach((button) => {
|
|
||||||
if (parseInt(button.textContent) === currentPageJustificatifs) {
|
|
||||||
button.classList.add("active");
|
|
||||||
} else {
|
|
||||||
button.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadAll() {
|
|
||||||
try { getAssi(assiduiteCallBack) } catch { }
|
|
||||||
try { getJusti(justificatifCallBack) } catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
function order(keyword, callback = () => { }, el, assi = true) {
|
|
||||||
const call = (array, ordered) => {
|
|
||||||
const sorted = array.sort((a, b) => {
|
|
||||||
let keyValueA = a[keyword];
|
|
||||||
let keyValueB = b[keyword];
|
|
||||||
|
|
||||||
if (keyword.indexOf("date") != -1) {
|
|
||||||
keyValueA = new Date(Date.removeUTC(keyValueA))
|
|
||||||
keyValueB = new Date(Date.removeUTC(keyValueB))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyword.indexOf("module") != -1) {
|
|
||||||
keyValueA = getModuleImpl(a);
|
|
||||||
keyValueB = getModuleImpl(b);
|
|
||||||
}
|
|
||||||
if (keyword.indexOf("etudid") != -1) {
|
|
||||||
keyValueA = getEtudiant(a.etudid);
|
|
||||||
keyValueB = getEtudiant(b.etudid);
|
|
||||||
|
|
||||||
keyValueA = `${keyValueA.prenom.capitalize()} ${keyValueA.nom.toUpperCase()}`
|
|
||||||
keyValueB = `${keyValueB.prenom.capitalize()} ${keyValueB.nom.toUpperCase()}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let orderDertermined = keyValueA > keyValueB;
|
|
||||||
|
|
||||||
if (!ordered) {
|
|
||||||
orderDertermined = keyValueA < keyValueB;
|
|
||||||
}
|
|
||||||
return orderDertermined
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
callback(sorted);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
if (assi) {
|
|
||||||
orderAssiduites = !orderAssiduites;
|
|
||||||
getAssi((a) => { call(a, orderAssiduites) });
|
|
||||||
} else {
|
|
||||||
orderJustificatifs = !orderJustificatifs;
|
|
||||||
getJusti((a) => { call(a, orderJustificatifs) });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function columnTranslator(colName) {
|
|
||||||
switch (colName) {
|
|
||||||
case "date_debut":
|
|
||||||
return "Début";
|
|
||||||
case "entry_date":
|
|
||||||
return "Saisie le";
|
|
||||||
case "date_fin":
|
|
||||||
return "Fin";
|
|
||||||
case "etat":
|
|
||||||
return "État";
|
|
||||||
case "moduleimpl_id":
|
|
||||||
return "Module";
|
|
||||||
case "est_just":
|
|
||||||
return "Justifiée";
|
|
||||||
case "raison":
|
|
||||||
return "Raison";
|
|
||||||
case "fichier":
|
|
||||||
return "Fichier";
|
|
||||||
case "etudid":
|
|
||||||
return "Etudiant";
|
|
||||||
case "formsemestre":
|
|
||||||
return "Semestre";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openContext(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
selectedRow = e.target.parentElement;
|
|
||||||
contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`;
|
|
||||||
contextMenu.style.left = `${e.clientX}px`;
|
|
||||||
contextMenu.style.display = "block";
|
|
||||||
if (contextMenu.childElementCount > 3) {
|
|
||||||
contextMenu.removeChild(contextMenu.lastElementChild)
|
|
||||||
}
|
|
||||||
if (selectedRow.getAttribute('type') == "assiduite") {
|
|
||||||
|
|
||||||
const li = document.createElement('li')
|
|
||||||
li.textContent = "Justifier"
|
|
||||||
let obj_id = selectedRow.getAttribute('obj_id');
|
|
||||||
let assi = Object.values(assiduites).flat().filter((a) => { return a.assiduite_id == obj_id })[0]
|
|
||||||
li.addEventListener('click', () => {
|
|
||||||
|
|
||||||
if (assi && !assi.est_just && assi.etat != "PRESENT") {
|
|
||||||
fastJustify(assi)
|
|
||||||
} else {
|
|
||||||
openAlertModal("Erreur", document.createTextNode("L'assiduité est déjà justifiée."))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (assi && assi.etat != "PRESENT") {
|
|
||||||
contextMenu.appendChild(li);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadStr(data, name) {
|
|
||||||
const blob = new Blob([data], { type: 'text/csv' });
|
|
||||||
const url = window.URL.createObjectURL(blob)
|
|
||||||
const a = document.createElement('a')
|
|
||||||
a.setAttribute('href', url)
|
|
||||||
a.setAttribute('download', name);
|
|
||||||
a.click()
|
|
||||||
|
|
||||||
a.remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
function askDownload(data) {
|
|
||||||
|
|
||||||
const div = document.createElement('div');
|
|
||||||
const head = document.createElement('h3');
|
|
||||||
const input = document.createElement('input');
|
|
||||||
head.textContent = "Veuillez nommer le fichier qui sera téléchargé (sera au format CSV)"
|
|
||||||
|
|
||||||
input.type = "text";
|
|
||||||
input.placeholder = "liste.csv"
|
|
||||||
|
|
||||||
div.appendChild(head)
|
|
||||||
div.appendChild(input)
|
|
||||||
|
|
||||||
openPromptModal("Préparation du téléchargement", div, () => {
|
|
||||||
|
|
||||||
downloadStr(data, input.value ? input.value : "download.csv")
|
|
||||||
|
|
||||||
}, () => { }, "var(--color-present)");
|
|
||||||
}
|
|
||||||
|
|
||||||
function toCSV(array, filters) {
|
|
||||||
array = filterArray(array, filters.filters)
|
|
||||||
|
|
||||||
let csv = filters.columns.map((c) => columnTranslator(c)).join(',') + "\n";
|
|
||||||
array.forEach((a) => {
|
|
||||||
let line = ""
|
|
||||||
filters.columns.forEach((c) => {
|
|
||||||
switch (c) {
|
|
||||||
case "fichier":
|
|
||||||
line += a[c] ? "Oui," : "Non,"
|
|
||||||
break;
|
|
||||||
case "etudid":
|
|
||||||
const e = getEtudiant(a.etudid);
|
|
||||||
line += `${e.nom.toUpperCase()} ${e.prenom.capitalize()},`
|
|
||||||
break;
|
|
||||||
case "formsemestre":
|
|
||||||
line += a.hasOwnProperty("formsemestre") ? a.formsemestre.title : ""
|
|
||||||
line += ","
|
|
||||||
break;
|
|
||||||
case "est_just":
|
|
||||||
line += a[c] ? "Oui," : "Non,"
|
|
||||||
break;
|
|
||||||
case "moduleimpl_id":
|
|
||||||
line += `${getModuleImpl(a)},`
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
line += `${a[c]},`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
line = line.substring(0, line.lastIndexOf(',')) + "\n"
|
|
||||||
csv += line;
|
|
||||||
})
|
|
||||||
askDownload(csv);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEtudiant(id) {
|
|
||||||
if (id in etuds) {
|
|
||||||
return etuds[id];
|
|
||||||
}
|
|
||||||
getSingleEtud(id);
|
|
||||||
|
|
||||||
return etuds[id];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.pageContent {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--sco-content-max-width);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
border: 1px solid #dddddd;
|
|
||||||
padding: 8px;
|
|
||||||
color: var(--color-default-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover {
|
|
||||||
filter: brightness(1.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 10px 0;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 45;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu li {
|
|
||||||
padding: 8px 16px;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-menu li:hover {
|
|
||||||
filter: brightness(0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
#deleteOption {
|
|
||||||
background-color: var(--color-absent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-present {
|
|
||||||
background-color: var(--color-present)
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-absent,
|
|
||||||
.l-invalid {
|
|
||||||
background-color: var(--color-absent-clair);
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-valid {
|
|
||||||
background-color: var(--color-justi-clair);
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-retard {
|
|
||||||
background-color: var(--color-retard);
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-absent.est_just {
|
|
||||||
background-color: var(--color-absent-justi);
|
|
||||||
}
|
|
||||||
|
|
||||||
.l-retard.est_just {
|
|
||||||
background-color: var(--color-retard-justi);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ajoutez des styles pour le conteneur de pagination et les boutons */
|
|
||||||
.pagination-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-button {
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
margin: 0 5px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-button:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-button.active {
|
|
||||||
background-color: var(--color-primary);
|
|
||||||
color: #fff;
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
th>div {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-head {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-line {
|
|
||||||
display: flex;
|
|
||||||
justify-content: start;
|
|
||||||
align-items: center;
|
|
||||||
margin: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-line>* {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.rbtn {
|
|
||||||
width: 35px;
|
|
||||||
height: 35px;
|
|
||||||
margin: 0 5px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.f-label {
|
|
||||||
margin: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chk {
|
|
||||||
margin-left: 2px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-body label {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.obj-title {
|
|
||||||
text-decoration: underline #bbb;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.obj-part {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 33%;
|
|
||||||
padding: 5px;
|
|
||||||
border: 1px solid #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.obj-dates,
|
|
||||||
.obj-mod,
|
|
||||||
.obj-rest {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.liste_pagination {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,679 +0,0 @@
|
|||||||
<table id="justificatifTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Début</span>
|
|
||||||
<a class="icon order" onclick="order('date_debut', justificatifCallBack, this, false)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Fin</span>
|
|
||||||
<a class="icon order" onclick="order('date_fin', justificatifCallBack, this, false)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>État</span>
|
|
||||||
<a class="icon order" onclick="order('etat', justificatifCallBack, this, false)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Raison</span>
|
|
||||||
<a class="icon order" onclick="order('raison', justificatifCallBack, this, false)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Fichier</span>
|
|
||||||
<a class="icon order" onclick="order('fichier', justificatifCallBack, this, false)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="tableBodyJustificatifs">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div id="paginationContainerJustificatifs" class="pagination-container">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const paginationContainerJustificatifs = document.getElementById("paginationContainerJustificatifs");
|
|
||||||
let currentPageJustificatifs = 1;
|
|
||||||
let orderJustificatifs = true;
|
|
||||||
let filterJustificatifs = {
|
|
||||||
columns: [
|
|
||||||
"entry_date", "date_debut", "date_fin", "etat", "raison", "fichier"
|
|
||||||
],
|
|
||||||
filters: {}
|
|
||||||
}
|
|
||||||
const tableBodyJustificatifs = document.getElementById("tableBodyJustificatifs");
|
|
||||||
|
|
||||||
function justificatifCallBack(justi) {
|
|
||||||
justi = filterArray(justi, filterJustificatifs.filters)
|
|
||||||
renderTableJustificatifs(currentPageJustificatifs, justi);
|
|
||||||
renderPaginationButtons(justi, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTableJustificatifs(page, justificatifs) {
|
|
||||||
generateTableHead(filterJustificatifs.columns, false)
|
|
||||||
|
|
||||||
tableBodyJustificatifs.innerHTML = "";
|
|
||||||
const start = (page - 1) * itemsPerPage;
|
|
||||||
const end = start + itemsPerPage;
|
|
||||||
|
|
||||||
justificatifs.slice(start, end).forEach((justificatif) => {
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
row.setAttribute('type', "justificatif");
|
|
||||||
row.setAttribute('obj_id', justificatif.justif_id);
|
|
||||||
|
|
||||||
const etat = justificatif.etat.toLowerCase();
|
|
||||||
|
|
||||||
if (etat == "valide") {
|
|
||||||
row.classList.add(`l-valid`);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
row.classList.add(`l-invalid`);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
filterJustificatifs.columns.forEach((k) => {
|
|
||||||
const td = document.createElement('td');
|
|
||||||
if (k.indexOf('date') != -1) {
|
|
||||||
td.textContent = new Date(Date.removeUTC(justificatif[k])).format(`DD/MM/Y HH:mm`)
|
|
||||||
} else if (k.indexOf('fichier') != -1) {
|
|
||||||
td.textContent = justificatif.fichier ? "Oui" : "Non";
|
|
||||||
} else if (k.indexOf('etudid') != -1) {
|
|
||||||
const e = getEtudiant(justificatif.etudid);
|
|
||||||
|
|
||||||
td.innerHTML = `<a class="etudinfo" id="line-${justificatif.etudid}" href="bilan_etud?etudid=${justificatif.etudid}">${e.prenom.capitalize()} ${e.nom.toUpperCase()}</a>`;
|
|
||||||
} else if (k == "formsemestre") {
|
|
||||||
if (justificatif.hasOwnProperty("formsemestre")) {
|
|
||||||
td.textContent = justificatif.formsemestre.title.replaceAll('-', ' ');
|
|
||||||
} else {
|
|
||||||
td.textContent = `Pas de Semestre`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (justificatif[k] != null) {
|
|
||||||
td.textContent = `${justificatif[k]}`.capitalize()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
td.textContent = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
row.appendChild(td)
|
|
||||||
})
|
|
||||||
|
|
||||||
row.addEventListener("contextmenu", openContext);
|
|
||||||
tableBodyJustificatifs.appendChild(row);
|
|
||||||
});
|
|
||||||
updateActivePaginationButton(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function detailJustificatifs(justi_id) {
|
|
||||||
const path = getUrl() + `/api/justificatif/${justi_id}`;
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data) => {
|
|
||||||
const user = getUser(data);
|
|
||||||
const date_debut = new Date(Date.removeUTC(data.date_debut)).format("DD/MM/YYYY HH:mm");
|
|
||||||
const date_fin = new Date(Date.removeUTC(data.date_fin)).format("DD/MM/YYYY HH:mm");
|
|
||||||
const entry_date = new Date(Date.removeUTC(data.entry_date)).format("DD/MM/YYYY HH:mm");
|
|
||||||
|
|
||||||
const etat = data.etat.capitalize();
|
|
||||||
const desc = data.raison == null ? "" : data.raison;
|
|
||||||
const id = data.justif_id;
|
|
||||||
const fichier = data.fichier != null ? "Oui" : "Non";
|
|
||||||
let filenames = []
|
|
||||||
let totalFiles = 0;
|
|
||||||
if (fichier) {
|
|
||||||
sync_get(path + "/list", (data2) => {
|
|
||||||
filenames = data2.filenames;
|
|
||||||
totalFiles = data2.total;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div class="obj-detail">
|
|
||||||
<div class="obj-dates">
|
|
||||||
<div id="date_debut" class="obj-part">
|
|
||||||
<span class="obj-title">Date de début</span>
|
|
||||||
<span class="obj-content">${date_debut}</span>
|
|
||||||
</div>
|
|
||||||
<div id="date_fin" class="obj-part">
|
|
||||||
<span class="obj-title">Date de fin</span>
|
|
||||||
<span class="obj-content">${date_fin}</span>
|
|
||||||
</div>
|
|
||||||
<div id="entry_date" class="obj-part">
|
|
||||||
<span class="obj-title">Date de saisie</span>
|
|
||||||
<span class="obj-content">${entry_date}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="obj-mod">
|
|
||||||
<div id="module" class="obj-part">
|
|
||||||
<span class="obj-title">Raison</span>
|
|
||||||
<span class="obj-content">${desc}</span>
|
|
||||||
</div>
|
|
||||||
<div id="etat" class="obj-part">
|
|
||||||
<span class="obj-title">Etat</span>
|
|
||||||
<span class="obj-content">${etat}</span>
|
|
||||||
</div>
|
|
||||||
<div id="user" class="obj-part">
|
|
||||||
<span class="obj-title">Créé par</span>
|
|
||||||
<span class="obj-content">${user}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="obj-rest">
|
|
||||||
<div id="est_just" class="obj-part obj-66">
|
|
||||||
<span class="obj-title">Fichier(s)</span>
|
|
||||||
<div class="obj-content" id="fich-content"></div>
|
|
||||||
</div>
|
|
||||||
<div id="id" class="obj-part">
|
|
||||||
<span class="obj-title">Identifiant du justificatif</span>
|
|
||||||
<span class="obj-content">${id}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const el = document.createElement('div');
|
|
||||||
el.innerHTML = html;
|
|
||||||
|
|
||||||
const fichContent = el.querySelector('#fich-content');
|
|
||||||
const s = document.createElement('span')
|
|
||||||
s.textContent = `${totalFiles} fichier(s) dont ${filenames.length} visible(s)`
|
|
||||||
|
|
||||||
fichContent.appendChild(s)
|
|
||||||
|
|
||||||
filenames.forEach((name) => {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.textContent = name
|
|
||||||
a.classList.add("fich-file")
|
|
||||||
|
|
||||||
a.onclick = () => { downloadFile(id, name) };
|
|
||||||
|
|
||||||
fichContent.appendChild(a);
|
|
||||||
})
|
|
||||||
|
|
||||||
openAlertModal("Détails", el.firstElementChild, null, "var(--color-information)")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadFile(id, name) {
|
|
||||||
const path = getUrl() + `/api/justificatif/${id}/export/${name}`;
|
|
||||||
|
|
||||||
fetch(path, {
|
|
||||||
method: "POST"
|
|
||||||
|
|
||||||
})
|
|
||||||
// This returns a promise inside of which we are checking for errors from the server.
|
|
||||||
// The catch promise at the end of the call does not getting called when the server returns an error.
|
|
||||||
// More information about the error catching can be found here: https://www.tjvantoll.com/2015/09/13/fetch-and-errors/.
|
|
||||||
.then((result) => {
|
|
||||||
if (!result.ok) {
|
|
||||||
throw Error(result.statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are reading the *Content-Disposition* header for getting the original filename given from the server
|
|
||||||
const header = result.headers.get('Content-Disposition');
|
|
||||||
const parts = header.split(';');
|
|
||||||
filename = parts[1].split('=')[1].replaceAll("\"", "");
|
|
||||||
|
|
||||||
return result.blob();
|
|
||||||
})
|
|
||||||
// We use the download property for triggering the download of the file from our browser.
|
|
||||||
// More information about the following code can be found here: https://stackoverflow.com/questions/32545632/how-can-i-download-a-file-using-window-fetch.
|
|
||||||
// The filename from the first promise is used as name of the file.
|
|
||||||
.then((blob) => {
|
|
||||||
if (blob != null) {
|
|
||||||
var url = window.URL.createObjectURL(blob);
|
|
||||||
var a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = filename;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// The catch is getting called only for client-side errors.
|
|
||||||
// For example the throw in the first then-promise, which is the error that came from the server.
|
|
||||||
.catch((err) => {
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function editionJustificatifs(justif_id) {
|
|
||||||
const path = getUrl() + `/api/justificatif/${justif_id}`;
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data) => {
|
|
||||||
const html = `
|
|
||||||
<div class="assi-edit">
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_date_debut">Date de début</legend>
|
|
||||||
<input type="datetime-local" name="justi_date_debut" id="justi_date_debut">
|
|
||||||
</div>
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_date_fin">Date de fin</legend>
|
|
||||||
<input type="datetime-local" name="justi_date_fin" id="justi_date_fin">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_etat">Etat du justificatif</legend>
|
|
||||||
<select name="justi_etat" id="justi_etat">
|
|
||||||
<option value="attente" selected>En Attente de validation</option>
|
|
||||||
<option value="non_valide">Non Valide</option>
|
|
||||||
<option value="modifie">Modifié</option>
|
|
||||||
<option value="valide">Valide</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_raison">Raison</legend>
|
|
||||||
<textarea name="justi_raison" id="justi_raison" cols="50" rows="10" maxlength="500"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-sect">
|
|
||||||
</div>
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_fich">Importer un fichier</legend>
|
|
||||||
<input type="file" name="justi_fich" id="justi_fich" multiple>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
|
|
||||||
const desc = data.raison == null ? "" : data.raison;
|
|
||||||
const fichier = data.fichier != null ? "Oui" : "Non";
|
|
||||||
|
|
||||||
|
|
||||||
const el = document.createElement('div')
|
|
||||||
el.innerHTML = html;
|
|
||||||
const assiEdit = el.firstElementChild;
|
|
||||||
|
|
||||||
assiEdit.querySelector('#justi_etat').value = data.etat.toLowerCase();
|
|
||||||
assiEdit.querySelector('#justi_raison').value = desc;
|
|
||||||
const d_deb = new Date(Date.removeUTC(data.date_debut)).format("YYYY-MM-DDTHH:mm")
|
|
||||||
const d_fin = new Date(Date.removeUTC(data.date_fin)).format("YYYY-MM-DDTHH:mm")
|
|
||||||
assiEdit.querySelector('#justi_date_debut').value = d_deb
|
|
||||||
assiEdit.querySelector('#justi_date_fin').value = d_fin
|
|
||||||
|
|
||||||
const fichContent = assiEdit.querySelector('.justi-sect');
|
|
||||||
|
|
||||||
let filenames = []
|
|
||||||
let totalFiles = 0;
|
|
||||||
if (data.fichier) {
|
|
||||||
sync_get(path + "/list", (data2) => {
|
|
||||||
filenames = data2.filenames;
|
|
||||||
totalFiles = data2.total;
|
|
||||||
})
|
|
||||||
let html = "<legend>Fichier(s)</legend>"
|
|
||||||
html += `<span>${totalFiles} fichier(s) dont ${filenames.length} visible(s)</span>`
|
|
||||||
fichContent.insertAdjacentHTML('beforeend', html)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
filenames.forEach((name) => {
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.textContent = name
|
|
||||||
a.classList.add("fich-file")
|
|
||||||
|
|
||||||
a.onclick = () => { downloadFile(justif_id, name) };
|
|
||||||
|
|
||||||
const input = document.createElement('input')
|
|
||||||
input.type = "checkbox"
|
|
||||||
input.name = "destroyFile";
|
|
||||||
input.classList.add('icon')
|
|
||||||
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.classList.add('file-line')
|
|
||||||
span.appendChild(input)
|
|
||||||
span.appendChild(a)
|
|
||||||
|
|
||||||
|
|
||||||
fichContent.appendChild(span);
|
|
||||||
})
|
|
||||||
|
|
||||||
openPromptModal("Modification du justificatif", assiEdit, () => {
|
|
||||||
const prompt = document.querySelector('.assi-edit');
|
|
||||||
|
|
||||||
let date_debut = prompt.querySelector('#justi_date_debut').value;
|
|
||||||
let date_fin = prompt.querySelector('#justi_date_fin').value;
|
|
||||||
|
|
||||||
if (date_debut == "" || date_fin == "") {
|
|
||||||
openAlertModal("Dates erronées", document.createTextNode('Les dates sont invalides'));
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
date_debut = new Date(Date.removeUTC(date_debut))
|
|
||||||
date_fin = new Date(Date.removeUTC(date_fin))
|
|
||||||
|
|
||||||
if (date_debut >= date_fin) {
|
|
||||||
openAlertModal("Dates erronées", document.createTextNode('La date de fin doit être après la date de début'));
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
const edit = {
|
|
||||||
date_debut: date_debut.format(),
|
|
||||||
date_fin: date_fin.format(),
|
|
||||||
raison: prompt.querySelector('#justi_raison').value,
|
|
||||||
etat: prompt.querySelector('#justi_etat').value,
|
|
||||||
}
|
|
||||||
|
|
||||||
const toRemoveFiles = [...prompt.querySelectorAll('[name="destroyFile"]:checked')]
|
|
||||||
|
|
||||||
if (toRemoveFiles.length > 0) {
|
|
||||||
removeFiles(justif_id, toRemoveFiles);
|
|
||||||
}
|
|
||||||
|
|
||||||
const in_files = prompt.querySelector('#justi_fich');
|
|
||||||
|
|
||||||
if (in_files.files.length > 0) {
|
|
||||||
importNewFiles(justif_id, in_files);
|
|
||||||
}
|
|
||||||
|
|
||||||
fullEditJustificatifs(data.justif_id, edit, () => {
|
|
||||||
loadAll();
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}, () => { }, "var(--color-information)");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullEditJustificatifs(justif_id, obj, call = () => { }) {
|
|
||||||
const path = getUrl() + `/api/justificatif/${justif_id}/edit`;
|
|
||||||
async_post(
|
|
||||||
path,
|
|
||||||
obj,
|
|
||||||
call,
|
|
||||||
(data, status) => {
|
|
||||||
//error
|
|
||||||
console.error(data, status);
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeFiles(justif_id, files = []) {
|
|
||||||
const path = getUrl() + `/api/justificatif/${justif_id}/remove`;
|
|
||||||
files = files.map((el) => {
|
|
||||||
return el.parentElement.querySelector('a').textContent;
|
|
||||||
});
|
|
||||||
|
|
||||||
sync_post(
|
|
||||||
path,
|
|
||||||
{
|
|
||||||
"remove": "list",
|
|
||||||
"filenames": files,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function importNewFiles(justif_id, in_files) {
|
|
||||||
const path = getUrl() + `/api/justificatif/${justif_id}/import`;
|
|
||||||
|
|
||||||
const requests = []
|
|
||||||
Array.from(in_files.files).forEach((f) => {
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append('file', f);
|
|
||||||
requests.push(
|
|
||||||
$.ajax(
|
|
||||||
{
|
|
||||||
url: path,
|
|
||||||
type: 'POST',
|
|
||||||
data: fd,
|
|
||||||
dateType: 'json',
|
|
||||||
contentType: false,
|
|
||||||
processData: false,
|
|
||||||
success: () => { },
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
$.when(
|
|
||||||
requests
|
|
||||||
).done(() => {
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
function filterJusti(dept = false) {
|
|
||||||
|
|
||||||
let dept_html_head = `
|
|
||||||
<label>
|
|
||||||
Semestre
|
|
||||||
<input class="chk" type="checkbox" name="formsemestre" id="formsemestre" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Etudiant
|
|
||||||
<input class="chk" type="checkbox" name="etudid" id="etudid" checked>
|
|
||||||
</label>
|
|
||||||
`
|
|
||||||
|
|
||||||
let dept_html_body = `
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="formsemestre">Recherche dans les semestre</span>
|
|
||||||
<input type="text" name="formsemestre" id="formsemestre" placeholder="S1 2023" >
|
|
||||||
</span>
|
|
||||||
`
|
|
||||||
|
|
||||||
let html = `
|
|
||||||
<div class="filter-body">
|
|
||||||
<h3>Affichage des colonnes:</h3>
|
|
||||||
<div class="filter-head">
|
|
||||||
${dept ? dept_html_head : ""}
|
|
||||||
<label>
|
|
||||||
Date de saisie
|
|
||||||
<input class="chk" type="checkbox" name="entry_date" id="entry_date">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Date de Début
|
|
||||||
<input class="chk" type="checkbox" name="date_debut" id="date_debut" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Date de Fin
|
|
||||||
<input class="chk" type="checkbox" name="date_fin" id="date_fin" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Etat
|
|
||||||
<input class="chk" type="checkbox" name="etat" id="etat" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Raison
|
|
||||||
<input class="chk" type="checkbox" name="raison" id="raison" checked>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Fichier
|
|
||||||
<input class="chk" type="checkbox" name="fichier" id="fichier" checked>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
<h3>Filtrage des colonnes:</h3>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="entry_date">Date de saisie</span>
|
|
||||||
<select name="entry_date_pref" id="entry_date_pref">
|
|
||||||
<option value="-1">Avant</option>
|
|
||||||
<option value="0">Égal</option>
|
|
||||||
<option value="1">Après</option>
|
|
||||||
</select>
|
|
||||||
<input type="datetime-local" name="entry_date_time" id="entry_date_time">
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="date_debut">Date de début</span>
|
|
||||||
<select name="date_debut_pref" id="date_debut_pref">
|
|
||||||
<option value="-1">Avant</option>
|
|
||||||
<option value="0">Égal</option>
|
|
||||||
<option value="1">Après</option>
|
|
||||||
</select>
|
|
||||||
<input type="datetime-local" name="date_debut_time" id="date_debut_time">
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="date_fin">Date de fin</span>
|
|
||||||
<select name="date_fin_pref" id="date_fin_pref">
|
|
||||||
<option value="-1">Avant</option>
|
|
||||||
<option value="0">Égal</option>
|
|
||||||
<option value="1">Après</option>
|
|
||||||
</select>
|
|
||||||
<input type="datetime-local" name="date_fin_time" id="date_fin_time">
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="etat">Etat</span>
|
|
||||||
<label>
|
|
||||||
Valide
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="valide">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Non Valide
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="non_valide">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
En Attente
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="attente">
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Modifié
|
|
||||||
<input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="modifie">
|
|
||||||
</label>
|
|
||||||
</span>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="etud">Rechercher dans les étudiants</span>
|
|
||||||
<input type="text" name="etud" id="etud" placeholder="Anne Onymous" >
|
|
||||||
</span>
|
|
||||||
${dept ? dept_html_body : ""}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.innerHTML = html
|
|
||||||
html = span.firstElementChild
|
|
||||||
|
|
||||||
const filterHead = html.querySelector('.filter-head');
|
|
||||||
filterHead.innerHTML = ""
|
|
||||||
let cols = ["etudid", "entry_date", "date_debut", "date_fin", "etat", "raison", "fichier"];
|
|
||||||
|
|
||||||
if (dept) { cols.push("formsemestre") }
|
|
||||||
|
|
||||||
cols.forEach((k) => {
|
|
||||||
const label = document.createElement('label')
|
|
||||||
label.classList.add('f-label')
|
|
||||||
const s = document.createElement('span');
|
|
||||||
s.textContent = columnTranslator(k);
|
|
||||||
|
|
||||||
|
|
||||||
const input = document.createElement('input');
|
|
||||||
input.classList.add('chk')
|
|
||||||
input.type = "checkbox"
|
|
||||||
input.name = k
|
|
||||||
input.id = k;
|
|
||||||
input.checked = filterJustificatifs.columns.includes(k)
|
|
||||||
|
|
||||||
label.appendChild(s)
|
|
||||||
label.appendChild(input)
|
|
||||||
filterHead.appendChild(label)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mise à jour des filtres
|
|
||||||
|
|
||||||
Object.keys(filterJustificatifs.filters).forEach((key) => {
|
|
||||||
const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement;
|
|
||||||
if (key.indexOf('date') != -1) {
|
|
||||||
l.querySelector(`#${key}_pref`).value = filterJustificatifs.filters[key].pref;
|
|
||||||
l.querySelector(`#${key}_time`).value = filterJustificatifs.filters[key].time.format("YYYY-MM-DDTHH:mm");
|
|
||||||
|
|
||||||
} else if (key.indexOf('etat') != -1) {
|
|
||||||
l.querySelectorAll('input').forEach((e) => {
|
|
||||||
e.checked = filterJustificatifs.filters[key].includes(e.value)
|
|
||||||
})
|
|
||||||
} else if (key == "formsemestre") {
|
|
||||||
l.querySelector('#formsemestre').value = filterJustificatifs.filters["formsemestre"];
|
|
||||||
} else if (key == "etudid") {
|
|
||||||
l.querySelector('#etudid').value = filterJustificatifs.filters["etud"];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
openPromptModal("Filtrage des Justificatifs", html, () => {
|
|
||||||
|
|
||||||
const columns = [...document.querySelectorAll('.chk')]
|
|
||||||
.map((el) => { if (el.checked) return el.id })
|
|
||||||
.filter((el) => el)
|
|
||||||
|
|
||||||
filterJustificatifs.columns = columns
|
|
||||||
filterJustificatifs.filters = {}
|
|
||||||
//reste des filtres
|
|
||||||
|
|
||||||
const lines = [...document.querySelectorAll('.filter-line')];
|
|
||||||
|
|
||||||
lines.forEach((l) => {
|
|
||||||
const key = l.querySelector('.filter-title').getAttribute('for');
|
|
||||||
|
|
||||||
if (key.indexOf('date') != -1) {
|
|
||||||
const pref = l.querySelector(`#${key}_pref`).value;
|
|
||||||
const time = l.querySelector(`#${key}_time`).value;
|
|
||||||
if (l.querySelector(`#${key}_time`).value != "") {
|
|
||||||
filterJustificatifs.filters[key] = {
|
|
||||||
pref: pref,
|
|
||||||
time: new Date(Date.removeUTC(time))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (key.indexOf('etat') != -1) {
|
|
||||||
filterJustificatifs.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value);
|
|
||||||
} else if (key == "formsemestre") {
|
|
||||||
filterJustificatifs.filters["formsemestre"] = l.querySelector('#formsemestre').value;
|
|
||||||
|
|
||||||
} else if (key == "etud") {
|
|
||||||
filterJustificatifs.filters["etud"] = l.querySelector('#etud').value;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
loadAll();
|
|
||||||
|
|
||||||
|
|
||||||
}, () => { }, "var(--color-primary)");
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadJusti() {
|
|
||||||
getJusti((d) => { toCSV(d, filterJustificatifs) })
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJusti(action) {
|
|
||||||
try { getAllJustificatifsFromEtud(etudid, action, true, assi_limit_annee) } catch (_) { }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style>
|
|
||||||
.fich-file {
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fich-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.obj-66 {
|
|
||||||
width: 66%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-line {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -56,8 +56,8 @@
|
|||||||
</h2>
|
</h2>
|
||||||
<b>Absences</b>
|
<b>Absences</b>
|
||||||
{% if sco.etud_cur_sem %}
|
{% if sco.etud_cur_sem %}
|
||||||
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
|
<span title="absences du {{ sco.etud_cur_sem['date_debut'].strftime('%d/%m/%Y') }}
|
||||||
au {{ sco.etud_cur_sem['date_fin'] }}">({{sco.prefs["assi_metrique"]}})
|
au {{ sco.etud_cur_sem['date_fin'].strftime('%d/%m/%Y') }}">({{sco.prefs["assi_metrique"]}})
|
||||||
<br />{{'%1.0f'|format(sco.nbabsjust)}} J., {{'%1.0f'|format(sco.nbabsnj)}} N.J.</span>
|
<br />{{'%1.0f'|format(sco.nbabsjust)}} J., {{'%1.0f'|format(sco.nbabsnj)}} N.J.</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -74,8 +74,9 @@ class ScoData:
|
|||||||
if ins:
|
if ins:
|
||||||
self.etud_cur_sem = ins.formsemestre
|
self.etud_cur_sem = ins.formsemestre
|
||||||
(
|
(
|
||||||
self.nbabs,
|
self.nbabsnj,
|
||||||
self.nbabsjust,
|
self.nbabsjust,
|
||||||
|
self.nbabs,
|
||||||
) = sco_assiduites.get_assiduites_count_in_interval(
|
) = sco_assiduites.get_assiduites_count_in_interval(
|
||||||
etud.id,
|
etud.id,
|
||||||
self.etud_cur_sem.date_debut.isoformat(),
|
self.etud_cur_sem.date_debut.isoformat(),
|
||||||
@ -84,7 +85,6 @@ class ScoData:
|
|||||||
sco_preferences.get_preference("assi_metrique")
|
sco_preferences.get_preference("assi_metrique")
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.nbabsnj = self.nbabs - self.nbabsjust
|
|
||||||
else:
|
else:
|
||||||
self.etud_cur_sem = None
|
self.etud_cur_sem = None
|
||||||
else:
|
else:
|
||||||
|
@ -64,7 +64,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, compute_assiduites_justified
|
from app.models.assiduites import get_assiduites_justif
|
||||||
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
|
||||||
|
|
||||||
@ -468,7 +468,7 @@ def _record_assiduite_etud(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# On met à jour les assiduités en fonction du nouveau justificatif
|
# On met à jour les assiduités en fonction du nouveau justificatif
|
||||||
compute_assiduites_justified(etud.id, [justi])
|
justi.justifier_assiduites()
|
||||||
|
|
||||||
# Invalider cache
|
# Invalider cache
|
||||||
scass.simple_invalidate_cache(ass.to_dict(), etud.id)
|
scass.simple_invalidate_cache(ass.to_dict(), etud.id)
|
||||||
@ -778,6 +778,7 @@ def _record_justificatif_etud(
|
|||||||
form.date_debut.data = dt_debut_tz_server
|
form.date_debut.data = dt_debut_tz_server
|
||||||
form.date_fin.data = dt_fin_tz_server
|
form.date_fin.data = dt_fin_tz_server
|
||||||
form.entry_date.data = dt_entry_date_tz_server
|
form.entry_date.data = dt_entry_date_tz_server
|
||||||
|
justif.dejustifier_assiduites()
|
||||||
if justif.edit_from_form(form):
|
if justif.edit_from_form(form):
|
||||||
message = "Justificatif modifié"
|
message = "Justificatif modifié"
|
||||||
|
|
||||||
@ -792,7 +793,6 @@ def _record_justificatif_etud(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
message = "Pas de modification"
|
message = "Pas de modification"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
justif = Justificatif.create_justificatif(
|
justif = Justificatif.create_justificatif(
|
||||||
etud,
|
etud,
|
||||||
@ -816,7 +816,7 @@ def _record_justificatif_etud(
|
|||||||
# pour utiliser le "reset" (remise en "non_just") des assiduités
|
# pour utiliser le "reset" (remise en "non_just") des assiduités
|
||||||
# (à terme, il faudrait ne recalculer que les assiduités impactées)
|
# (à terme, il faudrait ne recalculer que les assiduités impactées)
|
||||||
# VOIR TODO dans compute_assiduites_justified
|
# VOIR TODO dans compute_assiduites_justified
|
||||||
compute_assiduites_justified(etud.id, reset=True)
|
justif.justifier_assiduites()
|
||||||
scass.simple_invalidate_cache(justif.to_dict(), etud.id)
|
scass.simple_invalidate_cache(justif.to_dict(), etud.id)
|
||||||
flash(message)
|
flash(message)
|
||||||
return True
|
return True
|
||||||
@ -1595,7 +1595,7 @@ def tableau_assiduite_actions():
|
|||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
)
|
)
|
||||||
|
|
||||||
compute_assiduites_justified(objet.etudiant.id, [justificatif_correspondant])
|
justificatif_correspondant.justifier_assiduites()
|
||||||
scass.simple_invalidate_cache(
|
scass.simple_invalidate_cache(
|
||||||
justificatif_correspondant.to_dict(), objet.etudiant.id
|
justificatif_correspondant.to_dict(), objet.etudiant.id
|
||||||
)
|
)
|
||||||
@ -1707,9 +1707,10 @@ def _action_modifier_justificatif(justi: Justificatif):
|
|||||||
|
|
||||||
justi.fichier = archive_name
|
justi.fichier = archive_name
|
||||||
|
|
||||||
|
justi.dejustifier_assiduites()
|
||||||
db.session.add(justi)
|
db.session.add(justi)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
scass.compute_assiduites_justified(justi.etudid, reset=True)
|
justi.justifier_assiduites()
|
||||||
scass.simple_invalidate_cache(justi.to_dict(True), justi.etudid)
|
scass.simple_invalidate_cache(justi.to_dict(True), justi.etudid)
|
||||||
|
|
||||||
|
|
||||||
@ -2181,12 +2182,8 @@ def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> s
|
|||||||
"""
|
"""
|
||||||
# récupération des ues du semestre
|
# récupération des ues du semestre
|
||||||
ntc: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
ntc: NotesTableCompat = res_sem.load_formsemestre_results(formsemestre)
|
||||||
ues = ntc.get_ues_stat_dict()
|
|
||||||
|
|
||||||
modimpls_list: list[dict] = []
|
modimpls_list: list[dict] = ntc.get_modimpls_dict()
|
||||||
for ue in ues:
|
|
||||||
# Ajout des moduleimpl de chaque ue dans la liste des moduleimpls
|
|
||||||
modimpls_list += ntc.get_modimpls_dict(ue_id=ue["ue_id"])
|
|
||||||
|
|
||||||
# prévoie la sélection par défaut d'un moduleimpl s'il a été passé en paramètre
|
# prévoie la sélection par défaut d'un moduleimpl s'il a été passé en paramètre
|
||||||
selected = "" if moduleimpl_id is not None else "selected"
|
selected = "" if moduleimpl_id is not None else "selected"
|
||||||
|
@ -1187,14 +1187,18 @@ def view_module_abs(moduleimpl_id, fmt="html"):
|
|||||||
|
|
||||||
rows = []
|
rows = []
|
||||||
for etud in inscrits:
|
for etud in inscrits:
|
||||||
nb_abs, nb_abs_just = sco_assiduites.formsemestre_get_assiduites_count(
|
(
|
||||||
|
nb_abs_nj,
|
||||||
|
nb_abs_just,
|
||||||
|
nb_abs,
|
||||||
|
) = sco_assiduites.formsemestre_get_assiduites_count(
|
||||||
etud.id, modimpl.formsemestre, moduleimpl_id=modimpl.id
|
etud.id, modimpl.formsemestre, moduleimpl_id=modimpl.id
|
||||||
)
|
)
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
"nomprenom": etud.nomprenom,
|
"nomprenom": etud.nomprenom,
|
||||||
"just": nb_abs_just,
|
"just": nb_abs_just,
|
||||||
"nojust": nb_abs - nb_abs_just,
|
"nojust": nb_abs_nj,
|
||||||
"total": nb_abs,
|
"total": nb_abs,
|
||||||
"_nomprenom_target": url_for(
|
"_nomprenom_target": url_for(
|
||||||
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
"scolar.fiche_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
|
@ -38,6 +38,8 @@ JUSTIFICATIFS_FIELDS = {
|
|||||||
"external_data": dict,
|
"external_data": dict,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEPT_JUSTIFICATIFS_FIELDS = JUSTIFICATIFS_FIELDS | {"formsemestre": dict | None}
|
||||||
|
|
||||||
CREATE_FIELD = {"justif_id": int, "couverture": list}
|
CREATE_FIELD = {"justif_id": int, "couverture": list}
|
||||||
BATCH_FIELD = {"errors": list, "success": list}
|
BATCH_FIELD = {"errors": list, "success": list}
|
||||||
|
|
||||||
@ -169,6 +171,32 @@ def test_route_justificatifs(api_headers):
|
|||||||
check_failure_get(f"/justificatifs/{FAUX}/query?", api_headers)
|
check_failure_get(f"/justificatifs/{FAUX}/query?", api_headers)
|
||||||
|
|
||||||
|
|
||||||
|
def test_route_justificatifs_formsemestre(api_headers):
|
||||||
|
"""test de la route /justificatifs/formsemestre/<int:formsemestre_id>"""
|
||||||
|
# Bon fonctionnement
|
||||||
|
|
||||||
|
data = GET(path="/justificatifs/formsemestre/1", headers=api_headers)
|
||||||
|
assert isinstance(data, list)
|
||||||
|
for just in data:
|
||||||
|
check_fields(just, JUSTIFICATIFS_FIELDS)
|
||||||
|
|
||||||
|
# Mauvais fonctionnement
|
||||||
|
check_failure_get(path="/justificatifs/formsemestre/42069", headers=api_headers)
|
||||||
|
|
||||||
|
|
||||||
|
def test_justificatifs_dept(api_headers):
|
||||||
|
"""test de la route /justificatifs/dept/<int:dept_id>"""
|
||||||
|
# Bon fonctionnement
|
||||||
|
|
||||||
|
data = GET(path="/justificatifs/dept/1", headers=api_headers)
|
||||||
|
assert isinstance(data, list)
|
||||||
|
for just in data:
|
||||||
|
check_fields(just, DEPT_JUSTIFICATIFS_FIELDS)
|
||||||
|
|
||||||
|
# Mauvais fonctionnement
|
||||||
|
check_failure_get(path="/justificatifs/dept/42069", headers=api_headers)
|
||||||
|
|
||||||
|
|
||||||
def test_route_create(api_admin_headers):
|
def test_route_create(api_admin_headers):
|
||||||
"""test de la route /justificatif/<justif_id:int>/create"""
|
"""test de la route /justificatif/<justif_id:int>/create"""
|
||||||
# -== Unique ==-
|
# -== Unique ==-
|
||||||
|
@ -20,7 +20,6 @@ from app.models import (
|
|||||||
ModuleImpl,
|
ModuleImpl,
|
||||||
Absence,
|
Absence,
|
||||||
)
|
)
|
||||||
from app.models.assiduites import compute_assiduites_justified
|
|
||||||
|
|
||||||
from app.scodoc.sco_exceptions import ScoValueError
|
from app.scodoc.sco_exceptions import ScoValueError
|
||||||
from tests.unit import sco_fake_gen
|
from tests.unit import sco_fake_gen
|
||||||
@ -73,8 +72,6 @@ def test_general(test_client):
|
|||||||
verifier_comptage_et_filtrage_assiduites(etuds, moduleimpls[:4], formsemestres)
|
verifier_comptage_et_filtrage_assiduites(etuds, moduleimpls[:4], formsemestres)
|
||||||
verifier_filtrage_justificatifs(etuds[0], justificatifs)
|
verifier_filtrage_justificatifs(etuds[0], justificatifs)
|
||||||
|
|
||||||
essais_cache(etuds[0].etudid, formsemestres[:2], moduleimpls)
|
|
||||||
|
|
||||||
editer_supprimer_assiduites(etuds, moduleimpls)
|
editer_supprimer_assiduites(etuds, moduleimpls)
|
||||||
editer_supprimer_justificatif(etuds[0])
|
editer_supprimer_justificatif(etuds[0])
|
||||||
|
|
||||||
@ -403,54 +400,6 @@ def _get_justi(
|
|||||||
).first()
|
).first()
|
||||||
|
|
||||||
|
|
||||||
def essais_cache(etudid, sems: tuple[FormSemestre], moduleimpls: list[ModuleImpl]):
|
|
||||||
"""Vérification des fonctionnalités du cache"""
|
|
||||||
# TODO faire un test séparé du test_general
|
|
||||||
# voir test_calcul_assiduites pour faire
|
|
||||||
|
|
||||||
date_deb: str = "2022-09-01T07:00"
|
|
||||||
date_fin: str = "2023-01-31T19:00"
|
|
||||||
|
|
||||||
assiduites_count_no_cache = scass.get_assiduites_count_in_interval(
|
|
||||||
etudid, date_deb, date_fin
|
|
||||||
)
|
|
||||||
assiduites_count_cache = scass.get_assiduites_count_in_interval(
|
|
||||||
etudid, date_deb, date_fin
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
|
||||||
assiduites_count_cache == assiduites_count_no_cache == (2, 1)
|
|
||||||
), "Erreur cache classique"
|
|
||||||
|
|
||||||
assert scass.formsemestre_get_assiduites_count(etudid, sems[0]) == (
|
|
||||||
2,
|
|
||||||
1,
|
|
||||||
), "Erreur formsemestre_get_assiduites_count (sans module) A"
|
|
||||||
assert scass.formsemestre_get_assiduites_count(etudid, sems[1]) == (
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
), "Erreur formsemestre_get_assiduites_count (sans module) B"
|
|
||||||
|
|
||||||
assert scass.formsemestre_get_assiduites_count(
|
|
||||||
etudid, sems[0], moduleimpl_id=moduleimpls[0].id
|
|
||||||
) == (
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
), "Erreur formsemestre_get_assiduites_count (avec module) A"
|
|
||||||
assert scass.formsemestre_get_assiduites_count(
|
|
||||||
etudid, sems[0], moduleimpl_id=moduleimpls[1].id
|
|
||||||
) == (
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
), "Erreur formsemestre_get_assiduites_count (avec module) A"
|
|
||||||
assert scass.formsemestre_get_assiduites_count(
|
|
||||||
etudid, sems[0], moduleimpl_id=moduleimpls[2].id
|
|
||||||
) == (
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
), "Erreur formsemestre_get_assiduites_count (avec module) A"
|
|
||||||
|
|
||||||
|
|
||||||
def ajouter_justificatifs(etud):
|
def ajouter_justificatifs(etud):
|
||||||
"""test de l'ajout des justificatifs"""
|
"""test de l'ajout des justificatifs"""
|
||||||
|
|
||||||
@ -498,10 +447,9 @@ def ajouter_justificatifs(etud):
|
|||||||
)
|
)
|
||||||
db.session.add(just_obj)
|
db.session.add(just_obj)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
just_obj.justifier_assiduites()
|
||||||
justificatifs.append(just_obj)
|
justificatifs.append(just_obj)
|
||||||
|
|
||||||
compute_assiduites_justified(etud.etudid, justificatifs)
|
|
||||||
|
|
||||||
# Vérification de la création des justificatifs
|
# Vérification de la création des justificatifs
|
||||||
assert [
|
assert [
|
||||||
justi for justi in justificatifs if not isinstance(justi, Justificatif)
|
justi for justi in justificatifs if not isinstance(justi, Justificatif)
|
||||||
@ -1416,6 +1364,7 @@ def test_cas_justificatifs(test_client):
|
|||||||
Tests de certains cas particuliers des justificatifs
|
Tests de certains cas particuliers des justificatifs
|
||||||
- Création du justificatif avant ou après assiduité
|
- Création du justificatif avant ou après assiduité
|
||||||
- Assiduité complétement couverte ou non
|
- Assiduité complétement couverte ou non
|
||||||
|
- Modification de la couverture (edition du justificatif)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data = _setup_fake_db(
|
data = _setup_fake_db(
|
||||||
@ -1462,7 +1411,7 @@ def test_cas_justificatifs(test_client):
|
|||||||
etat=scu.EtatJustificatif.VALIDE,
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
)
|
)
|
||||||
|
|
||||||
compute_assiduites_justified(etud_1.etudid, [justif_2])
|
justif_2.justifier_assiduites()
|
||||||
|
|
||||||
assert len(scass.justifies(justif_2)) == 1, "Justification non prise en compte (b1)"
|
assert len(scass.justifies(justif_2)) == 1, "Justification non prise en compte (b1)"
|
||||||
|
|
||||||
@ -1496,7 +1445,8 @@ def test_cas_justificatifs(test_client):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Mise à jour de l'assiduité
|
# Mise à jour de l'assiduité
|
||||||
compute_assiduites_justified(etud_1.etudid, [justif_3, justif_4])
|
justif_3.justifier_assiduites()
|
||||||
|
justif_4.justifier_assiduites()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
len(scass.justifies(justif_3)) == 1
|
len(scass.justifies(justif_3)) == 1
|
||||||
@ -1504,3 +1454,279 @@ def test_cas_justificatifs(test_client):
|
|||||||
assert (
|
assert (
|
||||||
len(scass.justifies(justif_4)) == 0
|
len(scass.justifies(justif_4)) == 0
|
||||||
), "Justification complète non prise en compte (c2)"
|
), "Justification complète non prise en compte (c2)"
|
||||||
|
|
||||||
|
# <- Vérification modification de la couverture ->
|
||||||
|
|
||||||
|
# Deux assiduités, 8/01/2024 de 8h à 10h et 14h à 16h
|
||||||
|
|
||||||
|
assi_2: Assiduite = Assiduite.create_assiduite(
|
||||||
|
etud=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-08T08:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-08T10:00", True),
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
)
|
||||||
|
assi_3: Assiduite = Assiduite.create_assiduite(
|
||||||
|
etud=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-08T14:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-08T16:00", True),
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# <=>Justification complète<=>
|
||||||
|
# les deux assiduités sont couvertes
|
||||||
|
|
||||||
|
justif_5: Justificatif = Justificatif.create_justificatif(
|
||||||
|
etudiant=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-08T00:00:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-08T23:59:59", True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Justification des assiduités
|
||||||
|
assi_ids: list[int] = justif_5.justifier_assiduites()
|
||||||
|
assert len(assi_ids) == 2, "Vérification Modification couverture (d1)"
|
||||||
|
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (d2)"
|
||||||
|
assert assi_3.assiduite_id in assi_ids, "Vérification Modification couverture (d3)"
|
||||||
|
assert assi_2.est_just is True, "Vérification Modification couverture (d4)"
|
||||||
|
assert assi_3.est_just is True, "Vérification Modification couverture (d5)"
|
||||||
|
|
||||||
|
# Déjustification des assiduités
|
||||||
|
justif_5.dejustifier_assiduites()
|
||||||
|
assi_ids: list[int] = justif_5.dejustifier_assiduites()
|
||||||
|
assert len(assi_ids) == 2, "Vérification Modification couverture (d6)"
|
||||||
|
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (d7)"
|
||||||
|
assert assi_3.assiduite_id in assi_ids, "Vérification Modification couverture (d8)"
|
||||||
|
assert assi_2.est_just is False, "Vérification Modification couverture (d9)"
|
||||||
|
assert assi_3.est_just is False, "Vérification Modification couverture (d10)"
|
||||||
|
|
||||||
|
# <=>Justification Partielle<=>
|
||||||
|
# Seule la première assiduité est couverte
|
||||||
|
|
||||||
|
justif_5.date_fin = scu.is_iso_formated("2024-01-08T11:00", True)
|
||||||
|
db.session.add(justif_5)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
assi_ids: list[int] = justif_5.justifier_assiduites()
|
||||||
|
assert len(assi_ids) == 1, "Vérification Modification couverture (e1)"
|
||||||
|
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (e2)"
|
||||||
|
assert (
|
||||||
|
assi_3.assiduite_id not in assi_ids
|
||||||
|
), "Vérification Modification couverture (e3)"
|
||||||
|
assert assi_2.est_just is True, "Vérification Modification couverture (e4)"
|
||||||
|
assert assi_3.est_just is False, "Vérification Modification couverture (e5)"
|
||||||
|
|
||||||
|
assi_ids: list[int] = justif_5.dejustifier_assiduites()
|
||||||
|
assert len(assi_ids) == 1, "Vérification Modification couverture (e6)"
|
||||||
|
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (e7)"
|
||||||
|
assert (
|
||||||
|
assi_3.assiduite_id not in assi_ids
|
||||||
|
), "Vérification Modification couverture (e3)"
|
||||||
|
assert assi_2.est_just is False, "Vérification Modification couverture (e8)"
|
||||||
|
assert assi_3.est_just is False, "Vérification Modification couverture (e9)"
|
||||||
|
|
||||||
|
# <=>Justification Multiple<=>
|
||||||
|
# Deux justificatifs couvrent une même assiduité
|
||||||
|
|
||||||
|
# on justifie la première assiduité avec le premier justificatif
|
||||||
|
justif_5.justifier_assiduites()
|
||||||
|
|
||||||
|
# deuxième justificatif
|
||||||
|
justif_6: Justificatif = Justificatif.create_justificatif(
|
||||||
|
etudiant=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-08T08:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-08T10:00", True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
)
|
||||||
|
|
||||||
|
assi_ids: list[int] = justif_6.justifier_assiduites()
|
||||||
|
assert len(assi_ids) == 1, "Vérification Modification couverture (f1)"
|
||||||
|
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (f2)"
|
||||||
|
assert (
|
||||||
|
assi_3.assiduite_id not in assi_ids
|
||||||
|
), "Vérification Modification couverture (f3)"
|
||||||
|
assert assi_2.est_just is True, "Vérification Modification couverture (f4)"
|
||||||
|
assert assi_3.est_just is False, "Vérification Modification couverture (f5)"
|
||||||
|
|
||||||
|
# on déjustifie le justificatif 5
|
||||||
|
justif_5.etat = scu.EtatJustificatif.NON_VALIDE
|
||||||
|
db.session.add(justif_5)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
assi_ids: list[int] = justif_5.dejustifier_assiduites()
|
||||||
|
assert len(assi_ids) == 0, "Vérification Modification couverture (f6)"
|
||||||
|
assert (
|
||||||
|
assi_2.assiduite_id not in assi_ids
|
||||||
|
), "Vérification Modification couverture (f7)"
|
||||||
|
assert assi_2.est_just is True, "Vérification Modification couverture (f8)"
|
||||||
|
|
||||||
|
# on déjustifie le justificatif 6
|
||||||
|
justif_6.etat = scu.EtatJustificatif.NON_VALIDE
|
||||||
|
db.session.add(justif_6)
|
||||||
|
db.session.commit()
|
||||||
|
assi_ids: list[int] = justif_6.dejustifier_assiduites()
|
||||||
|
assert len(assi_ids) == 1, "Vérification Modification couverture (f9)"
|
||||||
|
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (f10)"
|
||||||
|
assert assi_2.est_just is False, "Vérification Modification couverture (f11)"
|
||||||
|
|
||||||
|
# <=>Justification Chevauchée<=>
|
||||||
|
# 1 justificatif chevauche une assiduité (8h -> 10h) (9h -> 11h)
|
||||||
|
|
||||||
|
justif_7: Justificatif = Justificatif.create_justificatif(
|
||||||
|
etudiant=etud_1,
|
||||||
|
date_debut=scu.is_iso_formated("2024-01-08T09:00", True),
|
||||||
|
date_fin=scu.is_iso_formated("2024-01-08T11:00", True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
)
|
||||||
|
|
||||||
|
assi_ids: list[int] = justif_7.justifier_assiduites()
|
||||||
|
assert len(assi_ids) == 0, "Vérification Modification couverture (g1)"
|
||||||
|
assert (
|
||||||
|
assi_2.assiduite_id not in assi_ids
|
||||||
|
), "Vérification Modification couverture (g2)"
|
||||||
|
assert assi_2.est_just is False, "Vérification Modification couverture (g3)"
|
||||||
|
|
||||||
|
# Modification pour correspondre à l'assiduité
|
||||||
|
justif_7.date_debut = scu.is_iso_formated("2024-01-08T08:00", True)
|
||||||
|
db.session.add(justif_7)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
assi_ids: list[int] = justif_7.justifier_assiduites()
|
||||||
|
assert len(assi_ids) == 1, "Vérification Modification couverture (g4)"
|
||||||
|
assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (g5)"
|
||||||
|
assert assi_2.est_just is True, "Vérification Modification couverture (g6)"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cache_assiduites(test_client):
|
||||||
|
"""Vérification du bon fonctionnement du cache des assiduités"""
|
||||||
|
|
||||||
|
data = _setup_fake_db(
|
||||||
|
[("2024-01-01", "2024-06-30"), ("2024-07-01", "2024-12-31")],
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
formsemestre1: FormSemestre = data["formsemestres"][0]
|
||||||
|
formsemestre2: FormSemestre = data["formsemestres"][1]
|
||||||
|
|
||||||
|
moduleimpl: ModuleImpl = data["moduleimpls"][0]
|
||||||
|
etud: Identite = data["etuds"][0]
|
||||||
|
|
||||||
|
# Création des assiduités
|
||||||
|
assiduites: list[dict] = [
|
||||||
|
# Semestre 1
|
||||||
|
{
|
||||||
|
"date_debut": "2024-01-08T08:00",
|
||||||
|
"date_fin": "2024-01-08T10:00",
|
||||||
|
"moduleimpl": moduleimpl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-01-08T14:00",
|
||||||
|
"date_fin": "2024-01-08T16:00",
|
||||||
|
"moduleimpl": moduleimpl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-01-09T08:00",
|
||||||
|
"date_fin": "2024-01-09T10:00",
|
||||||
|
"moduleimpl": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-01-09T14:00",
|
||||||
|
"date_fin": "2024-01-09T16:00",
|
||||||
|
"moduleimpl": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-01-10T08:00",
|
||||||
|
"date_fin": "2024-01-10T10:00",
|
||||||
|
"moduleimpl": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-01-10T14:00",
|
||||||
|
"date_fin": "2024-01-10T16:00",
|
||||||
|
"moduleimpl": moduleimpl,
|
||||||
|
},
|
||||||
|
# Semestre 2
|
||||||
|
{
|
||||||
|
"date_debut": "2024-07-09T14:00",
|
||||||
|
"date_fin": "2024-07-09T16:00",
|
||||||
|
"moduleimpl": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-07-10T08:00",
|
||||||
|
"date_fin": "2024-07-10T10:00",
|
||||||
|
"moduleimpl": None,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-07-10T14:00",
|
||||||
|
"date_fin": "2024-07-10T16:00",
|
||||||
|
"moduleimpl": None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
justificatifs: list[dict] = [
|
||||||
|
{
|
||||||
|
"date_debut": "2024-01-10T00:00",
|
||||||
|
"date_fin": "2024-01-10T23:59",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date_debut": "2024-07-09T00:00",
|
||||||
|
"date_fin": "2024-07-09T23:59",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# On ajoute les assiduités et les justificatifs
|
||||||
|
|
||||||
|
for assi in assiduites:
|
||||||
|
Assiduite.create_assiduite(
|
||||||
|
etud=etud,
|
||||||
|
date_debut=scu.is_iso_formated(assi["date_debut"], True),
|
||||||
|
date_fin=scu.is_iso_formated(assi["date_fin"], True),
|
||||||
|
moduleimpl=assi["moduleimpl"],
|
||||||
|
etat=scu.EtatAssiduite.ABSENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
for justi in justificatifs:
|
||||||
|
Justificatif.create_justificatif(
|
||||||
|
etudiant=etud,
|
||||||
|
date_debut=scu.is_iso_formated(justi["date_debut"], True),
|
||||||
|
date_fin=scu.is_iso_formated(justi["date_fin"], True),
|
||||||
|
etat=scu.EtatJustificatif.VALIDE,
|
||||||
|
).justifier_assiduites()
|
||||||
|
|
||||||
|
# Premier semestre 4nj / 2j / 6t
|
||||||
|
assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (4, 2, 6)
|
||||||
|
assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (4, 2, 6)
|
||||||
|
|
||||||
|
# ModuleImpl 2nj / 1j / 3t
|
||||||
|
assert scass.formsemestre_get_assiduites_count(
|
||||||
|
etud.id, formsemestre1, moduleimpl.id
|
||||||
|
) == (2, 1, 3)
|
||||||
|
# Deuxième semestre 2nj / 1j / 3t
|
||||||
|
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
||||||
|
|
||||||
|
# On supprime la première assiduité (sans invalider le cache)
|
||||||
|
assi: Assiduite = Assiduite.query.filter_by(etudid=etud.id).first()
|
||||||
|
db.session.delete(assi)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# Premier semestre 4nj / 2j / 6t (Identique car cache)
|
||||||
|
assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (4, 2, 6)
|
||||||
|
assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (4, 2, 6)
|
||||||
|
# ModuleImpl 1nj / 1j / 2t (Change car non cache)
|
||||||
|
assert scass.formsemestre_get_assiduites_count(
|
||||||
|
etud.id, formsemestre1, moduleimpl.id
|
||||||
|
) == (1, 1, 2)
|
||||||
|
# Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
|
||||||
|
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
||||||
|
|
||||||
|
# On invalide maintenant le cache
|
||||||
|
scass.invalidate_assiduites_count(etud.id, formsemestre1.to_dict())
|
||||||
|
|
||||||
|
# Premier semestre 3nj / 2j / 5t (Change car cache invalidé)
|
||||||
|
assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (3, 2, 5)
|
||||||
|
assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (3, 2, 5)
|
||||||
|
# ModuleImpl 1nj / 1j / 2t (Ne change pas car pas de changement)
|
||||||
|
assert scass.formsemestre_get_assiduites_count(
|
||||||
|
etud.id, formsemestre1, moduleimpl.id
|
||||||
|
) == (1, 1, 2)
|
||||||
|
# Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
|
||||||
|
assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
|
||||||
|
@ -33,7 +33,6 @@ from app.scodoc import sco_formsemestre_validation
|
|||||||
from app.scodoc import sco_cursus_dut
|
from app.scodoc import sco_cursus_dut
|
||||||
from app.scodoc import sco_saisie_notes
|
from app.scodoc import sco_saisie_notes
|
||||||
from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif, localize_datetime
|
from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif, localize_datetime
|
||||||
from app.models.assiduites import compute_assiduites_justified
|
|
||||||
|
|
||||||
DEPT = TestConfig.DEPT_TEST
|
DEPT = TestConfig.DEPT_TEST
|
||||||
|
|
||||||
@ -192,7 +191,7 @@ def run_sco_basic(verbose=False, dept=None) -> FormSemestre:
|
|||||||
etudid = etuds[0]["etudid"]
|
etudid = etuds[0]["etudid"]
|
||||||
|
|
||||||
_signal_absences_justificatifs(etudid)
|
_signal_absences_justificatifs(etudid)
|
||||||
nbabs, nbabsjust = scass.get_assiduites_count(etudid, sem)
|
_, nbabsjust, nbabs = scass.get_assiduites_count(etudid, sem)
|
||||||
assert nbabs == 6, f"incorrect nbabs ({nbabs})"
|
assert nbabs == 6, f"incorrect nbabs ({nbabs})"
|
||||||
assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})"
|
assert nbabsjust == 2, f"incorrect nbabsjust ({nbabsjust})"
|
||||||
|
|
||||||
@ -267,8 +266,5 @@ def _signal_absences_justificatifs(etudid: int):
|
|||||||
etat=EtatJustificatif.VALIDE,
|
etat=EtatJustificatif.VALIDE,
|
||||||
)
|
)
|
||||||
db.session.add(justif)
|
db.session.add(justif)
|
||||||
compute_assiduites_justified(
|
|
||||||
etud.etudid,
|
|
||||||
[justif],
|
|
||||||
)
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
justif.justifier_assiduites()
|
||||||
|
@ -396,7 +396,7 @@ def ajouter_assiduites_justificatifs(formsemestre: FormSemestre):
|
|||||||
|
|
||||||
for etud in formsemestre.etuds:
|
for etud in formsemestre.etuds:
|
||||||
base_date = datetime.datetime(
|
base_date = datetime.datetime(
|
||||||
2022, 9, [5, 12, 19, 26][random.randint(0, 3)], 8, 0, 0
|
2021, 9, [6, 13, 20, 27][random.randint(0, 3)], 8, 0, 0
|
||||||
)
|
)
|
||||||
base_date = localize_datetime(base_date)
|
base_date = localize_datetime(base_date)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user