"""Tracking & proof of delivery — Phase 10.7.""" from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, Float, 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 ( CustomerTrackingTokenStatus, GUID, ProofOfDeliveryStatus, TrackingSessionStatus, ) class TrackingSession( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, OptimisticLockMixin, ): __tablename__ = "tracking_sessions" dispatch_job_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) driver_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True) status: Mapped[TrackingSessionStatus] = mapped_column( default=TrackingSessionStatus.ACTIVE, nullable=False ) started_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) ended_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( Index("ix_tracking_sessions_tenant_status", "tenant_id", "status"), Index("ix_tracking_sessions_job", "tenant_id", "dispatch_job_id"), Index("ix_tracking_sessions_tenant_deleted", "tenant_id", "is_deleted"), ) class TrackingPoint( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, ): __tablename__ = "tracking_points" tracking_session_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) latitude: Mapped[float] = mapped_column(Float, nullable=False) longitude: Mapped[float] = mapped_column(Float, nullable=False) recorded_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False ) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( Index("ix_tracking_points_session", "tenant_id", "tracking_session_id"), Index("ix_tracking_points_recorded", "tenant_id", "recorded_at"), ) class ProofOfDelivery( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, OptimisticLockMixin, ): """Storage file refs for POD only — no blob ownership.""" __tablename__ = "proof_of_delivery" dispatch_job_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) status: Mapped[ProofOfDeliveryStatus] = mapped_column( default=ProofOfDeliveryStatus.PENDING, nullable=False ) signature_file_ref: Mapped[str | None] = mapped_column(String(255), nullable=True) photo_file_ref: Mapped[str | None] = mapped_column(String(255), nullable=True) delivery_code: Mapped[str | None] = mapped_column(String(50), nullable=True) notes: Mapped[str | None] = mapped_column(Text, nullable=True) captured_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( Index("ix_proof_of_delivery_job", "tenant_id", "dispatch_job_id"), Index("ix_proof_of_delivery_tenant_status", "tenant_id", "status"), Index("ix_proof_of_delivery_tenant_deleted", "tenant_id", "is_deleted"), ) class CustomerTrackingToken( Base, UUIDPrimaryKeyMixin, TenantMixin, TimestampMixin, SoftDeleteMixin, ActorAuditMixin, ): __tablename__ = "customer_tracking_tokens" dispatch_job_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False) token: Mapped[str] = mapped_column(String(128), nullable=False) status: Mapped[CustomerTrackingTokenStatus] = mapped_column( default=CustomerTrackingTokenStatus.ACTIVE, nullable=False ) expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) __table_args__ = ( UniqueConstraint("tenant_id", "token", name="uq_customer_tracking_tokens_token"), Index("ix_customer_tracking_tokens_job", "tenant_id", "dispatch_job_id"), Index("ix_customer_tracking_tokens_tenant_status", "tenant_id", "status"), )