"""Availability, shifts & working zones — Phase 10.3.""" from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, Index, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.types import JSON from app.core.database import Base from app.models.base import ( ActorAuditMixin, OptimisticLockMixin, SoftDeleteMixin, TenantMixin, TimestampMixin, UUIDPrimaryKeyMixin, ) from app.models.types import ( DriverAvailabilityStatus, GUID, ShiftAssignmentStatus, ShiftStatus, WorkingZoneStatus, ) class DriverAvailability( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, OptimisticLockMixin, ): __tablename__ = "driver_availabilities" driver_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) status: Mapped[DriverAvailabilityStatus] = mapped_column( default=DriverAvailabilityStatus.OFFLINE, nullable=False ) effective_from: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) effective_until: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) notes: Mapped[str | None] = mapped_column(Text, nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( Index("ix_driver_availabilities_driver", "tenant_id", "driver_id"), Index("ix_driver_availabilities_tenant_deleted", "tenant_id", "is_deleted"), ) class Shift( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, OptimisticLockMixin, ): __tablename__ = "shifts" organization_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) hub_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) status: Mapped[ShiftStatus] = mapped_column( default=ShiftStatus.DRAFT, nullable=False ) starts_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) ends_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_shifts_tenant_code"), Index("ix_shifts_tenant_status", "tenant_id", "status"), Index("ix_shifts_tenant_deleted", "tenant_id", "is_deleted"), ) class ShiftAssignment( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, ): __tablename__ = "shift_assignments" shift_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) driver_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) status: Mapped[ShiftAssignmentStatus] = mapped_column( default=ShiftAssignmentStatus.ASSIGNED, nullable=False ) notes: Mapped[str | None] = mapped_column(Text, nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( UniqueConstraint( "tenant_id", "shift_id", "driver_id", name="uq_shift_assignments_tenant_shift_driver", ), Index("ix_shift_assignments_shift", "tenant_id", "shift_id"), Index("ix_shift_assignments_driver", "tenant_id", "driver_id"), ) class WorkingZone( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, OptimisticLockMixin, ): __tablename__ = "working_zones" organization_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) code: Mapped[str] = mapped_column(String(50), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) status: Mapped[WorkingZoneStatus] = mapped_column( default=WorkingZoneStatus.ACTIVE, nullable=False ) geo_boundary_ref: Mapped[str | None] = mapped_column(String(255), nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( UniqueConstraint("tenant_id", "code", name="uq_working_zones_tenant_code"), Index("ix_working_zones_tenant_status", "tenant_id", "status"), Index("ix_working_zones_tenant_deleted", "tenant_id", "is_deleted"), )