Assiduité : optimisation justification assiduités

This commit is contained in:
Iziram 2024-02-29 14:20:39 +01:00
parent 21b2e0f582
commit 5158bd0c8f
6 changed files with 81 additions and 108 deletions

View File

@ -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")

View File

@ -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é

View File

@ -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])

View File

@ -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)

View File

@ -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
@ -498,10 +497,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)
@ -1462,7 +1460,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 +1494,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

View File

@ -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
@ -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()