Files
timemaster/backend/app/models/absence.py
T
sysops 1fedd683e0 Initial commit – TimeMaster Zeiterfassung & HR-Tool
Stand: agent-06 (Audit-Log), agent-05 (Krankmeldung), agent-07 Phase 1 (Personalnummer),
Busylight-Pull-Integration, TOTP/2FA, Abwesenheiten, Zeiterfassung, Kiosk-Grundgerüst.
Migrations 0001–0023 deployed auf 192.168.1.137 + .164.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 20:03:27 +02:00

88 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"<Absence {self.user_id} {self.start_date}{self.end_date} [{self.status}]>"