import uuid import enum from datetime import date, datetime from typing import TYPE_CHECKING from sqlalchemy import Boolean, Date, DateTime, Enum, ForeignKey, Numeric, String, Text, func from sqlalchemy.dialects.postgresql import JSONB, UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.database import Base if TYPE_CHECKING: from app.models.user import User from app.models.absence_type import AbsenceType class AbsenceStatus(str, enum.Enum): PENDING = "pending" APPROVED = "approved" REJECTED = "rejected" CANCELLED = "cancelled" class Absence(Base): __tablename__ = "absences" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True ) type_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("absence_types.id", ondelete="RESTRICT"), nullable=False ) start_date: Mapped[date] = mapped_column(Date, nullable=False) end_date: Mapped[date] = mapped_column(Date, nullable=False) half_day_start: Mapped[bool] = mapped_column(Boolean, default=False) half_day_end: Mapped[bool] = mapped_column(Boolean, default=False) working_days: Mapped[float] = mapped_column(Numeric(5, 1), default=0) status: Mapped[AbsenceStatus] = mapped_column( Enum(AbsenceStatus, name="absencestatus", values_callable=lambda x: [e.value for e in x]), nullable=False, default=AbsenceStatus.PENDING, ) approved_by: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL") ) substitute_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL") ) note: Mapped[str | None] = mapped_column(Text) rejection_reason: Mapped[str | None] = mapped_column(Text) correction_note: Mapped[str | None] = mapped_column(Text) # Zusatzinformationen (Weiterbildung, Dienstreise, etc.) # Struktur je Kategorie: # training: {"course_name": str, "provider": str, "location": str} # business_trip: {"destination": str, "purpose": str} meta: Mapped[dict | None] = mapped_column(JSONB) # Krankheit: Arbeitsunfähigkeitsbescheinigung certificate_required_by: Mapped[date | None] = mapped_column(Date) certificate_received_at: Mapped[date | None] = mapped_column(Date) # CalDAV-Sync caldav_uid: Mapped[str | None] = mapped_column(String(255)) caldav_user_etag: Mapped[str | None] = mapped_column(Text) caldav_company_etag: Mapped[str | None] = mapped_column(Text) caldav_last_error: Mapped[str | None] = mapped_column(Text) caldav_synced_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) user: Mapped["User"] = relationship( "User", primaryjoin="Absence.user_id == User.id", foreign_keys="[Absence.user_id]", lazy="noload", ) absence_type: Mapped["AbsenceType"] = relationship("AbsenceType", lazy="noload") approver: Mapped["User | None"] = relationship( "User", primaryjoin="Absence.approved_by == User.id", foreign_keys="[Absence.approved_by]", lazy="noload", ) substitute: Mapped["User | None"] = relationship( "User", primaryjoin="Absence.substitute_id == User.id", foreign_keys="[Absence.substitute_id]", lazy="noload", ) def __repr__(self) -> str: return f""