from datetime import UTC, 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=lambda: datetime.now(UTC) ) updated_at: so.Mapped[datetime] = so.mapped_column( sa.DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC) ) 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)