forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -59,7 +59,13 @@ def get_model_api_object(model_cls: db.Model, model_id: int, join_cls: db.Model
|
|||||||
query = model_cls.query.filter_by(id=model_id)
|
query = model_cls.query.filter_by(id=model_id)
|
||||||
if g.scodoc_dept and join_cls is not None:
|
if g.scodoc_dept and join_cls is not None:
|
||||||
query = query.join(join_cls).filter_by(dept_id=g.scodoc_dept_id)
|
query = query.join(join_cls).filter_by(dept_id=g.scodoc_dept_id)
|
||||||
unique: model_cls = query.first_or_404()
|
unique: model_cls = query.first()
|
||||||
|
|
||||||
|
if unique is None:
|
||||||
|
return scu.json_error(
|
||||||
|
404,
|
||||||
|
message=f"{model_cls.__name__} inexistant(e)",
|
||||||
|
)
|
||||||
|
|
||||||
return unique.to_dict(format_api=True)
|
return unique.to_dict(format_api=True)
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ from app.scodoc.sco_utils import json_error
|
|||||||
@api_web_bp.route("/assiduite/<int:assiduite_id>")
|
@api_web_bp.route("/assiduite/<int:assiduite_id>")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
|
@as_json
|
||||||
def assiduite(assiduite_id: int = None):
|
def assiduite(assiduite_id: int = None):
|
||||||
"""Retourne un objet assiduité à partir de son id
|
"""Retourne un objet assiduité à partir de son id
|
||||||
|
|
||||||
@ -172,6 +173,7 @@ def count_assiduites(
|
|||||||
404,
|
404,
|
||||||
message="étudiant inconnu",
|
message="étudiant inconnu",
|
||||||
)
|
)
|
||||||
|
g.scodoc_dept_id = etud.dept_id
|
||||||
|
|
||||||
# Les filtres qui seront appliqués au comptage (type, date, etudid...)
|
# Les filtres qui seront appliqués au comptage (type, date, etudid...)
|
||||||
filtered: dict[str, object] = {}
|
filtered: dict[str, object] = {}
|
||||||
@ -444,6 +446,8 @@ def count_assiduites_formsemestre(
|
|||||||
if formsemestre is None:
|
if formsemestre is None:
|
||||||
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
|
||||||
|
|
||||||
|
g.scodoc_dept_id = formsemestre.dept_id
|
||||||
|
|
||||||
# Récupération des étudiants du formsemestre
|
# Récupération des étudiants du formsemestre
|
||||||
etuds = formsemestre.etuds.all()
|
etuds = formsemestre.etuds.all()
|
||||||
etuds_id = [etud.id for etud in etuds]
|
etuds_id = [etud.id for etud in etuds]
|
||||||
@ -833,9 +837,9 @@ def assiduite_edit(assiduite_id: int):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Récupération de l'assiduité à modifier
|
# Récupération de l'assiduité à modifier
|
||||||
assiduite_unique: Assiduite = Assiduite.query.filter_by(
|
assiduite_unique: Assiduite = Assiduite.query.filter_by(id=assiduite_id).first()
|
||||||
id=assiduite_id
|
if assiduite_unique is None:
|
||||||
).first_or_404()
|
return json_error(404, "Assiduité non existante")
|
||||||
# Récupération des valeurs à modifier
|
# Récupération des valeurs à modifier
|
||||||
data = request.get_json(force=True)
|
data = request.get_json(force=True)
|
||||||
|
|
||||||
|
@ -154,7 +154,9 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
|||||||
"""XXX TODO missing doc"""
|
"""XXX TODO missing doc"""
|
||||||
|
|
||||||
# Récupération du département et des étudiants du département
|
# Récupération du département et des étudiants du département
|
||||||
dept: Departement = Departement.query.get_or_404(dept_id)
|
dept: Departement = Departement.query.get(dept_id)
|
||||||
|
if dept is None:
|
||||||
|
json_error(404, "Assiduité non existante")
|
||||||
etuds: list[int] = [etud.id for etud in dept.etudiants]
|
etuds: list[int] = [etud.id for etud in dept.etudiants]
|
||||||
|
|
||||||
# Récupération des justificatifs des étudiants du département
|
# Récupération des justificatifs des étudiants du département
|
||||||
|
@ -119,15 +119,15 @@ def check_ics_regexp(form, field):
|
|||||||
class ConfigAssiduitesForm(FlaskForm):
|
class ConfigAssiduitesForm(FlaskForm):
|
||||||
"Formulaire paramétrage Module Assiduité"
|
"Formulaire paramétrage Module Assiduité"
|
||||||
|
|
||||||
morning_time = TimeField(
|
assi_morning_time = TimeField(
|
||||||
"Début de la journée"
|
"Début de la journée"
|
||||||
) # TODO utiliser TextField + timepicker voir AjoutAssiOrJustForm
|
) # TODO utiliser TextField + timepicker voir AjoutAssiOrJustForm
|
||||||
lunch_time = TimeField(
|
assi_lunch_time = TimeField(
|
||||||
"Heure de midi (date pivot entre matin et après-midi)"
|
"Heure de midi (date pivot entre matin et après-midi)"
|
||||||
) # TODO
|
) # TODO
|
||||||
afternoon_time = TimeField("Fin de la journée") # TODO
|
assi_afternoon_time = TimeField("Fin de la journée") # TODO
|
||||||
|
|
||||||
tick_time = DecimalField(
|
assi_tick_time = DecimalField(
|
||||||
"Granularité de la timeline (temps en minutes)",
|
"Granularité de la timeline (temps en minutes)",
|
||||||
places=0,
|
places=0,
|
||||||
validators=[check_tick_time],
|
validators=[check_tick_time],
|
||||||
|
@ -25,6 +25,7 @@ from app.scodoc.sco_utils import (
|
|||||||
EtatJustificatif,
|
EtatJustificatif,
|
||||||
localize_datetime,
|
localize_datetime,
|
||||||
is_assiduites_module_forced,
|
is_assiduites_module_forced,
|
||||||
|
NonWorkDays,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -154,6 +155,33 @@ class Assiduite(ScoDocModel):
|
|||||||
)
|
)
|
||||||
if date_fin.tzinfo is None:
|
if date_fin.tzinfo is None:
|
||||||
log(f"Warning: create_assiduite: date_fin without timezone ({date_fin})")
|
log(f"Warning: create_assiduite: date_fin without timezone ({date_fin})")
|
||||||
|
|
||||||
|
# Vérification jours non travaillés
|
||||||
|
# -> vérifie si la date de début ou la date de fin est sur un jour non travaillé
|
||||||
|
# On récupère les formsemestres des dates de début et de fin
|
||||||
|
formsemetre_date_debut: FormSemestre = get_formsemestre_from_data(
|
||||||
|
{
|
||||||
|
"etudid": etud.id,
|
||||||
|
"date_debut": date_debut,
|
||||||
|
"date_fin": date_debut,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
formsemetre_date_fin: FormSemestre = get_formsemestre_from_data(
|
||||||
|
{
|
||||||
|
"etudid": etud.id,
|
||||||
|
"date_debut": date_fin,
|
||||||
|
"date_fin": date_fin,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if date_debut.weekday() in NonWorkDays.get_all_non_work_days(
|
||||||
|
formsemestre_id=formsemetre_date_debut
|
||||||
|
):
|
||||||
|
raise ScoValueError("La date de début n'est pas un jour travaillé")
|
||||||
|
if date_fin.weekday() in NonWorkDays.get_all_non_work_days(
|
||||||
|
formsemestre_id=formsemetre_date_fin
|
||||||
|
):
|
||||||
|
raise ScoValueError("La date de fin n'est pas un jour travaillé")
|
||||||
|
|
||||||
# Vérification de non duplication des périodes
|
# Vérification de non duplication des périodes
|
||||||
assiduites: Query = etud.assiduites
|
assiduites: Query = etud.assiduites
|
||||||
if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite):
|
if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite):
|
||||||
|
@ -15,59 +15,222 @@ 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
|
||||||
from app.scodoc import sco_etud
|
from app.scodoc import sco_etud
|
||||||
|
from app.models import ScoDocSiteConfig
|
||||||
|
from flask import g
|
||||||
|
|
||||||
|
|
||||||
class CountCalculator:
|
class CountCalculator:
|
||||||
"""Classe qui gére le comptage des assiduités"""
|
"""
|
||||||
|
La classe CountCalculator est conçue pour gérer le comptage des assiduités,
|
||||||
|
en calculant le nombre total de jours complets,
|
||||||
|
de demi-journées, et d'heures passées sur une période donnée.
|
||||||
|
Elle prend en compte les jours non travaillés,
|
||||||
|
les horaires de travail standard et les assiduités s'étendant sur plusieurs jours.
|
||||||
|
|
||||||
# TODO documenter
|
Utilisation :
|
||||||
|
------------
|
||||||
|
1. Initialisation : La classe peut être initialisée avec des horaires personnalisés
|
||||||
|
pour le matin, le midi et le soir, ainsi qu'une durée de pause déjeuner.
|
||||||
|
Si non spécifiés, les valeurs par défaut seront chargées depuis la configuration `ScoDocSiteConfig`.
|
||||||
|
Exemple d'initialisation :
|
||||||
|
calculator = CountCalculator(morning="08:00", noon="13:00", evening="18:00", nb_heures_par_jour=8)
|
||||||
|
|
||||||
|
2. Ajout d'assiduités :
|
||||||
|
Exemple d'ajout d'assiduité :
|
||||||
|
- calculator.compute_assiduites(etudiant.assiduites)
|
||||||
|
- calculator.compute_assiduites([<Assiduite>, <Assiduite>, <Assiduite>, <Assiduite>])
|
||||||
|
|
||||||
|
3. Accès aux métriques : Après l'ajout des assiduités, on peut accéder aux métriques telles que :
|
||||||
|
le nombre total de jours, de demi-journées et d'heures calculées.
|
||||||
|
Exemple d'accès aux métriques :
|
||||||
|
metrics = calculator.to_dict()
|
||||||
|
|
||||||
|
4.Réinitialisation du comptage: Si besoin on peut réinitialisé le compteur sans perdre la configuration
|
||||||
|
(horaires personnalisés)
|
||||||
|
Exemple de réinitialisation :
|
||||||
|
calculator.reset()
|
||||||
|
|
||||||
|
Méthodes Principales :
|
||||||
|
---------------------
|
||||||
|
- reset() : Réinitialise les compteurs de la classe.
|
||||||
|
- add_half_day(day: date, is_morning: bool) : Ajoute une demi-journée au comptage.
|
||||||
|
- add_day(day: date) : Ajoute un jour complet au comptage.
|
||||||
|
- compute_long_assiduite(assi: Assiduite) : Traite les assiduités s'étendant sur plus d'un jour.
|
||||||
|
- compute_assiduites(assiduites: Query | list) : Calcule les métriques pour une collection d'assiduités.
|
||||||
|
- to_dict() : Retourne les métriques sous forme de dictionnaire.
|
||||||
|
|
||||||
|
Notes :
|
||||||
|
------
|
||||||
|
|
||||||
|
Détails des calculs des heures:
|
||||||
|
Pour les assiduités courtes (<= 1 jour):
|
||||||
|
heures = assi.deb - assi.fin
|
||||||
|
Pour les assiduités longues (> 1 jour):
|
||||||
|
heures =
|
||||||
|
heures(assi.deb => fin_journee)
|
||||||
|
nb_heure_par_jour * (nb_jours-2) +
|
||||||
|
heures(assi.fin => fin_journee)
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
morning: time = time(8, 0), # TODO utiliser ScoDocSiteConfig
|
morning: str = None,
|
||||||
noon: time = time(12, 0),
|
noon: str = None,
|
||||||
after_noon: time = time(14, 00),
|
evening: str = None,
|
||||||
evening: time = time(18, 0),
|
nb_heures_par_jour: int = None,
|
||||||
skip_saturday: bool = True, # TODO préférence workdays
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.morning: time = morning
|
# Transformation d'une heure "HH:MM" en time(h,m)
|
||||||
self.noon: time = noon
|
STR_TIME = lambda x: time(*list(map(int, x.split(":"))))
|
||||||
self.after_noon: time = after_noon
|
|
||||||
self.evening: time = evening
|
|
||||||
self.skip_saturday: bool = skip_saturday
|
|
||||||
|
|
||||||
delta_total: timedelta = datetime.combine(date.min, evening) - datetime.combine(
|
self.morning: time = STR_TIME(
|
||||||
date.min, morning
|
morning if morning else ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
||||||
|
)
|
||||||
|
# Date pivot pour déterminer les demi-journées
|
||||||
|
self.noon: time = STR_TIME(
|
||||||
|
noon if noon else ScoDocSiteConfig.get("assi_lunch_time", "13:00")
|
||||||
|
)
|
||||||
|
self.evening: time = STR_TIME(
|
||||||
|
evening if evening else ScoDocSiteConfig.get("assi_afternoon_time", "18:00")
|
||||||
)
|
)
|
||||||
delta_lunch: timedelta = datetime.combine(
|
|
||||||
date.min, after_noon
|
|
||||||
) - datetime.combine(date.min, noon)
|
|
||||||
|
|
||||||
self.hour_per_day: float = (delta_total - delta_lunch).total_seconds() / 3600
|
self.non_work_days: list[
|
||||||
|
scu.NonWorkDays
|
||||||
|
] = scu.NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
||||||
|
|
||||||
self.days: list[date] = []
|
delta_total: timedelta = datetime.combine(
|
||||||
self.half_days: list[tuple[date, bool]] = [] # tuple -> (date, morning:bool)
|
date.min, self.evening
|
||||||
self.hours: float = 0.0
|
) - datetime.combine(date.min, self.morning)
|
||||||
|
|
||||||
self.count: int = 0
|
# Sera utilisé pour les assiduités longues (> 1 journée)
|
||||||
|
self.nb_heures_par_jour = (
|
||||||
|
nb_heures_par_jour
|
||||||
|
if nb_heures_par_jour
|
||||||
|
else sco_preferences.get_preference(
|
||||||
|
"nb_heures_par_jour", dept_id=g.scodoc_dept_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.data = {}
|
||||||
|
self.reset()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Remet à zero les compteurs"""
|
"""Remet à zero les compteurs"""
|
||||||
self.days = []
|
self.data = {
|
||||||
self.half_days = []
|
"total": {
|
||||||
self.hours = 0.0
|
"journee": [],
|
||||||
self.count = 0
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
"absent": {
|
||||||
|
"journee": [],
|
||||||
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
"absent_just": {
|
||||||
|
"journee": [],
|
||||||
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
"absent_non_just": {
|
||||||
|
"journee": [],
|
||||||
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
"retard": {
|
||||||
|
"journee": [],
|
||||||
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
"retard_just": {
|
||||||
|
"journee": [],
|
||||||
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
"retard_non_just": {
|
||||||
|
"journee": [],
|
||||||
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
"present": {
|
||||||
|
"journee": [],
|
||||||
|
"demi": [],
|
||||||
|
"heure": 0,
|
||||||
|
"compte": 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def add_half_day(self, day: date, is_morning: bool = True):
|
def get_count_key(self, etat: scu.EtatAssiduite, justi: bool = False) -> str:
|
||||||
|
"""Récupère une clé de dictionnaire en fonction de l'état de l'assiduité
|
||||||
|
et si elle est justifié
|
||||||
|
"""
|
||||||
|
keys: dict[EtatAssiduite, str] = {
|
||||||
|
scu.EtatAssiduite.ABSENT: "absent",
|
||||||
|
scu.EtatAssiduite.RETARD: "retard",
|
||||||
|
scu.EtatAssiduite.PRESENT: "present",
|
||||||
|
}
|
||||||
|
count_key: str = keys.get(etat)
|
||||||
|
if etat != scu.EtatAssiduite.PRESENT:
|
||||||
|
count_key += "_just" if justi else "_non_just"
|
||||||
|
return count_key
|
||||||
|
|
||||||
|
def add_half_day(self, day: date, assi: Assiduite, is_morning: bool = True):
|
||||||
"""Ajoute une demi-journée dans le comptage"""
|
"""Ajoute une demi-journée dans le comptage"""
|
||||||
key: tuple[date, bool] = (day, is_morning)
|
key: tuple[date, bool] = (day, is_morning)
|
||||||
if key not in self.half_days:
|
|
||||||
self.half_days.append(key)
|
|
||||||
|
|
||||||
def add_day(self, day: date):
|
count_key: str = self.get_count_key(assi.etat, assi.est_just)
|
||||||
|
if assi.etat != scu.EtatAssiduite.PRESENT:
|
||||||
|
_key: str = scu.EtatAssiduite.inverse().get(assi.etat).name.lower()
|
||||||
|
if key not in self.data[_key]["demi"]:
|
||||||
|
self.data[_key]["demi"].append(day)
|
||||||
|
|
||||||
|
if key not in self.data["total"]["demi"]:
|
||||||
|
self.data["total"]["demi"].append(key)
|
||||||
|
if key not in self.data[count_key]["demi"]:
|
||||||
|
self.data[count_key]["demi"].append(key)
|
||||||
|
|
||||||
|
def add_day(self, day: date, assi: Assiduite):
|
||||||
"""Ajoute un jour dans le comptage"""
|
"""Ajoute un jour dans le comptage"""
|
||||||
if day not in self.days:
|
count_key: str = self.get_count_key(assi.etat, assi.est_just)
|
||||||
self.days.append(day)
|
if assi.etat != scu.EtatAssiduite.PRESENT:
|
||||||
|
key: str = scu.EtatAssiduite.inverse().get(assi.etat).name.lower()
|
||||||
|
if day not in self.data[key]["journee"]:
|
||||||
|
self.data[key]["journee"].append(day)
|
||||||
|
|
||||||
|
if day not in self.data["total"]["journee"]:
|
||||||
|
self.data["total"]["journee"].append(day)
|
||||||
|
if day not in self.data[count_key]["journee"]:
|
||||||
|
self.data[count_key]["journee"].append(day)
|
||||||
|
|
||||||
|
def add_hours(self, hours: float, assi: Assiduite):
|
||||||
|
"""Ajoute des heures dans le comptage"""
|
||||||
|
|
||||||
|
count_key: str = self.get_count_key(assi.etat, assi.est_just)
|
||||||
|
if assi.etat != scu.EtatAssiduite.PRESENT:
|
||||||
|
self.data[scu.EtatAssiduite.inverse().get(assi.etat).name.lower()][
|
||||||
|
"heure"
|
||||||
|
] += hours
|
||||||
|
|
||||||
|
self.data[count_key]["heure"] += hours
|
||||||
|
self.data["total"]["heure"] += hours
|
||||||
|
|
||||||
|
def add_count(self, assi: Assiduite):
|
||||||
|
"""Ajoute 1 count dans le comptage"""
|
||||||
|
|
||||||
|
count_key: str = self.get_count_key(assi.etat, assi.est_just)
|
||||||
|
if assi.etat != scu.EtatAssiduite.PRESENT:
|
||||||
|
self.data[scu.EtatAssiduite.inverse().get(assi.etat).name.lower()][
|
||||||
|
"compte"
|
||||||
|
] += 1
|
||||||
|
|
||||||
|
self.data[count_key]["compte"] += 1
|
||||||
|
self.data["total"]["compte"] += 1
|
||||||
|
|
||||||
def is_in_morning(self, period: tuple[datetime, datetime]) -> bool:
|
def is_in_morning(self, period: tuple[datetime, datetime]) -> bool:
|
||||||
"""Vérifiée si la période donnée fait partie du matin
|
"""Vérifiée si la période donnée fait partie du matin
|
||||||
@ -90,7 +253,9 @@ class CountCalculator:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
interval_evening: tuple[datetime, datetime] = (
|
interval_evening: tuple[datetime, datetime] = (
|
||||||
scu.localize_datetime(datetime.combine(period[0].date(), self.after_noon)),
|
scu.localize_datetime(
|
||||||
|
datetime.combine(period[0].date(), self.noon) + timedelta(seconds=1)
|
||||||
|
),
|
||||||
scu.localize_datetime(datetime.combine(period[0].date(), self.evening)),
|
scu.localize_datetime(datetime.combine(period[0].date(), self.evening)),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -102,15 +267,9 @@ class CountCalculator:
|
|||||||
"""Calcule les métriques sur une assiduité longue (plus d'un jour)"""
|
"""Calcule les métriques sur une assiduité longue (plus d'un jour)"""
|
||||||
|
|
||||||
pointer_date: date = assi.date_debut.date() + timedelta(days=1)
|
pointer_date: date = assi.date_debut.date() + timedelta(days=1)
|
||||||
start_hours: timedelta = assi.date_debut - scu.localize_datetime(
|
|
||||||
datetime.combine(assi.date_debut, self.morning)
|
|
||||||
)
|
|
||||||
finish_hours: timedelta = assi.date_fin - scu.localize_datetime(
|
|
||||||
datetime.combine(assi.date_fin, self.morning)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.add_day(assi.date_debut.date())
|
self.add_day(assi.date_debut.date(), assi)
|
||||||
self.add_day(assi.date_fin.date())
|
self.add_day(assi.date_fin.date(), assi)
|
||||||
|
|
||||||
start_period: tuple[datetime, datetime] = (
|
start_period: tuple[datetime, datetime] = (
|
||||||
assi.date_debut,
|
assi.date_debut,
|
||||||
@ -123,58 +282,67 @@ class CountCalculator:
|
|||||||
scu.localize_datetime(datetime.combine(assi.date_fin.date(), self.morning)),
|
scu.localize_datetime(datetime.combine(assi.date_fin.date(), self.morning)),
|
||||||
assi.date_fin,
|
assi.date_fin,
|
||||||
)
|
)
|
||||||
hours = 0.0
|
|
||||||
for period in (start_period, finish_period):
|
for period in (start_period, finish_period):
|
||||||
if self.is_in_evening(period):
|
if self.is_in_evening(period):
|
||||||
self.add_half_day(period[0].date(), False)
|
self.add_half_day(period[0].date(), assi, False)
|
||||||
if self.is_in_morning(period):
|
if self.is_in_morning(period):
|
||||||
self.add_half_day(period[0].date())
|
self.add_half_day(period[0].date(), assi)
|
||||||
|
|
||||||
while pointer_date < assi.date_fin.date():
|
while pointer_date < assi.date_fin.date():
|
||||||
# TODO : Utiliser la préférence de département : workdays
|
if pointer_date.weekday() not in self.non_work_days:
|
||||||
if pointer_date.weekday() < (6 - self.skip_saturday):
|
self.add_day(pointer_date, assi)
|
||||||
self.add_day(pointer_date)
|
self.add_half_day(pointer_date, assi)
|
||||||
self.add_half_day(pointer_date)
|
self.add_half_day(pointer_date, assi, False)
|
||||||
self.add_half_day(pointer_date, False)
|
self.add_hours(self.nb_heures_par_jour, assi)
|
||||||
self.hours += self.hour_per_day
|
|
||||||
hours += self.hour_per_day
|
|
||||||
|
|
||||||
pointer_date += timedelta(days=1)
|
pointer_date += timedelta(days=1)
|
||||||
|
|
||||||
self.hours += finish_hours.total_seconds() / 3600
|
# Gestion des heures des dates de début et des dates de fin
|
||||||
self.hours += self.hour_per_day - (start_hours.total_seconds() / 3600)
|
deb_hours = (start_period[1] - start_period[0]).total_seconds() / 3600
|
||||||
|
fin_hours = (finish_period[1] - finish_period[0]).total_seconds() / 3600
|
||||||
|
|
||||||
|
self.add_hours(deb_hours + fin_hours, assi)
|
||||||
|
|
||||||
def compute_assiduites(self, assiduites: Query | list):
|
def compute_assiduites(self, assiduites: Query | list):
|
||||||
"""Calcule les métriques pour la collection d'assiduité donnée"""
|
"""Calcule les métriques pour la collection d'assiduité donnée"""
|
||||||
assi: Assiduite
|
assi: Assiduite
|
||||||
for assi in assiduites:
|
for assi in assiduites:
|
||||||
self.count += 1
|
# Ajout vérification workday
|
||||||
|
# (Si préférence mise après avoir déjà noté des assiduités)
|
||||||
|
if assi.date_debut.weekday() in self.non_work_days:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.add_count(assi)
|
||||||
|
|
||||||
delta: timedelta = assi.date_fin - assi.date_debut
|
delta: timedelta = assi.date_fin - assi.date_debut
|
||||||
|
|
||||||
if delta.days > 0:
|
if delta.days > 0:
|
||||||
self.compute_long_assiduite(assi)
|
self.compute_long_assiduite(assi)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
period: tuple[datetime, datetime] = (assi.date_debut, assi.date_fin)
|
period: tuple[datetime, datetime] = (assi.date_debut, assi.date_fin)
|
||||||
deb_date: date = assi.date_debut.date()
|
deb_date: date = assi.date_debut.date()
|
||||||
if self.is_in_morning(period):
|
if self.is_in_morning(period):
|
||||||
self.add_half_day(deb_date)
|
self.add_half_day(deb_date, assi)
|
||||||
if self.is_in_evening(period):
|
if self.is_in_evening(period):
|
||||||
self.add_half_day(deb_date, False)
|
self.add_half_day(deb_date, assi, False)
|
||||||
|
|
||||||
self.add_day(deb_date)
|
self.add_day(deb_date, assi)
|
||||||
|
|
||||||
self.hours += delta.total_seconds() / 3600
|
self.add_hours(delta.total_seconds() / 3600, assi)
|
||||||
|
self.setup_data()
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, int | float]:
|
def setup_data(self):
|
||||||
|
"""Met en forme les données
|
||||||
|
pour les journées et les demi-journées : au lieu d'avoir list[str] on a le nombre (len(list[str]))
|
||||||
|
"""
|
||||||
|
for key in self.data:
|
||||||
|
self.data[key]["journee"] = len(self.data[key]["journee"])
|
||||||
|
self.data[key]["demi"] = len(self.data[key]["demi"])
|
||||||
|
|
||||||
|
def to_dict(self, only_total: bool = True) -> dict[str, int | float]:
|
||||||
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
"""Retourne les métriques sous la forme d'un dictionnaire"""
|
||||||
return {
|
return self.data["total"] if only_total else self.data
|
||||||
"compte": self.count,
|
|
||||||
"journee": len(self.days),
|
|
||||||
"demi": len(self.half_days),
|
|
||||||
"heure": round(self.hours, 2),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_assiduites_stats(
|
def get_assiduites_stats(
|
||||||
@ -211,55 +379,34 @@ def get_assiduites_stats(
|
|||||||
metrics: list[str] = metric.split(",")
|
metrics: list[str] = metric.split(",")
|
||||||
output: dict = {}
|
output: dict = {}
|
||||||
calculator: CountCalculator = CountCalculator()
|
calculator: CountCalculator = CountCalculator()
|
||||||
|
calculator.compute_assiduites(assiduites)
|
||||||
|
|
||||||
if filtered is None or "split" not in filtered:
|
if filtered is None or "split" not in filtered:
|
||||||
calculator.compute_assiduites(assiduites)
|
count: dict = calculator.to_dict(only_total=True)
|
||||||
count: dict = calculator.to_dict()
|
|
||||||
|
|
||||||
for key, val in count.items():
|
for key, val in count.items():
|
||||||
if key in metrics:
|
if key in metrics:
|
||||||
output[key] = val
|
output[key] = val
|
||||||
return output if output else count
|
return output if output else count
|
||||||
|
|
||||||
|
# Récupération des états
|
||||||
etats: list[str] = (
|
etats: list[str] = (
|
||||||
filtered["etat"].split(",")
|
filtered["etat"].split(",")
|
||||||
if "etat" in filtered
|
if "etat" in filtered
|
||||||
else ["absent", "present", "retard"]
|
else ["absent", "present", "retard"]
|
||||||
)
|
)
|
||||||
|
# Préparation du dictionnaire de retour avec les valeurs du calcul
|
||||||
|
count: dict = calculator.to_dict(only_total=False)
|
||||||
for etat in etats:
|
for etat in etats:
|
||||||
output[etat] = _count_assiduites_etat(etat, assiduites, calculator, metrics)
|
if etat != "present":
|
||||||
if "est_just" not in filtered:
|
output[etat] = count[etat]
|
||||||
output[etat]["justifie"] = _count_assiduites_etat(
|
output[etat]["justifie"] = count[etat + "_just"]
|
||||||
etat, assiduites, calculator, metrics, justifie=True
|
output[etat]["non_justifie"] = count[etat + "_non_just"]
|
||||||
)
|
else:
|
||||||
|
output[etat] = count[etat]
|
||||||
|
output["total"] = count["total"]
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def _count_assiduites_etat(
|
|
||||||
etat: str,
|
|
||||||
assiduites: Query,
|
|
||||||
calculator: CountCalculator,
|
|
||||||
metrics: list[str],
|
|
||||||
justifie: bool = False,
|
|
||||||
): # TODO type retour ?
|
|
||||||
# TODO documenter
|
|
||||||
calculator.reset()
|
|
||||||
etat_num: int = scu.EtatAssiduite.get(etat, -1)
|
|
||||||
assiduites_etat: Query = assiduites.filter(Assiduite.etat == etat_num)
|
|
||||||
if justifie:
|
|
||||||
assiduites_etat = assiduites_etat.filter(Assiduite.est_just == True)
|
|
||||||
|
|
||||||
calculator.compute_assiduites(assiduites_etat)
|
|
||||||
count_etat: dict = calculator.to_dict()
|
|
||||||
output_etat: dict = {}
|
|
||||||
for key, val in count_etat.items():
|
|
||||||
if key in metrics:
|
|
||||||
output_etat[key] = val
|
|
||||||
return output_etat if output_etat else count_etat
|
|
||||||
|
|
||||||
|
|
||||||
def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Query:
|
def filter_assiduites_by_etat(assiduites: Assiduite, etat: str) -> Query:
|
||||||
"""
|
"""
|
||||||
Filtrage d'une collection d'assiduites en fonction de leur état
|
Filtrage d'une collection d'assiduites en fonction de leur état
|
||||||
|
@ -655,6 +655,17 @@ class BasePreferences:
|
|||||||
"explanation": "Durée d'un créneau en heure. Utilisé dans les pages de saisie",
|
"explanation": "Durée d'un créneau en heure. Utilisé dans les pages de saisie",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"nb_heures_par_jour",
|
||||||
|
{
|
||||||
|
"initvalue": 8,
|
||||||
|
"size": 10,
|
||||||
|
"title": "Nombre d'heures de travail dans une journée",
|
||||||
|
"type": "int",
|
||||||
|
"explanation": "Est utilisé dans le calcul de la métrique 'heure'. ",
|
||||||
|
"category": "assi",
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"assi_etat_defaut",
|
"assi_etat_defaut",
|
||||||
{
|
{
|
||||||
|
@ -238,6 +238,47 @@ class EtatJustificatif(int, BiDirectionalEnum):
|
|||||||
return etat in cls._value2member_map_
|
return etat in cls._value2member_map_
|
||||||
|
|
||||||
|
|
||||||
|
class NonWorkDays(int, BiDirectionalEnum):
|
||||||
|
"""Correspondance entre les jours et les numéros de jours"""
|
||||||
|
|
||||||
|
LUN = 0
|
||||||
|
MAR = 1
|
||||||
|
MER = 2
|
||||||
|
JEU = 3
|
||||||
|
VEN = 4
|
||||||
|
SAM = 5
|
||||||
|
DIM = 6
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all_non_work_days(
|
||||||
|
cls, formsemestre_id: int = None, dept_id: int = None
|
||||||
|
) -> list["NonWorkDays"]:
|
||||||
|
"""
|
||||||
|
get_all_non_work_days Récupère la liste des non workdays (str) depuis les préférences
|
||||||
|
puis renvoie une liste BiDirectionnalEnum<int> NonWorkDays
|
||||||
|
|
||||||
|
Example:
|
||||||
|
non_work_days : list[NonWorkDays] = NonWorkDays.get_all_non_work_days(dept_id=g.scodoc_dept_id)
|
||||||
|
if datetime.datetime.now().weekday() in non_work_days:
|
||||||
|
print("Aujourd'hui est un jour non travaillé")
|
||||||
|
|
||||||
|
Args:
|
||||||
|
formsemestre_id (int, optional): id d'un formsemestre . Defaults to None.
|
||||||
|
dept_id (int, optional): id d'un départment. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[NonWorkDays]: La liste des NonWorkDays en version BiDirectionnalEnum<int>
|
||||||
|
"""
|
||||||
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
|
return [
|
||||||
|
cls.get(day.strip())
|
||||||
|
for day in sco_preferences.get_preference(
|
||||||
|
"non_travail", formsemestre_id=formsemestre_id, dept_id=dept_id
|
||||||
|
).split(",")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None:
|
def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or None:
|
||||||
"""
|
"""
|
||||||
Vérifie si une date est au format iso
|
Vérifie si une date est au format iso
|
||||||
|
@ -79,10 +79,10 @@ c'est à dire à la montre des étudiants.
|
|||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{{ wtf.form_errors(form, hiddens="only") }}
|
{{ wtf.form_errors(form, hiddens="only") }}
|
||||||
|
|
||||||
{{ wtf.form_field(form.morning_time) }}
|
{{ wtf.form_field(form.assi_morning_time) }}
|
||||||
{{ wtf.form_field(form.lunch_time) }}
|
{{ wtf.form_field(form.assi_lunch_time) }}
|
||||||
{{ wtf.form_field(form.afternoon_time) }}
|
{{ wtf.form_field(form.assi_afternoon_time) }}
|
||||||
{{ wtf.form_field(form.tick_time) }}
|
{{ wtf.form_field(form.assi_tick_time) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -348,8 +348,8 @@ def _get_dates_from_assi_form(
|
|||||||
Ramène ok=True si ok.
|
Ramène ok=True si ok.
|
||||||
Met des messages d'erreur dans le form.
|
Met des messages d'erreur dans le form.
|
||||||
"""
|
"""
|
||||||
debut_jour = "00:00"
|
debut_jour = ScoDocSiteConfig.get("assi_morning_time", "08:00")
|
||||||
fin_jour = "23:59:59"
|
fin_jour = ScoDocSiteConfig.get("assi_afternoon_time", "17:00")
|
||||||
date_fin = None
|
date_fin = None
|
||||||
# On commence par convertir individuellement tous les champs
|
# On commence par convertir individuellement tous les champs
|
||||||
try:
|
try:
|
||||||
|
@ -337,15 +337,19 @@ def config_assiduites():
|
|||||||
("edt_ics_user_path", "Chemin vers les ics des enseignants"),
|
("edt_ics_user_path", "Chemin vers les ics des enseignants"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assi_options = (
|
||||||
|
("assi_morning_time", "Heure du début de la journée"),
|
||||||
|
("assi_lunch_time", "Heure du midi"),
|
||||||
|
("assi_afternoon_time", "Heure du fin de la journée"),
|
||||||
|
("assi_tick_time", "Granularité de la timeline"),
|
||||||
|
)
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
if ScoDocSiteConfig.set("assi_morning_time", form.data["morning_time"]):
|
# --- Options assiduités
|
||||||
flash("Heure du début de la journée enregistrée")
|
for opt_name, message in assi_options:
|
||||||
if ScoDocSiteConfig.set("assi_lunch_time", form.data["lunch_time"]):
|
if ScoDocSiteConfig.set(opt_name, form.data[opt_name]):
|
||||||
flash("Heure de midi enregistrée")
|
flash(f"{message} enregistrée")
|
||||||
if ScoDocSiteConfig.set("assi_afternoon_time", form.data["afternoon_time"]):
|
|
||||||
flash("Heure de fin de la journée enregistrée")
|
|
||||||
if ScoDocSiteConfig.set("assi_tick_time", float(form.data["tick_time"])):
|
|
||||||
flash("Granularité de la timeline enregistrée")
|
|
||||||
# --- Calendriers emploi du temps
|
# --- Calendriers emploi du temps
|
||||||
for opt_name, message in edt_options:
|
for opt_name, message in edt_options:
|
||||||
if ScoDocSiteConfig.set(opt_name, form.data[opt_name]):
|
if ScoDocSiteConfig.set(opt_name, form.data[opt_name]):
|
||||||
@ -354,19 +358,21 @@ def config_assiduites():
|
|||||||
return redirect(url_for("scodoc.configuration"))
|
return redirect(url_for("scodoc.configuration"))
|
||||||
|
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
form.morning_time.data = ScoDocSiteConfig.get(
|
form.assi_morning_time.data = ScoDocSiteConfig.get(
|
||||||
"assi_morning_time", datetime.time(8, 0, 0)
|
"assi_morning_time", datetime.time(8, 0, 0)
|
||||||
)
|
)
|
||||||
form.lunch_time.data = ScoDocSiteConfig.get(
|
form.assi_lunch_time.data = ScoDocSiteConfig.get(
|
||||||
"assi_lunch_time", datetime.time(13, 0, 0)
|
"assi_lunch_time", datetime.time(13, 0, 0)
|
||||||
)
|
)
|
||||||
form.afternoon_time.data = ScoDocSiteConfig.get(
|
form.assi_afternoon_time.data = ScoDocSiteConfig.get(
|
||||||
"assi_afternoon_time", datetime.time(18, 0, 0)
|
"assi_afternoon_time", datetime.time(18, 0, 0)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
form.tick_time.data = float(ScoDocSiteConfig.get("assi_tick_time", 15.0))
|
form.assi_tick_time.data = float(
|
||||||
|
ScoDocSiteConfig.get("assi_tick_time", 15.0)
|
||||||
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
form.tick_time.data = 15.0
|
form.assi_tick_time.data = 15.0
|
||||||
ScoDocSiteConfig.set("assi_tick_time", 15.0)
|
ScoDocSiteConfig.set("assi_tick_time", 15.0)
|
||||||
# --- Emplois du temps
|
# --- Emplois du temps
|
||||||
for opt_name, _ in edt_options:
|
for opt_name, _ in edt_options:
|
||||||
|
@ -81,6 +81,9 @@ def GET(path: str, headers: dict = None, errmsg=None, dept=None):
|
|||||||
timeout=SCO_TEST_API_TIMEOUT,
|
timeout=SCO_TEST_API_TIMEOUT,
|
||||||
)
|
)
|
||||||
if reply.status_code != 200:
|
if reply.status_code != 200:
|
||||||
|
print("url", SCODOC_URL)
|
||||||
|
print("url", url)
|
||||||
|
print("reply", reply.text)
|
||||||
raise APIError(
|
raise APIError(
|
||||||
errmsg or f"""erreur status={reply.status_code} !""", reply.json()
|
errmsg or f"""erreur status={reply.status_code} !""", reply.json()
|
||||||
)
|
)
|
||||||
@ -153,7 +156,7 @@ def check_failure_get(path: str, headers: dict, err: str = None):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
GET(path=path, headers=headers)
|
GET(path=path, headers=headers, dept=DEPT_ACRONYM)
|
||||||
# ^ Renvoi un 404
|
# ^ Renvoi un 404
|
||||||
except APIError as api_err:
|
except APIError as api_err:
|
||||||
if err is not None:
|
if err is not None:
|
||||||
@ -177,7 +180,7 @@ def check_failure_post(path: str, headers: dict, data: dict, err: str = None):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = POST_JSON(path=path, headers=headers, data=data)
|
data = POST_JSON(path=path, headers=headers, data=data, dept=DEPT_ACRONYM)
|
||||||
# ^ Renvoie un 404
|
# ^ Renvoie un 404
|
||||||
except APIError as api_err:
|
except APIError as api_err:
|
||||||
if err is not None:
|
if err is not None:
|
||||||
|
@ -11,6 +11,7 @@ from types import NoneType
|
|||||||
from tests.api.setup_test_api import (
|
from tests.api.setup_test_api import (
|
||||||
GET,
|
GET,
|
||||||
POST_JSON,
|
POST_JSON,
|
||||||
|
DEPT_ACRONYM,
|
||||||
APIError,
|
APIError,
|
||||||
api_headers,
|
api_headers,
|
||||||
api_admin_headers,
|
api_admin_headers,
|
||||||
@ -45,7 +46,7 @@ ASSIDUITES_FIELDS = {
|
|||||||
CREATE_FIELD = {"assiduite_id": int}
|
CREATE_FIELD = {"assiduite_id": int}
|
||||||
BATCH_FIELD = {"errors": list, "success": list}
|
BATCH_FIELD = {"errors": list, "success": list}
|
||||||
|
|
||||||
COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": float}
|
COUNT_FIELDS = {"compte": int, "journee": int, "demi": int, "heure": int | float}
|
||||||
|
|
||||||
TO_REMOVE = []
|
TO_REMOVE = []
|
||||||
|
|
||||||
@ -81,7 +82,7 @@ def test_route_assiduite(api_headers):
|
|||||||
"""test de la route /assiduite/<assiduite_id:int>"""
|
"""test de la route /assiduite/<assiduite_id:int>"""
|
||||||
|
|
||||||
# Bon fonctionnement == id connu
|
# Bon fonctionnement == id connu
|
||||||
data = GET(path="/assiduite/1", headers=api_headers)
|
data = GET(path="/assiduite/1", headers=api_headers, dept=DEPT_ACRONYM)
|
||||||
check_fields(data, fields=ASSIDUITES_FIELDS)
|
check_fields(data, fields=ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
# Mauvais Fonctionnement == id inconnu
|
# Mauvais Fonctionnement == id inconnu
|
||||||
@ -97,13 +98,16 @@ def test_route_count_assiduites(api_headers):
|
|||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
data = GET(path=f"/assiduites/{ETUDID}/count", headers=api_headers)
|
data = GET(
|
||||||
|
path=f"/assiduites/{ETUDID}/count", headers=api_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
check_fields(data, COUNT_FIELDS)
|
check_fields(data, COUNT_FIELDS)
|
||||||
|
|
||||||
metrics = {"heure", "compte"}
|
metrics = {"heure", "compte"}
|
||||||
data = GET(
|
data = GET(
|
||||||
path=f"/assiduites/{ETUDID}/count/query?metric={','.join(metrics)}",
|
path=f"/assiduites/{ETUDID}/count/query?metric={','.join(metrics)}",
|
||||||
headers=api_headers,
|
headers=api_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert set(data.keys()) == metrics
|
assert set(data.keys()) == metrics
|
||||||
@ -118,12 +122,14 @@ def test_route_assiduites(api_headers):
|
|||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
data = GET(path=f"/assiduites/{ETUDID}", headers=api_headers)
|
data = GET(path=f"/assiduites/{ETUDID}", headers=api_headers, dept=DEPT_ACRONYM)
|
||||||
assert isinstance(data, list)
|
assert isinstance(data, list)
|
||||||
for ass in data:
|
for ass in data:
|
||||||
check_fields(ass, ASSIDUITES_FIELDS)
|
check_fields(ass, ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
data = GET(path=f"/assiduites/{ETUDID}/query?", headers=api_headers)
|
data = GET(
|
||||||
|
path=f"/assiduites/{ETUDID}/query?", headers=api_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
assert isinstance(data, list)
|
assert isinstance(data, list)
|
||||||
for ass in data:
|
for ass in data:
|
||||||
check_fields(ass, ASSIDUITES_FIELDS)
|
check_fields(ass, ASSIDUITES_FIELDS)
|
||||||
@ -138,13 +144,19 @@ def test_route_formsemestre_assiduites(api_headers):
|
|||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
data = GET(path=f"/assiduites/formsemestre/{FORMSEMESTREID}", headers=api_headers)
|
data = GET(
|
||||||
|
path=f"/assiduites/formsemestre/{FORMSEMESTREID}",
|
||||||
|
headers=api_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
|
)
|
||||||
assert isinstance(data, list)
|
assert isinstance(data, list)
|
||||||
for ass in data:
|
for ass in data:
|
||||||
check_fields(ass, ASSIDUITES_FIELDS)
|
check_fields(ass, ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
data = GET(
|
data = GET(
|
||||||
path=f"/assiduites/formsemestre/{FORMSEMESTREID}/query?", headers=api_headers
|
path=f"/assiduites/formsemestre/{FORMSEMESTREID}/query?",
|
||||||
|
headers=api_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
)
|
)
|
||||||
assert isinstance(data, list)
|
assert isinstance(data, list)
|
||||||
for ass in data:
|
for ass in data:
|
||||||
@ -169,13 +181,19 @@ def test_route_count_formsemestre_assiduites(api_headers):
|
|||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
data = GET(
|
data = GET(
|
||||||
path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count", headers=api_headers
|
path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count",
|
||||||
|
headers=api_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print("data: ", data)
|
||||||
|
|
||||||
check_fields(data, COUNT_FIELDS)
|
check_fields(data, COUNT_FIELDS)
|
||||||
metrics = {"heure", "compte"}
|
metrics = {"heure", "compte"}
|
||||||
data = GET(
|
data = GET(
|
||||||
path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count/query?metric={','.join(metrics)}",
|
path=f"/assiduites/formsemestre/{FORMSEMESTREID}/count/query?metric={','.join(metrics)}",
|
||||||
headers=api_headers,
|
headers=api_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
)
|
)
|
||||||
assert set(data.keys()) == metrics
|
assert set(data.keys()) == metrics
|
||||||
|
|
||||||
@ -198,9 +216,11 @@ def test_route_create(api_admin_headers):
|
|||||||
# -== Unique ==-
|
# -== Unique ==-
|
||||||
|
|
||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
data = create_data("present", "01")
|
data = create_data("present", "03")
|
||||||
|
|
||||||
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data], api_admin_headers)
|
res = POST_JSON(
|
||||||
|
f"/assiduite/{ETUDID}/create", [data], api_admin_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
assert len(res["success"]) == 1
|
assert len(res["success"]) == 1
|
||||||
|
|
||||||
@ -208,11 +228,14 @@ def test_route_create(api_admin_headers):
|
|||||||
data = GET(
|
data = GET(
|
||||||
path=f'/assiduite/{res["success"][0]["message"]["assiduite_id"]}',
|
path=f'/assiduite/{res["success"][0]["message"]["assiduite_id"]}',
|
||||||
headers=api_admin_headers,
|
headers=api_admin_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
)
|
)
|
||||||
check_fields(data, fields=ASSIDUITES_FIELDS)
|
check_fields(data, fields=ASSIDUITES_FIELDS)
|
||||||
|
|
||||||
data2 = create_data("absent", "02", MODULE, "desc")
|
data2 = create_data("absent", "04", MODULE, "desc")
|
||||||
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data2], api_admin_headers)
|
res = POST_JSON(
|
||||||
|
f"/assiduite/{ETUDID}/create", [data2], api_admin_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
assert len(res["success"]) == 1
|
assert len(res["success"]) == 1
|
||||||
|
|
||||||
@ -221,7 +244,9 @@ def test_route_create(api_admin_headers):
|
|||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
check_failure_post(f"/assiduite/{FAUX}/create", api_admin_headers, [data])
|
check_failure_post(f"/assiduite/{FAUX}/create", api_admin_headers, [data])
|
||||||
|
|
||||||
res = POST_JSON(f"/assiduite/{ETUDID}/create", [data], api_admin_headers)
|
res = POST_JSON(
|
||||||
|
f"/assiduite/{ETUDID}/create", [data], api_admin_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
assert len(res["errors"]) == 1
|
assert len(res["errors"]) == 1
|
||||||
assert (
|
assert (
|
||||||
@ -231,8 +256,9 @@ def test_route_create(api_admin_headers):
|
|||||||
|
|
||||||
res = POST_JSON(
|
res = POST_JSON(
|
||||||
f"/assiduite/{ETUDID}/create",
|
f"/assiduite/{ETUDID}/create",
|
||||||
[create_data("absent", "03", FAUX)],
|
[create_data("absent", "05", FAUX)],
|
||||||
api_admin_headers,
|
api_admin_headers,
|
||||||
|
dept=DEPT_ACRONYM,
|
||||||
)
|
)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
assert len(res["errors"]) == 1
|
assert len(res["errors"]) == 1
|
||||||
@ -245,10 +271,12 @@ def test_route_create(api_admin_headers):
|
|||||||
etats = ["present", "absent", "retard"]
|
etats = ["present", "absent", "retard"]
|
||||||
data = [
|
data = [
|
||||||
create_data(etats[d % 3], 10 + d, MODULE if d % 2 else None)
|
create_data(etats[d % 3], 10 + d, MODULE if d % 2 else None)
|
||||||
for d in range(randint(3, 5))
|
for d in range(randint(2, 4))
|
||||||
]
|
]
|
||||||
|
|
||||||
res = POST_JSON(f"/assiduite/{ETUDID}/create", data, api_admin_headers)
|
res = POST_JSON(
|
||||||
|
f"/assiduite/{ETUDID}/create", data, api_admin_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
for dat in res["success"]:
|
for dat in res["success"]:
|
||||||
check_fields(dat["message"], CREATE_FIELD)
|
check_fields(dat["message"], CREATE_FIELD)
|
||||||
@ -257,15 +285,18 @@ def test_route_create(api_admin_headers):
|
|||||||
# Mauvais Fonctionnement
|
# Mauvais Fonctionnement
|
||||||
|
|
||||||
data2 = [
|
data2 = [
|
||||||
create_data("present", "01"),
|
create_data("present", "03"),
|
||||||
create_data("present", "25", FAUX),
|
create_data("present", "25", FAUX),
|
||||||
create_data("blabla", 26),
|
create_data("blabla", 26),
|
||||||
create_data("absent", 32),
|
create_data("absent", 32),
|
||||||
|
create_data("absent", "01"),
|
||||||
]
|
]
|
||||||
|
|
||||||
res = POST_JSON(f"/assiduite/{ETUDID}/create", data2, api_admin_headers)
|
res = POST_JSON(
|
||||||
|
f"/assiduite/{ETUDID}/create", data2, api_admin_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
assert len(res["errors"]) == 4
|
assert len(res["errors"]) == 5
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
res["errors"][0]["message"]
|
res["errors"][0]["message"]
|
||||||
@ -277,6 +308,7 @@ def test_route_create(api_admin_headers):
|
|||||||
res["errors"][3]["message"]
|
res["errors"][3]["message"]
|
||||||
== "param 'date_debut': format invalide, param 'date_fin': format invalide"
|
== "param 'date_debut': format invalide, param 'date_fin': format invalide"
|
||||||
)
|
)
|
||||||
|
assert res["errors"][4]["message"] == "La date de début n'est pas un jour travaillé"
|
||||||
|
|
||||||
|
|
||||||
def test_route_edit(api_admin_headers):
|
def test_route_edit(api_admin_headers):
|
||||||
@ -285,11 +317,15 @@ def test_route_edit(api_admin_headers):
|
|||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
|
|
||||||
data = {"etat": "retard", "moduleimpl_id": MODULE}
|
data = {"etat": "retard", "moduleimpl_id": MODULE}
|
||||||
res = POST_JSON(f"/assiduite/{TO_REMOVE[0]}/edit", data, api_admin_headers)
|
res = POST_JSON(
|
||||||
|
f"/assiduite/{TO_REMOVE[0]}/edit", data, api_admin_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
assert res == {"OK": True}
|
assert res == {"OK": True}
|
||||||
|
|
||||||
data["moduleimpl_id"] = None
|
data["moduleimpl_id"] = None
|
||||||
res = POST_JSON(f"/assiduite/{TO_REMOVE[1]}/edit", data, api_admin_headers)
|
res = POST_JSON(
|
||||||
|
f"/assiduite/{TO_REMOVE[1]}/edit", data, api_admin_headers, dept=DEPT_ACRONYM
|
||||||
|
)
|
||||||
assert res == {"OK": True}
|
assert res == {"OK": True}
|
||||||
|
|
||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
@ -311,13 +347,13 @@ def test_route_delete(api_admin_headers):
|
|||||||
# Bon fonctionnement
|
# Bon fonctionnement
|
||||||
data = TO_REMOVE[0]
|
data = TO_REMOVE[0]
|
||||||
|
|
||||||
res = POST_JSON("/assiduite/delete", [data], api_admin_headers)
|
res = POST_JSON("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
for dat in res["success"]:
|
for dat in res["success"]:
|
||||||
assert dat["message"] == "OK"
|
assert dat["message"] == "OK"
|
||||||
|
|
||||||
# Mauvais fonctionnement
|
# Mauvais fonctionnement
|
||||||
res = POST_JSON("/assiduite/delete", [data], api_admin_headers)
|
res = POST_JSON("/assiduite/delete", [data], api_admin_headers, dept=DEPT_ACRONYM)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
assert len(res["errors"]) == 1
|
assert len(res["errors"]) == 1
|
||||||
|
|
||||||
@ -327,7 +363,7 @@ def test_route_delete(api_admin_headers):
|
|||||||
|
|
||||||
data = TO_REMOVE[1:]
|
data = TO_REMOVE[1:]
|
||||||
|
|
||||||
res = POST_JSON("/assiduite/delete", data, api_admin_headers)
|
res = POST_JSON("/assiduite/delete", data, api_admin_headers, dept=DEPT_ACRONYM)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
for dat in res["success"]:
|
for dat in res["success"]:
|
||||||
assert dat["message"] == "OK"
|
assert dat["message"] == "OK"
|
||||||
@ -340,7 +376,7 @@ def test_route_delete(api_admin_headers):
|
|||||||
FAUX + 2,
|
FAUX + 2,
|
||||||
]
|
]
|
||||||
|
|
||||||
res = POST_JSON("/assiduite/delete", data2, api_admin_headers)
|
res = POST_JSON("/assiduite/delete", data2, api_admin_headers, dept=DEPT_ACRONYM)
|
||||||
check_fields(res, BATCH_FIELD)
|
check_fields(res, BATCH_FIELD)
|
||||||
assert len(res["errors"]) == 3
|
assert len(res["errors"]) == 3
|
||||||
|
|
||||||
|
@ -50,106 +50,30 @@ def test_bi_directional_enum(test_client):
|
|||||||
def test_general(test_client):
|
def test_general(test_client):
|
||||||
"""tests général du modèle assiduite"""
|
"""tests général du modèle assiduite"""
|
||||||
|
|
||||||
g_fake = sco_fake_gen.ScoFake(verbose=False)
|
data: dict = _setup_fake_db(
|
||||||
|
dates_formsemestre=[
|
||||||
# Création d'une formation (1)
|
("01/09/2022", "31/12/2022"),
|
||||||
|
("01/01/2023", "31/07/2023"),
|
||||||
formation_id = g_fake.create_formation()
|
("01/01/2024", "31/07/2024"),
|
||||||
ue_id = g_fake.create_ue(
|
],
|
||||||
formation_id=formation_id, acronyme="T1", titre="UE TEST 1"
|
nb_modules=2,
|
||||||
|
nb_etuds=3,
|
||||||
)
|
)
|
||||||
matiere_id = g_fake.create_matiere(ue_id=ue_id, titre="test matière")
|
etuds, moduleimpls, etud_faux, formsemestres = (
|
||||||
module_id_1 = g_fake.create_module(
|
data["etuds"],
|
||||||
matiere_id=matiere_id, code="Mo1", coefficient=1.0, titre="test module"
|
data["moduleimpls"],
|
||||||
|
data["etud_faux"],
|
||||||
|
data["formsemestres"],
|
||||||
)
|
)
|
||||||
module_id_2 = g_fake.create_module(
|
|
||||||
matiere_id=matiere_id, code="Mo2", coefficient=1.0, titre="test module2"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Création semestre (2)
|
|
||||||
|
|
||||||
formsemestre_id_1 = g_fake.create_formsemestre(
|
|
||||||
formation_id=formation_id,
|
|
||||||
semestre_id=1,
|
|
||||||
date_debut="01/09/2022",
|
|
||||||
date_fin="31/12/2022",
|
|
||||||
)
|
|
||||||
formsemestre_id_2 = g_fake.create_formsemestre(
|
|
||||||
formation_id=formation_id,
|
|
||||||
semestre_id=2,
|
|
||||||
date_debut="01/01/2023",
|
|
||||||
date_fin="31/07/2023",
|
|
||||||
)
|
|
||||||
formsemestre_id_3 = g_fake.create_formsemestre(
|
|
||||||
formation_id=formation_id,
|
|
||||||
semestre_id=3,
|
|
||||||
date_debut="01/01/2024",
|
|
||||||
date_fin="31/07/2024",
|
|
||||||
)
|
|
||||||
formsemestre_1 = FormSemestre.get_formsemestre(formsemestre_id_1)
|
|
||||||
formsemestre_2 = FormSemestre.get_formsemestre(formsemestre_id_2)
|
|
||||||
formsemestre_3 = FormSemestre.get_formsemestre(formsemestre_id_3)
|
|
||||||
|
|
||||||
# Création des modulesimpls (4, 2 par semestre)
|
|
||||||
|
|
||||||
moduleimpl_1_1 = g_fake.create_moduleimpl(
|
|
||||||
module_id=module_id_1,
|
|
||||||
formsemestre_id=formsemestre_id_1,
|
|
||||||
)
|
|
||||||
moduleimpl_1_2 = g_fake.create_moduleimpl(
|
|
||||||
module_id=module_id_2,
|
|
||||||
formsemestre_id=formsemestre_id_1,
|
|
||||||
)
|
|
||||||
|
|
||||||
moduleimpl_2_1 = g_fake.create_moduleimpl(
|
|
||||||
module_id=module_id_1,
|
|
||||||
formsemestre_id=formsemestre_id_2,
|
|
||||||
)
|
|
||||||
moduleimpl_2_2 = g_fake.create_moduleimpl(
|
|
||||||
module_id=module_id_2,
|
|
||||||
formsemestre_id=formsemestre_id_2,
|
|
||||||
)
|
|
||||||
moduleimpls = [
|
|
||||||
moduleimpl_1_1,
|
|
||||||
moduleimpl_1_2,
|
|
||||||
moduleimpl_2_1,
|
|
||||||
moduleimpl_2_2,
|
|
||||||
]
|
|
||||||
moduleimpls = [
|
|
||||||
ModuleImpl.query.filter_by(id=mi_id).first() for mi_id in moduleimpls
|
|
||||||
]
|
|
||||||
|
|
||||||
# Création de 3 étudiants
|
|
||||||
etud_0 = g_fake.create_etud(prenom="etud0")
|
|
||||||
etud_1 = g_fake.create_etud(prenom="etud1")
|
|
||||||
etud_2 = g_fake.create_etud(prenom="etud2")
|
|
||||||
etuds_dict = [etud_0, etud_1, etud_2]
|
|
||||||
# etuds_dict = [g_fake.create_etud(prenom=f"etud{i}") for i in range(3)]
|
|
||||||
|
|
||||||
etuds = []
|
|
||||||
for etud in etuds_dict:
|
|
||||||
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_1, etud=etud)
|
|
||||||
g_fake.inscrit_etudiant(formsemestre_id=formsemestre_id_2, etud=etud)
|
|
||||||
|
|
||||||
etuds.append(Identite.query.filter_by(id=etud["etudid"]).first())
|
|
||||||
|
|
||||||
assert None not in etuds, "Problème avec la conversion en Identite"
|
|
||||||
|
|
||||||
# Etudiant faux
|
|
||||||
|
|
||||||
etud_faux_dict = g_fake.create_etud(prenom="etudfaux")
|
|
||||||
etud_faux = Identite.query.filter_by(id=etud_faux_dict["etudid"]).first()
|
|
||||||
|
|
||||||
verif_migration_abs_assiduites()
|
verif_migration_abs_assiduites()
|
||||||
|
|
||||||
ajouter_assiduites(etuds, moduleimpls, etud_faux)
|
ajouter_assiduites(etuds, moduleimpls, etud_faux)
|
||||||
justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
|
justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
|
||||||
verifier_comptage_et_filtrage_assiduites(
|
verifier_comptage_et_filtrage_assiduites(etuds, moduleimpls[:4], formsemestres)
|
||||||
etuds, moduleimpls, (formsemestre_1, formsemestre_2, formsemestre_3)
|
|
||||||
)
|
|
||||||
verifier_filtrage_justificatifs(etuds[0], justificatifs)
|
verifier_filtrage_justificatifs(etuds[0], justificatifs)
|
||||||
|
|
||||||
essais_cache(etuds[0].etudid, (formsemestre_1, formsemestre_2), moduleimpls)
|
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])
|
||||||
@ -531,8 +455,8 @@ def ajouter_justificatifs(etud):
|
|||||||
obj_justificatifs = [
|
obj_justificatifs = [
|
||||||
{
|
{
|
||||||
"etat": scu.EtatJustificatif.ATTENTE,
|
"etat": scu.EtatJustificatif.ATTENTE,
|
||||||
"deb": "2022-09-03T08:00+01:00",
|
"deb": "2022-09-05T08:00+01:00",
|
||||||
"fin": "2022-09-03T09:59:59+01:00",
|
"fin": "2022-09-05T09:59:59+01:00",
|
||||||
"raison": None,
|
"raison": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -543,14 +467,14 @@ def ajouter_justificatifs(etud):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"etat": scu.EtatJustificatif.VALIDE,
|
"etat": scu.EtatJustificatif.VALIDE,
|
||||||
"deb": "2022-09-03T10:00:00+01:00",
|
"deb": "2022-09-05T10:00:00+01:00",
|
||||||
"fin": "2022-09-03T12:00+01:00",
|
"fin": "2022-09-05T12:00+01:00",
|
||||||
"raison": None,
|
"raison": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"etat": scu.EtatJustificatif.NON_VALIDE,
|
"etat": scu.EtatJustificatif.NON_VALIDE,
|
||||||
"deb": "2022-09-03T14:00:00+01:00",
|
"deb": "2022-09-05T14:00:00+01:00",
|
||||||
"fin": "2022-09-03T15:00+01:00",
|
"fin": "2022-09-05T15:00+01:00",
|
||||||
"raison": "Description",
|
"raison": "Description",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -581,14 +505,6 @@ def ajouter_justificatifs(etud):
|
|||||||
justi for justi in justificatifs if not isinstance(justi, Justificatif)
|
justi for justi in justificatifs if not isinstance(justi, Justificatif)
|
||||||
] == [], "La création des justificatifs de base n'est pas OK"
|
] == [], "La création des justificatifs de base n'est pas OK"
|
||||||
|
|
||||||
# Vérification de la gestion des erreurs
|
|
||||||
|
|
||||||
test_assiduite = {
|
|
||||||
"etat": scu.EtatJustificatif.ATTENTE,
|
|
||||||
"deb": "2023-01-03T11:00:01+01:00",
|
|
||||||
"fin": "2023-01-03T12:00+01:00",
|
|
||||||
"raison": "Description",
|
|
||||||
}
|
|
||||||
return justificatifs
|
return justificatifs
|
||||||
|
|
||||||
|
|
||||||
@ -646,19 +562,19 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific
|
|||||||
== 5
|
== 5
|
||||||
), "Filtrage 'Toute Date' mauvais 2"
|
), "Filtrage 'Toute Date' mauvais 2"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T08:00+01:00")
|
date = scu.localize_datetime("2022-09-05T08:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
||||||
== 5
|
== 5
|
||||||
), "Filtrage 'date début' mauvais 3"
|
), "Filtrage 'date début' mauvais 3"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T08:00:01+01:00")
|
date = scu.localize_datetime("2022-09-05T08:00:01+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
||||||
== 5
|
== 5
|
||||||
), "Filtrage 'date début' mauvais 4"
|
), "Filtrage 'date début' mauvais 4"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T10:00+01:00")
|
date = scu.localize_datetime("2022-09-05T10:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
||||||
== 4
|
== 4
|
||||||
@ -668,25 +584,25 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific
|
|||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||||
== 0
|
== 0
|
||||||
), "Filtrage 'Toute Date' mauvais 6"
|
), "Filtrage 'date fin' mauvais 6"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T08:00+01:00")
|
date = scu.localize_datetime("2022-09-05T08:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||||
== 1
|
== 1
|
||||||
), "Filtrage 'date début' mauvais 7"
|
), "Filtrage 'date fin' mauvais 7"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T10:00:01+01:00")
|
date = scu.localize_datetime("2022-09-05T10:00:01+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||||
== 2
|
== 2
|
||||||
), "Filtrage 'date début' mauvais 8"
|
), "Filtrage 'date fin' mauvais 8"
|
||||||
|
|
||||||
date = scu.localize_datetime("2023-01-03T12:00+01:00")
|
date = scu.localize_datetime("2023-01-03T12:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||||
== 5
|
== 5
|
||||||
), "Filtrage 'date début' mauvais 9"
|
), "Filtrage 'date fin' mauvais 9"
|
||||||
|
|
||||||
# Justifications des assiduites
|
# Justifications des assiduites
|
||||||
|
|
||||||
@ -785,8 +701,8 @@ def ajouter_assiduites(
|
|||||||
obj_assiduites = [
|
obj_assiduites = [
|
||||||
{
|
{
|
||||||
"etat": scu.EtatAssiduite.PRESENT,
|
"etat": scu.EtatAssiduite.PRESENT,
|
||||||
"deb": "2022-09-03T08:00+01:00",
|
"deb": "2022-09-05T08:00+01:00",
|
||||||
"fin": "2022-09-03T10:00+01:00",
|
"fin": "2022-09-05T10:00+01:00",
|
||||||
"moduleimpl": None,
|
"moduleimpl": None,
|
||||||
"desc": None,
|
"desc": None,
|
||||||
},
|
},
|
||||||
@ -799,15 +715,15 @@ def ajouter_assiduites(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"etat": scu.EtatAssiduite.ABSENT,
|
"etat": scu.EtatAssiduite.ABSENT,
|
||||||
"deb": "2022-09-03T10:00:01+01:00",
|
"deb": "2022-09-05T10:00:01+01:00",
|
||||||
"fin": "2022-09-03T11:00+01:00",
|
"fin": "2022-09-05T11:00+01:00",
|
||||||
"moduleimpl": moduleimpls[0],
|
"moduleimpl": moduleimpls[0],
|
||||||
"desc": None,
|
"desc": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"etat": scu.EtatAssiduite.ABSENT,
|
"etat": scu.EtatAssiduite.ABSENT,
|
||||||
"deb": "2022-09-03T14:00:00+01:00",
|
"deb": "2022-09-05T14:00:00+01:00",
|
||||||
"fin": "2022-09-03T15:00+01:00",
|
"fin": "2022-09-05T15:00+01:00",
|
||||||
"moduleimpl": moduleimpls[1],
|
"moduleimpl": moduleimpls[1],
|
||||||
"desc": "Description",
|
"desc": "Description",
|
||||||
},
|
},
|
||||||
@ -877,6 +793,44 @@ def ajouter_assiduites(
|
|||||||
excp.args[0]
|
excp.args[0]
|
||||||
== "Duplication: la période rentre en conflit avec une plage enregistrée"
|
== "Duplication: la période rentre en conflit avec une plage enregistrée"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
test_assiduite2 = {
|
||||||
|
"etat": scu.EtatAssiduite.RETARD,
|
||||||
|
"deb": "2022-09-03T11:00:01+01:00",
|
||||||
|
"fin": "2022-09-03T12:00+01:00",
|
||||||
|
"moduleimpl": moduleimpls[3],
|
||||||
|
"desc": "Description",
|
||||||
|
}
|
||||||
|
Assiduite.create_assiduite(
|
||||||
|
etuds[0],
|
||||||
|
scu.is_iso_formated(test_assiduite2["deb"], True),
|
||||||
|
scu.is_iso_formated(test_assiduite2["fin"], True),
|
||||||
|
test_assiduite2["etat"],
|
||||||
|
test_assiduite2["moduleimpl"],
|
||||||
|
test_assiduite2["desc"],
|
||||||
|
)
|
||||||
|
except ScoValueError as excp:
|
||||||
|
assert excp.args[0] == "La date de début n'est pas un jour travaillé"
|
||||||
|
try:
|
||||||
|
test_assiduite2 = {
|
||||||
|
"etat": scu.EtatAssiduite.RETARD,
|
||||||
|
"deb": "2022-09-02T11:00:01+01:00",
|
||||||
|
"fin": "2022-09-03T12:00+01:00",
|
||||||
|
"moduleimpl": moduleimpls[3],
|
||||||
|
"desc": "Description",
|
||||||
|
}
|
||||||
|
Assiduite.create_assiduite(
|
||||||
|
etuds[0],
|
||||||
|
scu.is_iso_formated(test_assiduite2["deb"], True),
|
||||||
|
scu.is_iso_formated(test_assiduite2["fin"], True),
|
||||||
|
test_assiduite2["etat"],
|
||||||
|
test_assiduite2["moduleimpl"],
|
||||||
|
test_assiduite2["desc"],
|
||||||
|
)
|
||||||
|
except ScoValueError as excp:
|
||||||
|
assert excp.args[0] == "La date de fin n'est pas un jour travaillé"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Assiduite.create_assiduite(
|
Assiduite.create_assiduite(
|
||||||
etud_faux,
|
etud_faux,
|
||||||
@ -904,20 +858,6 @@ def verifier_comptage_et_filtrage_assiduites(
|
|||||||
|
|
||||||
mod11, mod12, mod21, mod22 = moduleimpls
|
mod11, mod12, mod21, mod22 = moduleimpls
|
||||||
|
|
||||||
# Vérification du comptage classique
|
|
||||||
comptage = scass.get_assiduites_stats(etu1.assiduites)
|
|
||||||
|
|
||||||
assert comptage["compte"] == 6 + 1, "la métrique 'Comptage' n'est pas bien calculée"
|
|
||||||
assert (
|
|
||||||
comptage["journee"] == 3 + 22
|
|
||||||
), "la métrique 'Journée' n'est pas bien calculée"
|
|
||||||
assert (
|
|
||||||
comptage["demi"] == 4 + 43
|
|
||||||
), "la métrique 'Demi-Journée' n'est pas bien calculée"
|
|
||||||
assert comptage["heure"] == float(
|
|
||||||
8 + 169
|
|
||||||
), "la métrique 'Heure' n'est pas bien calculée"
|
|
||||||
|
|
||||||
# Vérification du filtrage classique
|
# Vérification du filtrage classique
|
||||||
|
|
||||||
# Etat
|
# Etat
|
||||||
@ -993,12 +933,12 @@ def verifier_comptage_et_filtrage_assiduites(
|
|||||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
|
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
|
||||||
), "Filtrage 'Date début' mauvais 2"
|
), "Filtrage 'Date début' mauvais 2"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T10:00+01:00")
|
date = scu.localize_datetime("2022-09-05T10:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
|
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
|
||||||
), "Filtrage 'Date début' mauvais 3"
|
), "Filtrage 'Date début' mauvais 3"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T16:00+01:00")
|
date = scu.localize_datetime("2022-09-05T16:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 4
|
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 4
|
||||||
), "Filtrage 'Date début' mauvais 4"
|
), "Filtrage 'Date début' mauvais 4"
|
||||||
@ -1010,17 +950,17 @@ def verifier_comptage_et_filtrage_assiduites(
|
|||||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 0
|
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 0
|
||||||
), "Filtrage 'Date fin' mauvais 1"
|
), "Filtrage 'Date fin' mauvais 1"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T10:00+01:00")
|
date = scu.localize_datetime("2022-09-05T10:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 1
|
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 1
|
||||||
), "Filtrage 'Date fin' mauvais 2"
|
), "Filtrage 'Date fin' mauvais 2"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T10:00:01+01:00")
|
date = scu.localize_datetime("2022-09-05T10:00:01+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 2
|
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 2
|
||||||
), "Filtrage 'Date fin' mauvais 3"
|
), "Filtrage 'Date fin' mauvais 3"
|
||||||
|
|
||||||
date = scu.localize_datetime("2022-09-03T16:00+01:00")
|
date = scu.localize_datetime("2022-09-05T16:00+01:00")
|
||||||
assert (
|
assert (
|
||||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 3
|
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 3
|
||||||
), "Filtrage 'Date fin' mauvais 4"
|
), "Filtrage 'Date fin' mauvais 4"
|
||||||
@ -1112,3 +1052,310 @@ def _create_abs(
|
|||||||
|
|
||||||
db.session.add_all(abs_list)
|
db.session.add_all(abs_list)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_fake_db(
|
||||||
|
dates_formsemestre: list[tuple[str, str]],
|
||||||
|
nb_modules: int = 0,
|
||||||
|
nb_etuds: int = 1,
|
||||||
|
) -> dict:
|
||||||
|
g_fake = sco_fake_gen.ScoFake(verbose=False)
|
||||||
|
|
||||||
|
# Création d'une formation
|
||||||
|
formation_id = g_fake.create_formation()
|
||||||
|
ue_id = g_fake.create_ue(
|
||||||
|
formation_id=formation_id, acronyme="T1", titre="UE TEST 1"
|
||||||
|
)
|
||||||
|
matiere_id = g_fake.create_matiere(ue_id=ue_id, titre="test matière")
|
||||||
|
|
||||||
|
module_ids: list[int] = [
|
||||||
|
g_fake.create_module(
|
||||||
|
matiere_id=matiere_id,
|
||||||
|
code=f"Mo{i}",
|
||||||
|
coefficient=1.0,
|
||||||
|
titre=f"test module{i}",
|
||||||
|
)
|
||||||
|
for i in range(nb_modules)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Création semestre
|
||||||
|
|
||||||
|
formsemestre_ids: list[int] = [
|
||||||
|
g_fake.create_formsemestre(
|
||||||
|
formation_id=formation_id,
|
||||||
|
semestre_id=1,
|
||||||
|
date_debut=deb,
|
||||||
|
date_fin=fin,
|
||||||
|
)
|
||||||
|
for deb, fin in dates_formsemestre
|
||||||
|
]
|
||||||
|
|
||||||
|
formsemestres: list[FormSemestre] = list(
|
||||||
|
map(FormSemestre.get_formsemestre, formsemestre_ids)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Création des modulesimpls (2 par semestre)
|
||||||
|
|
||||||
|
moduleimpls: list[int] = []
|
||||||
|
|
||||||
|
for i in range(len(dates_formsemestre)):
|
||||||
|
for j in range(nb_modules):
|
||||||
|
mod, form = module_ids[j], formsemestres[i]
|
||||||
|
|
||||||
|
moduleimpl_id: int = g_fake.create_moduleimpl(
|
||||||
|
module_id=mod,
|
||||||
|
formsemestre_id=form.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
moduleimpls.append(ModuleImpl.query.filter_by(id=moduleimpl_id).first())
|
||||||
|
|
||||||
|
# Création de 3 étudiants
|
||||||
|
etud_0 = g_fake.create_etud(prenom="etud0")
|
||||||
|
etud_1 = g_fake.create_etud(prenom="etud1")
|
||||||
|
etud_2 = g_fake.create_etud(prenom="etud2")
|
||||||
|
etuds_dict = [etud_0, etud_1, etud_2]
|
||||||
|
|
||||||
|
etud_dicts: list[dict] = [
|
||||||
|
g_fake.create_etud(prenom=f"etud{i}") for i in range(nb_etuds)
|
||||||
|
]
|
||||||
|
|
||||||
|
etuds = []
|
||||||
|
for etud in etuds_dict:
|
||||||
|
for form_id in formsemestre_ids:
|
||||||
|
g_fake.inscrit_etudiant(formsemestre_id=form_id, etud=etud)
|
||||||
|
etuds.append(Identite.query.filter_by(id=etud["etudid"]).first())
|
||||||
|
# Etudiant faux
|
||||||
|
etud_faux_dict = g_fake.create_etud(prenom="etudfaux")
|
||||||
|
etud_faux = Identite.query.filter_by(id=etud_faux_dict["etudid"]).first()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"moduleimpls": moduleimpls,
|
||||||
|
"formsemestres": formsemestres,
|
||||||
|
"etuds": etuds,
|
||||||
|
"etud_faux": etud_faux,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_calcul_assiduites(test_client):
|
||||||
|
"""Vérification du bon calcul des assiduités"""
|
||||||
|
|
||||||
|
data: dict = _setup_fake_db([("01/12/2023", "31/12/2023")])
|
||||||
|
|
||||||
|
formsemestre: FormSemestre = data["formsemestres"][0]
|
||||||
|
etud: Identite = data["etuds"][0]
|
||||||
|
|
||||||
|
"""
|
||||||
|
Exemple tuple:
|
||||||
|
(
|
||||||
|
"12-04T08:00", # Date de début
|
||||||
|
"12-04T09:00", # Date de fin
|
||||||
|
scu.EtatAssiduite.ABSENT, # Etat
|
||||||
|
False # est_just
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
assiduites: list[tuple] = [
|
||||||
|
# Journée du 04/12
|
||||||
|
(
|
||||||
|
"12-04T08:00",
|
||||||
|
"12-04T10:00",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-04T10:15",
|
||||||
|
"12-04T12:15",
|
||||||
|
scu.EtatAssiduite.RETARD,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-04T13:15",
|
||||||
|
"12-04T15:15",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-04T15:15",
|
||||||
|
"12-04T17:00",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
# 05/12
|
||||||
|
(
|
||||||
|
"12-05T08:00",
|
||||||
|
"12-05T09:00",
|
||||||
|
scu.EtatAssiduite.RETARD,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-05T09:00",
|
||||||
|
"12-05T10:00",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-05T10:15",
|
||||||
|
"12-05T12:15",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-05T13:15",
|
||||||
|
"12-05T14:30",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-05T14:30",
|
||||||
|
"12-05T16:30",
|
||||||
|
scu.EtatAssiduite.RETARD,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-05T16:30",
|
||||||
|
"12-05T17:00",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
# 06/12
|
||||||
|
(
|
||||||
|
"12-06T08:00",
|
||||||
|
"12-06T10:00",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-06T10:15",
|
||||||
|
"12-06T12:15",
|
||||||
|
scu.EtatAssiduite.RETARD,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-06T13:15",
|
||||||
|
"12-06T13:45",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-06T13:45",
|
||||||
|
"12-06T15:00",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-06T15:00",
|
||||||
|
"12-06T17:00",
|
||||||
|
scu.EtatAssiduite.RETARD,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
# 07/12
|
||||||
|
(
|
||||||
|
"12-07T08:00",
|
||||||
|
"12-07T08:30",
|
||||||
|
scu.EtatAssiduite.RETARD,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-07T08:30",
|
||||||
|
"12-07T10:00",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-07T10:15",
|
||||||
|
"12-07T12:15",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
# 08/12
|
||||||
|
(
|
||||||
|
"12-08T08:00",
|
||||||
|
"12-08T10:00",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-08T10:15",
|
||||||
|
"12-08T12:15",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-08T13:15",
|
||||||
|
"12-08T14:15",
|
||||||
|
scu.EtatAssiduite.RETARD,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-08T14:15",
|
||||||
|
"12-08T15:15",
|
||||||
|
scu.EtatAssiduite.PRESENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"12-08T15:15",
|
||||||
|
"12-08T17:00",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
# 11/12 -> 15/12
|
||||||
|
(
|
||||||
|
"12-11T08:00",
|
||||||
|
"12-15T17:00",
|
||||||
|
scu.EtatAssiduite.ABSENT,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for ass in assiduites:
|
||||||
|
ass_obj = Assiduite.create_assiduite(
|
||||||
|
etud=etud,
|
||||||
|
date_debut=scu.is_iso_formated("2023-" + ass[0], True),
|
||||||
|
date_fin=scu.is_iso_formated("2023-" + ass[1], True),
|
||||||
|
etat=ass[2],
|
||||||
|
est_just=ass[3],
|
||||||
|
)
|
||||||
|
db.session.add(ass_obj)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
calculator = scass.CountCalculator(
|
||||||
|
morning="08:00", noon="12:15", evening="17:00", nb_heures_par_jour=8
|
||||||
|
)
|
||||||
|
|
||||||
|
calculator.compute_assiduites(etud.assiduites)
|
||||||
|
|
||||||
|
result: dict = calculator.to_dict(only_total=False)
|
||||||
|
|
||||||
|
# Résultat attendu :
|
||||||
|
# (les additions dans les absences corresponde à (compte_assiduite + compte_assiduite_longue))
|
||||||
|
resultat_attendu: dict = {
|
||||||
|
"present": {"journee": 5, "demi": 8, "heure": 13.25, "compte": 9},
|
||||||
|
"absent": {
|
||||||
|
"journee": 5 + 5,
|
||||||
|
"demi": 7 + 10,
|
||||||
|
"heure": 11.25 + 42,
|
||||||
|
"compte": 7 + 1,
|
||||||
|
},
|
||||||
|
"absent_just": {"journee": 3, "demi": 3, "heure": 4.25, "compte": 3},
|
||||||
|
"absent_non_just": {
|
||||||
|
"journee": 3 + 5,
|
||||||
|
"demi": 4 + 10,
|
||||||
|
"heure": 7 + 42,
|
||||||
|
"compte": 4 + 1,
|
||||||
|
},
|
||||||
|
"retard": {
|
||||||
|
"journee": 5,
|
||||||
|
"demi": 7,
|
||||||
|
"heure": 10.5,
|
||||||
|
"compte": 7,
|
||||||
|
},
|
||||||
|
"retard_just": {"journee": 3, "demi": 3, "heure": 2.5, "compte": 3},
|
||||||
|
"retard_non_just": {"journee": 3, "demi": 4, "heure": 8.0, "compte": 4},
|
||||||
|
"total": {"journee": 10, "demi": 19, "heure": 77.0, "compte": 24},
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in resultat_attendu:
|
||||||
|
for key2 in resultat_attendu[key]:
|
||||||
|
assert (
|
||||||
|
result[key][key2] == resultat_attendu[key][key2]
|
||||||
|
), f"Le calcul [{key}][{key2}] est faux (attendu > {resultat_attendu[key][key2]} ≠ {result[key][key2]} < obtenu)"
|
||||||
|
@ -388,7 +388,9 @@ def ajouter_assiduites_justificatifs(formsemestre: FormSemestre):
|
|||||||
MODS.append(None)
|
MODS.append(None)
|
||||||
|
|
||||||
for etud in formsemestre.etuds:
|
for etud in formsemestre.etuds:
|
||||||
base_date = datetime.datetime(2022, 9, random.randint(1, 30), 8, 0, 0)
|
base_date = datetime.datetime(
|
||||||
|
2022, 9, [5, 12, 19, 26][random.randint(0, 3)], 8, 0, 0
|
||||||
|
)
|
||||||
base_date = localize_datetime(base_date)
|
base_date = localize_datetime(base_date)
|
||||||
|
|
||||||
for i in range(random.randint(1, 5)):
|
for i in range(random.randint(1, 5)):
|
||||||
|
Loading…
Reference in New Issue
Block a user