""" TicketIssue Model - ServiceManagerWeb Asuntos de escalación asociados a tickets. Creados por el dueño del ticket o el CLIENT_ADMIN del tenant. """ from sqlalchemy import String, ForeignKey, Text, Table, Column from sqlalchemy.orm import Mapped, mapped_column, relationship from typing import Optional, List from datetime import datetime import uuid from app.core.database import Base, GUID from app.models.ticket import TicketPriority from sqlalchemy import Enum as SAEnum from sqlalchemy.dialects.postgresql import ENUM as PG_ENUM # Tabla de relación N:M entre TicketIssue y User (usuarios etiquetados) ticket_issue_tagged_users = Table( "ticket_issue_tagged_users", Base.metadata, Column( "issue_id", GUID(), ForeignKey("ticket_issues.id", ondelete="CASCADE"), primary_key=True, nullable=False, ), Column( "user_id", GUID(), ForeignKey("users.id", ondelete="CASCADE"), primary_key=True, nullable=False, ), ) class TicketIssue(Base): """ Asunto de escalación de un ticket. Solo puede crearlo: - El usuario que creó el ticket (created_by del Ticket) - El CLIENT_ADMIN del mismo tenant """ __tablename__ = "ticket_issues" # Multi-tenancy — siempre filtrar por esto tenant_id: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, ) # Relación con el ticket padre ticket_id: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("tickets.id", ondelete="CASCADE"), nullable=False, ) # Quién lo creó created_by: Mapped[uuid.UUID] = mapped_column( GUID(), ForeignKey("users.id"), nullable=False, ) # Contenido content: Mapped[str] = mapped_column(Text, nullable=False) priority: Mapped[TicketPriority] = mapped_column( SAEnum(TicketPriority, name="ticket_priority_enum", native_enum=False).with_variant( PG_ENUM(TicketPriority, name="ticket_priority_enum", create_type=True), "postgresql", ), default=TicketPriority.MEDIUM, nullable=False, ) # Adjunto opcional (reutiliza file_handler igual que TicketAttachment) attachment_path: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) attachment_filename: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) attachment_mime_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # Relationships ticket: Mapped["Ticket"] = relationship("Ticket", back_populates="issues") created_by_user: Mapped["User"] = relationship( "User", foreign_keys=[created_by], ) tagged_users: Mapped[List["User"]] = relationship( "User", secondary=ticket_issue_tagged_users, ) def __repr__(self) -> str: return f""