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>
103 lines
3.9 KiB
Python
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)
|