124 lines
7.0 KiB
Python
124 lines
7.0 KiB
Python
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"]
|