forked from ScoDoc/ScoDoc
344 lines
9.8 KiB
Python
344 lines
9.8 KiB
Python
|
# -*- coding: UTF-8 -*
|
||
|
"""Gestion de l'assiduité (assiduités + justificatifs)
|
||
|
"""
|
||
|
from datetime import datetime
|
||
|
|
||
|
from app import db
|
||
|
from app.models import ModuleImpl
|
||
|
from app.models.etudiants import Identite
|
||
|
from app.scodoc.sco_utils import (
|
||
|
EtatAssiduite,
|
||
|
EtatJustificatif,
|
||
|
localize_datetime,
|
||
|
is_period_overlapping,
|
||
|
)
|
||
|
from app.scodoc.sco_exceptions import ScoValueError
|
||
|
from app.scodoc.sco_utils import (
|
||
|
EtatAssiduite,
|
||
|
EtatJustificatif,
|
||
|
localize_datetime,
|
||
|
)
|
||
|
|
||
|
|
||
|
class Assiduite(db.Model):
|
||
|
"""
|
||
|
Représente une assiduité:
|
||
|
- une plage horaire lié à un état et un étudiant
|
||
|
- un module si spécifiée
|
||
|
- une description si spécifiée
|
||
|
"""
|
||
|
|
||
|
__tablename__ = "assiduites"
|
||
|
|
||
|
id = db.Column(db.Integer, primary_key=True, nullable=False)
|
||
|
assiduite_id = db.synonym("id")
|
||
|
|
||
|
date_debut = db.Column(
|
||
|
db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
|
||
|
)
|
||
|
date_fin = db.Column(
|
||
|
db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
|
||
|
)
|
||
|
|
||
|
moduleimpl_id = db.Column(
|
||
|
db.Integer,
|
||
|
db.ForeignKey("notes_moduleimpl.id", ondelete="SET NULL"),
|
||
|
)
|
||
|
etudid = db.Column(
|
||
|
db.Integer,
|
||
|
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||
|
index=True,
|
||
|
nullable=False,
|
||
|
)
|
||
|
etat = db.Column(db.Integer, nullable=False)
|
||
|
|
||
|
desc = db.Column(db.Text)
|
||
|
|
||
|
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||
|
|
||
|
user_id = db.Column(
|
||
|
db.Integer,
|
||
|
db.ForeignKey("user.id", ondelete="SET NULL"),
|
||
|
nullable=True,
|
||
|
)
|
||
|
|
||
|
est_just = db.Column(db.Boolean, server_default="false", nullable=False)
|
||
|
|
||
|
def to_dict(self, format_api=True) -> dict:
|
||
|
"""Retourne la représentation json de l'assiduité"""
|
||
|
etat = self.etat
|
||
|
|
||
|
if format_api:
|
||
|
etat = EtatAssiduite.inverse().get(self.etat).name
|
||
|
data = {
|
||
|
"assiduite_id": self.id,
|
||
|
"etudid": self.etudid,
|
||
|
"moduleimpl_id": self.moduleimpl_id,
|
||
|
"date_debut": self.date_debut,
|
||
|
"date_fin": self.date_fin,
|
||
|
"etat": etat,
|
||
|
"desc": self.desc,
|
||
|
"entry_date": self.entry_date,
|
||
|
"user_id": self.user_id,
|
||
|
"est_just": self.est_just,
|
||
|
}
|
||
|
return data
|
||
|
|
||
|
@classmethod
|
||
|
def create_assiduite(
|
||
|
cls,
|
||
|
etud: Identite,
|
||
|
date_debut: datetime,
|
||
|
date_fin: datetime,
|
||
|
etat: EtatAssiduite,
|
||
|
moduleimpl: ModuleImpl = None,
|
||
|
description: str = None,
|
||
|
entry_date: datetime = None,
|
||
|
user_id: int = None,
|
||
|
est_just: bool = False,
|
||
|
) -> object or int:
|
||
|
"""Créer une nouvelle assiduité pour l'étudiant"""
|
||
|
# Vérification de non duplication des périodes
|
||
|
assiduites: list[Assiduite] = etud.assiduites
|
||
|
if is_period_conflicting(date_debut, date_fin, assiduites, Assiduite):
|
||
|
raise ScoValueError(
|
||
|
"Duplication des assiduités (la période rentrée rentre en conflit avec une assiduité enregistrée)"
|
||
|
)
|
||
|
if moduleimpl is not None:
|
||
|
# Vérification de l'existence du module pour l'étudiant
|
||
|
if moduleimpl.est_inscrit(etud):
|
||
|
nouv_assiduite = Assiduite(
|
||
|
date_debut=date_debut,
|
||
|
date_fin=date_fin,
|
||
|
etat=etat,
|
||
|
etudiant=etud,
|
||
|
moduleimpl_id=moduleimpl.id,
|
||
|
desc=description,
|
||
|
entry_date=entry_date,
|
||
|
user_id=user_id,
|
||
|
est_just=est_just,
|
||
|
)
|
||
|
else:
|
||
|
raise ScoValueError("L'étudiant n'est pas inscrit au moduleimpl")
|
||
|
else:
|
||
|
nouv_assiduite = Assiduite(
|
||
|
date_debut=date_debut,
|
||
|
date_fin=date_fin,
|
||
|
etat=etat,
|
||
|
etudiant=etud,
|
||
|
desc=description,
|
||
|
entry_date=entry_date,
|
||
|
user_id=user_id,
|
||
|
est_just=est_just,
|
||
|
)
|
||
|
|
||
|
return nouv_assiduite
|
||
|
|
||
|
@classmethod
|
||
|
def fast_create_assiduite(
|
||
|
cls,
|
||
|
etudid: int,
|
||
|
date_debut: datetime,
|
||
|
date_fin: datetime,
|
||
|
etat: EtatAssiduite,
|
||
|
moduleimpl_id: int = None,
|
||
|
description: str = None,
|
||
|
entry_date: datetime = None,
|
||
|
est_just: bool = False,
|
||
|
) -> object or int:
|
||
|
"""Créer une nouvelle assiduité pour l'étudiant"""
|
||
|
# Vérification de non duplication des périodes
|
||
|
|
||
|
nouv_assiduite = Assiduite(
|
||
|
date_debut=date_debut,
|
||
|
date_fin=date_fin,
|
||
|
etat=etat,
|
||
|
etudid=etudid,
|
||
|
moduleimpl_id=moduleimpl_id,
|
||
|
desc=description,
|
||
|
entry_date=entry_date,
|
||
|
est_just=est_just,
|
||
|
)
|
||
|
|
||
|
return nouv_assiduite
|
||
|
|
||
|
|
||
|
class Justificatif(db.Model):
|
||
|
"""
|
||
|
Représente un justificatif:
|
||
|
- une plage horaire lié à un état et un étudiant
|
||
|
- une raison si spécifiée
|
||
|
- un fichier si spécifié
|
||
|
"""
|
||
|
|
||
|
__tablename__ = "justificatifs"
|
||
|
|
||
|
id = db.Column(db.Integer, primary_key=True)
|
||
|
justif_id = db.synonym("id")
|
||
|
|
||
|
date_debut = db.Column(
|
||
|
db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
|
||
|
)
|
||
|
date_fin = db.Column(
|
||
|
db.DateTime(timezone=True), server_default=db.func.now(), nullable=False
|
||
|
)
|
||
|
|
||
|
etudid = db.Column(
|
||
|
db.Integer,
|
||
|
db.ForeignKey("identite.id", ondelete="CASCADE"),
|
||
|
index=True,
|
||
|
nullable=False,
|
||
|
)
|
||
|
etat = db.Column(
|
||
|
db.Integer,
|
||
|
nullable=False,
|
||
|
)
|
||
|
|
||
|
entry_date = db.Column(db.DateTime(timezone=True), server_default=db.func.now())
|
||
|
|
||
|
user_id = db.Column(
|
||
|
db.Integer,
|
||
|
db.ForeignKey("user.id", ondelete="SET NULL"),
|
||
|
nullable=True,
|
||
|
index=True,
|
||
|
)
|
||
|
|
||
|
raison = db.Column(db.Text())
|
||
|
|
||
|
# Archive_id -> sco_archives_justificatifs.py
|
||
|
fichier = db.Column(db.Text())
|
||
|
|
||
|
def to_dict(self, format_api: bool = False) -> dict:
|
||
|
"""transformation de l'objet en dictionnaire sérialisable"""
|
||
|
|
||
|
etat = self.etat
|
||
|
|
||
|
if format_api:
|
||
|
etat = EtatJustificatif.inverse().get(self.etat).name
|
||
|
|
||
|
data = {
|
||
|
"justif_id": self.justif_id,
|
||
|
"etudid": self.etudid,
|
||
|
"date_debut": self.date_debut,
|
||
|
"date_fin": self.date_fin,
|
||
|
"etat": etat,
|
||
|
"raison": self.raison,
|
||
|
"fichier": self.fichier,
|
||
|
"entry_date": self.entry_date,
|
||
|
"user_id": self.user_id,
|
||
|
}
|
||
|
return data
|
||
|
|
||
|
@classmethod
|
||
|
def create_justificatif(
|
||
|
cls,
|
||
|
etud: Identite,
|
||
|
date_debut: datetime,
|
||
|
date_fin: datetime,
|
||
|
etat: EtatJustificatif,
|
||
|
raison: str = None,
|
||
|
entry_date: datetime = None,
|
||
|
user_id: int = None,
|
||
|
) -> object or int:
|
||
|
"""Créer un nouveau justificatif pour l'étudiant"""
|
||
|
nouv_justificatif = Justificatif(
|
||
|
date_debut=date_debut,
|
||
|
date_fin=date_fin,
|
||
|
etat=etat,
|
||
|
etudiant=etud,
|
||
|
raison=raison,
|
||
|
entry_date=entry_date,
|
||
|
user_id=user_id,
|
||
|
)
|
||
|
return nouv_justificatif
|
||
|
|
||
|
@classmethod
|
||
|
def fast_create_justificatif(
|
||
|
cls,
|
||
|
etudid: int,
|
||
|
date_debut: datetime,
|
||
|
date_fin: datetime,
|
||
|
etat: EtatJustificatif,
|
||
|
raison: str = None,
|
||
|
entry_date: datetime = None,
|
||
|
) -> object or int:
|
||
|
"""Créer un nouveau justificatif pour l'étudiant"""
|
||
|
|
||
|
nouv_justificatif = Justificatif(
|
||
|
date_debut=date_debut,
|
||
|
date_fin=date_fin,
|
||
|
etat=etat,
|
||
|
etudid=etudid,
|
||
|
raison=raison,
|
||
|
entry_date=entry_date,
|
||
|
)
|
||
|
|
||
|
return nouv_justificatif
|
||
|
|
||
|
|
||
|
def is_period_conflicting(
|
||
|
date_debut: datetime,
|
||
|
date_fin: datetime,
|
||
|
collection: list[Assiduite or Justificatif],
|
||
|
collection_cls: Assiduite or Justificatif,
|
||
|
) -> bool:
|
||
|
"""
|
||
|
Vérifie si une date n'entre pas en collision
|
||
|
avec les justificatifs ou assiduites déjà présentes
|
||
|
"""
|
||
|
|
||
|
date_debut = localize_datetime(date_debut)
|
||
|
date_fin = localize_datetime(date_fin)
|
||
|
|
||
|
if (
|
||
|
collection.filter_by(date_debut=date_debut, date_fin=date_fin).first()
|
||
|
is not None
|
||
|
):
|
||
|
return True
|
||
|
|
||
|
count: int = collection.filter(
|
||
|
collection_cls.date_debut < date_fin, collection_cls.date_fin > date_debut
|
||
|
).count()
|
||
|
|
||
|
return count > 0
|
||
|
|
||
|
|
||
|
def compute_assiduites_justified(
|
||
|
justificatifs: Justificatif = Justificatif, reset: bool = False
|
||
|
) -> list[int]:
|
||
|
"""Calcule et modifie les champs "est_just" de chaque assiduité lié à l'étud
|
||
|
retourne la liste des assiduite_id justifiées
|
||
|
|
||
|
Si reset alors : met à false toutes les assiduités non justifiées par les justificatifs donnés
|
||
|
"""
|
||
|
|
||
|
list_assiduites_id: set[int] = set()
|
||
|
for justi in justificatifs:
|
||
|
assiduites: Assiduite = (
|
||
|
Assiduite.query.join(Justificatif, Justificatif.etudid == Assiduite.etudid)
|
||
|
.filter(Assiduite.etat != EtatAssiduite.PRESENT)
|
||
|
.filter(
|
||
|
Assiduite.date_debut <= justi.date_fin,
|
||
|
Assiduite.date_fin >= justi.date_debut,
|
||
|
)
|
||
|
)
|
||
|
|
||
|
for assi in assiduites:
|
||
|
assi.est_just = True
|
||
|
list_assiduites_id.add(assi.id)
|
||
|
db.session.add(assi)
|
||
|
|
||
|
if reset:
|
||
|
un_justified: Assiduite = (
|
||
|
Assiduite.query.filter(Assiduite.id.not_in(list_assiduites_id))
|
||
|
.filter(Assiduite.etat != EtatAssiduite.PRESENT)
|
||
|
.join(Justificatif, Justificatif.etudid == Assiduite.etudid)
|
||
|
)
|
||
|
|
||
|
for assi in un_justified:
|
||
|
assi.est_just = False
|
||
|
db.session.add(assi)
|
||
|
|
||
|
db.session.commit()
|
||
|
return list(list_assiduites_id)
|