script migration abs -> assiduites (WIP)

This commit is contained in:
iziram 2023-02-07 18:49:51 +01:00
parent 095eb6ce20
commit c11599b64f
7 changed files with 230 additions and 5 deletions

View File

@ -15,8 +15,10 @@ class Absence(db.Model):
db.Integer, db.ForeignKey("identite.id", ondelete="CASCADE"), index=True db.Integer, db.ForeignKey("identite.id", ondelete="CASCADE"), index=True
) )
jour = db.Column(db.Date) jour = db.Column(db.Date)
# absent / justifié / absent+ justifié
estabs = db.Column(db.Boolean()) estabs = db.Column(db.Boolean())
estjust = db.Column(db.Boolean()) estjust = db.Column(db.Boolean())
matin = db.Column(db.Boolean()) matin = db.Column(db.Boolean())
# motif de l'absence: # motif de l'absence:
description = db.Column(db.Text()) description = db.Column(db.Text())

View File

@ -77,6 +77,7 @@ class Assiduite(db.Model):
etat: EtatAssiduite, etat: EtatAssiduite,
moduleimpl: ModuleImpl = None, moduleimpl: ModuleImpl = None,
description: str = None, description: str = None,
entry_date: datetime = None,
) -> object or int: ) -> object or int:
"""Créer une nouvelle assiduité pour l'étudiant""" """Créer une nouvelle assiduité pour l'étudiant"""
# Vérification de non duplication des périodes # Vérification de non duplication des périodes
@ -97,6 +98,7 @@ class Assiduite(db.Model):
etudiant=etud, etudiant=etud,
moduleimpl_id=moduleimpl.id, moduleimpl_id=moduleimpl.id,
desc=description, desc=description,
entry_date=entry_date,
) )
else: else:
raise ScoValueError("L'étudiant n'est pas inscrit au moduleimpl") raise ScoValueError("L'étudiant n'est pas inscrit au moduleimpl")
@ -107,6 +109,7 @@ class Assiduite(db.Model):
etat=etat, etat=etat,
etudiant=etud, etudiant=etud,
desc=description, desc=description,
entry_date=entry_date,
) )
return nouv_assiduite return nouv_assiduite
@ -178,6 +181,7 @@ class Justificatif(db.Model):
date_fin: datetime, date_fin: datetime,
etat: EtatJustificatif, etat: EtatJustificatif,
raison: str = None, raison: str = None,
entry_date: datetime = None,
) -> object or int: ) -> object or int:
"""Créer un nouveau justificatif pour l'étudiant""" """Créer un nouveau justificatif pour l'étudiant"""
# Vérification de non duplication des périodes # Vérification de non duplication des périodes
@ -193,6 +197,7 @@ class Justificatif(db.Model):
etat=etat, etat=etat,
etudiant=etud, etudiant=etud,
raison=raison, raison=raison,
entry_date=entry_date,
) )
return nouv_justificatif return nouv_justificatif
@ -214,8 +219,7 @@ def is_period_conflicting(
uni uni
for uni in collection for uni in collection
if is_period_overlapping( if is_period_overlapping(
(date_debut, date_fin), (date_debut, date_fin), (uni.date_debut, uni.date_fin), bornes=False
(uni.date_debut, uni.date_fin),
) )
] ]

View File

@ -42,6 +42,8 @@ from app.scodoc import sco_cache
from app.scodoc import sco_etud from app.scodoc import sco_etud
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.models import Assiduite
import app.scodoc.sco_assiduites as scass
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
# --- Misc tools.... ------------------ # --- Misc tools.... ------------------
@ -1026,7 +1028,7 @@ def get_abs_count(etudid, sem):
""" """
return get_abs_count_in_interval(etudid, sem["date_debut_iso"], sem["date_fin_iso"]) return get_abs_count_in_interval(etudid, sem["date_debut_iso"], sem["date_fin_iso"])
# TODO: relier avec module assiduites
def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso): def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
"""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, nb abs justifiées)
@ -1052,6 +1054,36 @@ def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
return r return r
def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
tuple (nb abs, nb abs justifiées)
Utilise un cache.
"""
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso
r = sco_cache.AbsSemEtudCache.get(key)
if not r:
date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
date_fin: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
assiduites = scass.filter_assiduites_by_date(assiduites, date_debut, sup=True)
assiduites = scass.filter_assiduites_by_date(assiduites, date_fin, sup=False)
nb_abs = scass.get_count(assiduites)["demi"]
nb_abs_just = count_abs_just(
etudid=etudid,
debut=date_debut_iso,
fin=date_fin_iso,
)
r = (nb_abs, nb_abs_just)
ans = sco_cache.AbsSemEtudCache.set(key, r)
if not ans:
log("warning: get_abs_count failed to cache")
return r
def invalidate_abs_count(etudid, sem): def invalidate_abs_count(etudid, sem):
"""Invalidate (clear) cached counts""" """Invalidate (clear) cached counts"""
date_debut = sem["date_debut_iso"] date_debut = sem["date_debut_iso"]

View File

@ -174,7 +174,7 @@ def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
def is_period_overlapping( def is_period_overlapping(
periode: tuple[datetime.datetime, datetime.datetime], periode: tuple[datetime.datetime, datetime.datetime],
interval: tuple[datetime.datetime, datetime.datetime], interval: tuple[datetime.datetime, datetime.datetime],
strict: bool = True, bornes: bool = True,
) -> bool: ) -> bool:
""" """
Vérifie si la période et l'interval s'intersectent Vérifie si la période et l'interval s'intersectent
@ -184,7 +184,7 @@ def is_period_overlapping(
p_deb, p_fin = periode p_deb, p_fin = periode
i_deb, i_fin = interval i_deb, i_fin = interval
if not strict: if bornes:
return p_deb <= i_fin and p_fin >= i_deb return p_deb <= i_fin and p_fin >= i_deb
return p_deb < i_fin and p_fin > i_deb return p_deb < i_fin and p_fin > i_deb

View File

@ -470,6 +470,19 @@ def migrate_scodoc7_dept_archives(dept: str): # migrate-scodoc7-dept-archives
tools.migrate_scodoc7_dept_archives(dept) tools.migrate_scodoc7_dept_archives(dept)
@app.cli.command()
@click.argument("dept", default="")
@click.argument("morning", default="")
@click.argument("noon", default="")
@click.argument("evening", default="")
@with_appcontext
def migrate_abs_to_assiduites(
dept: str = "", morning: str = "", noon: str = "", evening: str = ""
): # migrate-scodoc7-dept-archives
"""Post-migration: renomme les archives en fonction des id de ScoDoc 9"""
tools.migrate_abs_to_assiduites(dept, morning, noon, evening)
@app.cli.command() @app.cli.command()
@click.argument("dept", default="") @click.argument("dept", default="")
@with_appcontext @with_appcontext

View File

@ -8,3 +8,4 @@ from tools.import_scodoc7_user_db import import_scodoc7_user_db
from tools.import_scodoc7_dept import import_scodoc7_dept from tools.import_scodoc7_dept import import_scodoc7_dept
from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archives from tools.migrate_scodoc7_archives import migrate_scodoc7_dept_archives
from tools.migrate_scodoc7_logos import migrate_scodoc7_dept_logos from tools.migrate_scodoc7_logos import migrate_scodoc7_dept_logos
from tools.migrate_abs_to_assiduites import migrate_abs_to_assiduites

View File

@ -0,0 +1,173 @@
# Script de migration des données de la base "absences" -> "assiduites"/"justificatifs"
from app import db
from app.models import (
Assiduite,
Justificatif,
Absence,
Identite,
ModuleImpl,
Departement,
)
from app.scodoc.sco_utils import EtatAssiduite, EtatJustificatif, localize_datetime
from datetime import time, datetime, date
class glob:
DUPLICATIONS_ASSIDUITES: dict[tuple[date, bool, int], Assiduite] = {}
DUPLICATED: list[Justificatif] = []
def migrate_abs_to_assiduites(
dept: str = "", morning: str = None, noon: str = None, evening: str = None
):
"""
une absence à 3 états:
|.estabs|.estjust|
|1|0| -> absence non justifiée
|1|1| -> absence justifiée
|0|1| -> justifié
dualité des temps :
.matin: bool (0:00 -> time_pref | time_pref->23:59:59)
.jour : date (jour de l'absence/justificatif)
.moduleimpl_id: relation -> moduleimpl_id
description:str -> motif abs / raision justif
.entry_date: datetime -> timestamp d'entrée de l'abs
.etudid: relation -> Identite
"""
if morning == "":
pref_time_morning = time(8, 0)
else:
morning: list[str] = morning.split("h")
pref_time_morning = time(int(morning[0]), int(morning[1]))
if noon == "":
pref_time_noon = time(12, 0)
else:
noon: list[str] = noon.split("h")
pref_time_noon = time(int(noon[0]), int(noon[1]))
if evening == "":
pref_time_evening = time(18, 0)
else:
evening: list[str] = evening.split("h")
pref_time_evening = time(int(evening[0]), int(evening[1]))
absences_query = Absence.query
if dept != "":
depts_id = [dep.id for dep in Departement.query.filter_by(acronym=dept).all()]
absences_query = absences_query.filter(Absence.etudid.in_(depts_id))
absences: list[Absence] = absences_query.order_by(Absence.jour).all()
glob.DUPLICATED = []
glob.DUPLICATIONS_ASSIDUITES = {}
for abs in absences:
print(f"\n== {abs.jour}:{abs.etudid}:{abs.matin} ==")
if abs.estabs:
generated = _from_abs_to_assiduite(
abs, pref_time_morning, pref_time_noon, pref_time_evening
)
if not isinstance(generated, str):
db.session.add(generated)
print(
f"{abs.jour}:absence:{abs.etudid}:{abs.matin} -> {generated.date_debut}:{generated.date_fin}"
)
if abs.estjust:
generated = _from_abs_to_justificatif(
abs, pref_time_morning, pref_time_noon, pref_time_evening
)
if not isinstance(generated, str):
db.session.add(generated)
print(
f"{abs.jour}:justif:{abs.etudid}:{abs.matin} -> {generated.date_debut}:{generated.date_fin}"
)
dup_assi = glob.DUPLICATED
assi: Assiduite
for assi in dup_assi:
assi.moduleimpl_id = None
db.session.add(assi)
db.session.commit()
def _from_abs_to_assiduite(
_abs: Absence, morning: time, noon: time, evening: time
) -> Assiduite:
etat = EtatAssiduite.ABSENT
date_deb: datetime = None
date_fin: datetime = None
if _abs.matin:
date_deb = datetime.combine(_abs.jour, morning)
date_fin = datetime.combine(_abs.jour, noon)
else:
date_deb = datetime.combine(_abs.jour, noon)
date_fin = datetime.combine(_abs.jour, evening)
date_deb = localize_datetime(date_deb)
date_fin = localize_datetime(date_fin)
duplicata: Assiduite = glob.DUPLICATIONS_ASSIDUITES.get(
(_abs.jour, _abs.matin, _abs.etudid)
)
if duplicata is not None:
glob.DUPLICATED.append(duplicata)
return "Duplicated"
desc: str = _abs.description
entry_date: datetime = _abs.entry_date
etud: Identite = Identite.query.filter_by(id=_abs.etudid).first()
moduleimpl: ModuleImpl = ModuleImpl.query.filter_by(id=_abs.moduleimpl_id).first()
retour = Assiduite.create_assiduite(
etud=etud,
date_debut=date_deb,
date_fin=date_fin,
etat=etat,
moduleimpl=moduleimpl,
description=desc,
entry_date=entry_date,
)
glob.DUPLICATIONS_ASSIDUITES[(_abs.jour, _abs.matin, _abs.etudid)] = retour
return retour
def _from_abs_to_justificatif(
_abs: Absence, morning: time, noon: time, evening: time
) -> Justificatif:
etat = EtatJustificatif.VALIDE
date_deb: datetime = None
date_fin: datetime = None
if _abs.matin:
date_deb = datetime.combine(_abs.jour, morning)
date_fin = datetime.combine(_abs.jour, noon)
else:
date_deb = datetime.combine(_abs.jour, noon)
date_fin = datetime.combine(_abs.jour, evening)
date_deb = localize_datetime(date_deb)
date_fin = localize_datetime(date_fin)
desc: str = _abs.description
entry_date: datetime = _abs.entry_date
etud: Identite = Identite.query.filter_by(id=_abs.etudid).first()
retour = Justificatif.create_justificatif(
etud=etud,
date_debut=date_deb,
date_fin=date_fin,
etat=etat,
raison=desc,
entry_date=entry_date,
)
return retour