Files
tableau-de-bord/app/models.py
Antoine Van Elstraete 2d85fffd8d doc: document models and business rules
Co-authored-by: OpenAI/GPT-5.6-Terra <vibecoder@antoineve.me>
Co-authored-by: Google/Gemini-3.5-Flash <vibecoder@antoineve.me>
Co-authored-by: DeepSeek/DeepSeek-v4-Pro <vibecoder@antoineve.me>
Co-authored-by: DeepSeek/DeepSeek-v4-Flash <vibecoder@antoineve.me>
2026-08-13 10:58:49 +02:00

103 lines
3.9 KiB
Python

from datetime import date, datetime, time
import sqlalchemy as sa
import sqlalchemy.orm as so
from app import db
class WorkEntry(db.Model):
"""
Représente une entrée de journal de travail pour une journée unique.
Cette classe stocke les informations relatives à une journée de travail,
notamment la date, le type de journée (WORK, TT, GARDE, ASTREINTE, etc.),
les profils de trajet domicile-travail, le véhicule utilisé, un commentaire
et les plages horaires associées.
Invariants :
- La date est unique (une seule entrée par jour).
"""
__tablename__ = "work_entries"
id: so.Mapped[int] = so.mapped_column(primary_key=True)
date: so.Mapped[date] = so.mapped_column(sa.Date, unique=True, nullable=False)
journey_profile_id: so.Mapped[str | None] = so.mapped_column(sa.String(64), nullable=True)
motor_vehicle_id: so.Mapped[str | None] = so.mapped_column(sa.String(64), nullable=True)
day_type: so.Mapped[str] = so.mapped_column(sa.String(16), nullable=False, default="WORK")
comment: so.Mapped[str | None] = so.mapped_column(sa.Text, nullable=True)
created_at: so.Mapped[datetime] = so.mapped_column(sa.DateTime, default=datetime.utcnow)
updated_at: so.Mapped[datetime] = so.mapped_column(
sa.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow
)
time_slots: so.Mapped[list["TimeSlot"]] = so.relationship(
back_populates="entry", cascade="all, delete-orphan", order_by="TimeSlot.start_time"
)
def total_minutes(self) -> int:
"""
Calcule la durée totale travaillée dans la journée en minutes.
Cette méthode somme la durée de toutes les plages horaires (`TimeSlot`)
associées à cette entrée. Elle gère le passage de minuit : si l'heure de fin
d'une plage est inférieure ou égale à son heure de début, la plage est
considérée comme se terminant le lendemain (ajout de 24 heures).
Retour :
int : La durée totale en minutes.
"""
total = 0
for slot in self.time_slots:
start = slot.start_time.hour * 60 + slot.start_time.minute
end = slot.end_time.hour * 60 + slot.end_time.minute
if end <= start:
end += 24 * 60
total += end - start
return total
def total_hours_str(self) -> str:
"""
Retourne la durée totale travaillée sous forme de chaîne formatée (ex: "7h45").
Retour :
str : La durée formatée au format "HhMM".
"""
minutes = self.total_minutes()
return f"{minutes // 60}h{minutes % 60:02d}"
class TimeSlot(db.Model):
"""
Représente une plage horaire de travail au sein d'une journée.
Chaque plage possède une heure de début et une heure de fin. Elle est rattachée
à une entrée de journal (`WorkEntry`).
"""
__tablename__ = "time_slots"
id: so.Mapped[int] = so.mapped_column(primary_key=True)
entry_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey("work_entries.id"), nullable=False)
start_time: so.Mapped[time] = so.mapped_column(sa.Time, nullable=False)
end_time: so.Mapped[time] = so.mapped_column(sa.Time, nullable=False)
entry: so.Mapped["WorkEntry"] = so.relationship(back_populates="time_slots")
class LeaveBalance(db.Model):
"""
Représente le solde annuel des congés et RTT pour une année donnée.
Stocke les quotas initiaux/totaux de congés payés et de RTT alloués pour l'année.
Par défaut, un utilisateur bénéficie de 28 jours de congés et 19 jours de RTT.
"""
__tablename__ = "leave_balance"
id: so.Mapped[int] = so.mapped_column(primary_key=True)
year: so.Mapped[int] = so.mapped_column(sa.Integer, unique=True, nullable=False)
conges_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=28)
rtt_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=19)