from __future__ import annotations from datetime import datetime from typing import Any import uuid from sqlalchemy import DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from govoplan_core.db.base import Base, TimestampMixin def new_uuid() -> str: return str(uuid.uuid4()) class Ticket(Base, TimestampMixin): __tablename__ = "tickets" __table_args__ = ( UniqueConstraint("tenant_id", "ticket_number", name="uq_ticket_number"), Index("ix_ticket_queue", "tenant_id", "queue_ref", "status", "priority"), Index("ix_ticket_catalog", "tenant_id", "status", "updated_at"), Index("ix_ticket_service_target", "tenant_id", "service_target_at", "status"), ) id: Mapped[str] = mapped_column(String(255), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) ticket_number: Mapped[str] = mapped_column(String(255), nullable=False, index=True) revision: Mapped[int] = mapped_column(Integer, nullable=False, default=1) ticket_type: Mapped[str] = mapped_column(String(80), nullable=False, index=True) priority: Mapped[str] = mapped_column(String(40), nullable=False, index=True) status: Mapped[str] = mapped_column(String(40), nullable=False, index=True) title: Mapped[str] = mapped_column(String(500), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) visibility: Mapped[str] = mapped_column(String(40), nullable=False, index=True) queue_ref: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) assignee: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) reporter: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) requester: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) participants: Mapped[list[dict[str, Any]]] = mapped_column(JSON, nullable=False, default=list) links: Mapped[list[dict[str, Any]]] = mapped_column(JSON, nullable=False, default=list) metadata_payload: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, nullable=False, default=dict) search_text: Mapped[str] = mapped_column(Text, nullable=False) received_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) change_reason: Mapped[str] = mapped_column(String(1_000), nullable=False) service_target_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) resolution_summary: Mapped[str | None] = mapped_column(Text, nullable=True) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) created_by: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) updated_by: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) class TicketHistory(Base, TimestampMixin): __tablename__ = "ticket_history" __table_args__ = ( UniqueConstraint("tenant_id", "ticket_id", "revision", name="uq_ticket_history_revision"), UniqueConstraint("tenant_id", "idempotency_key", name="uq_ticket_history_idempotency"), Index("ix_ticket_history_timeline", "tenant_id", "ticket_id", "occurred_at"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) ticket_id: Mapped[str] = mapped_column( ForeignKey("tickets.id", ondelete="RESTRICT"), nullable=False, index=True ) revision: Mapped[int] = mapped_column(Integer, nullable=False) event_type: Mapped[str] = mapped_column(String(120), nullable=False, index=True) occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) actor_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) reason: Mapped[str] = mapped_column(String(1_000), nullable=False) idempotency_key: Mapped[str] = mapped_column(String(255), nullable=False) request_sha256: Mapped[str] = mapped_column(String(64), nullable=False) snapshot: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) details: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict) class TicketComment(Base, TimestampMixin): __tablename__ = "ticket_comments" __table_args__ = ( UniqueConstraint("tenant_id", "comment_id", name="uq_ticket_comment"), Index("ix_ticket_comment_timeline", "tenant_id", "ticket_id", "created_at"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) ticket_id: Mapped[str] = mapped_column( ForeignKey("tickets.id", ondelete="RESTRICT"), nullable=False, index=True ) comment_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) ticket_revision: Mapped[int] = mapped_column(Integer, nullable=False) visibility: Mapped[str] = mapped_column(String(40), nullable=False, index=True) body: Mapped[str] = mapped_column(Text, nullable=False) created_by: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) class TicketEscalation(Base, TimestampMixin): __tablename__ = "ticket_escalations" __table_args__ = ( UniqueConstraint("tenant_id", "ticket_id", "idempotency_key", name="uq_ticket_escalation_replay"), UniqueConstraint("tenant_id", "provider_id", "case_id", name="uq_ticket_case_link"), Index("ix_ticket_escalation_timeline", "tenant_id", "ticket_id", "occurred_at"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) ticket_id: Mapped[str] = mapped_column( ForeignKey("tickets.id", ondelete="RESTRICT"), nullable=False, index=True ) provider_id: Mapped[str] = mapped_column(String(200), nullable=False, index=True) idempotency_key: Mapped[str] = mapped_column(String(255), nullable=False) request_sha256: Mapped[str] = mapped_column(String(64), nullable=False) occurred_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) actor_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) case_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) case_number: Mapped[str] = mapped_column(String(255), nullable=False) case_url: Mapped[str] = mapped_column(String(1_500), nullable=False) handoff_note: Mapped[str | None] = mapped_column(Text, nullable=True) outcome: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict) __all__ = ["Ticket", "TicketComment", "TicketEscalation", "TicketHistory"]