- Enhanced ticket and comment models with proper relationships - Updated client_profile model for better data handling - Improved auth endpoint with better error handling - Updated main app configuration and imports - Added new dependencies to requirements.txt - Enhanced tickets endpoint with attachment support
142 lines
4.7 KiB
Python
142 lines
4.7 KiB
Python
"""
|
|
Ticket Model - ServiceManagerWeb
|
|
Tickets de soporte - Core del negocio
|
|
"""
|
|
from sqlalchemy import String, ForeignKey, Text, Integer, CheckConstraint, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from sqlalchemy.dialects.postgresql import UUID, ENUM
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
import enum
|
|
import uuid
|
|
|
|
from app.core.database import Base
|
|
|
|
class TicketStatus(str, enum.Enum):
|
|
"""Estados posibles de un ticket"""
|
|
NEW = "NEW"
|
|
TRIAGE = "TRIAGE"
|
|
IN_PROGRESS = "IN_PROGRESS"
|
|
WAITING_CUSTOMER = "WAITING_CUSTOMER" # ✅ CORREGIDO: nombre según schema.sql
|
|
RESOLVED = "RESOLVED"
|
|
CLOSED = "CLOSED"
|
|
REOPENED = "REOPENED"
|
|
|
|
class TicketPriority(str, enum.Enum):
|
|
"""Prioridades posibles de un ticket"""
|
|
LOW = "LOW"
|
|
MEDIUM = "MEDIUM"
|
|
HIGH = "HIGH"
|
|
URGENT = "URGENT"
|
|
|
|
class Ticket(Base):
|
|
"""Modelo de tickets de soporte"""
|
|
__tablename__ = "tickets"
|
|
|
|
# Multi-tenancy
|
|
tenant_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("tenants.id", ondelete="CASCADE"),
|
|
nullable=False
|
|
)
|
|
|
|
# Campos básicos
|
|
ticket_number: Mapped[str] = mapped_column(String(20), nullable=False)
|
|
subject: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str] = mapped_column(Text, nullable=False)
|
|
|
|
# Estado y Prioridad
|
|
status: Mapped[TicketStatus] = mapped_column(
|
|
ENUM(TicketStatus, name="ticket_status_enum", create_type=False),
|
|
default=TicketStatus.NEW,
|
|
nullable=False
|
|
)
|
|
priority: Mapped[TicketPriority] = mapped_column(
|
|
ENUM(TicketPriority, name="ticket_priority_enum", create_type=False),
|
|
default=TicketPriority.MEDIUM,
|
|
nullable=False
|
|
)
|
|
|
|
# ✅ CORREGIDO: Foreign Keys apuntan a tablas correctas
|
|
created_by: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("users.id"),
|
|
nullable=False
|
|
)
|
|
assigned_to: Mapped[Optional[uuid.UUID]] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("users.id"),
|
|
nullable=True
|
|
)
|
|
|
|
# ✅ CORREGIDO: Renombrado de system_id a affected_system_id
|
|
affected_system_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("affected_systems.id"), # ✅ Tabla correcta
|
|
nullable=True
|
|
)
|
|
|
|
# ✅ CORREGIDO: Foreign key a tabla correcta
|
|
category_id: Mapped[Optional[uuid.UUID]] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("ticket_categories.id"), # ✅ Tabla correcta
|
|
nullable=True
|
|
)
|
|
|
|
# ✅ AÑADIDOS: Campos de SLA según schema.sql
|
|
sla_response_due: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
|
sla_resolution_due: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
|
first_response_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
|
resolved_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
|
|
|
# ✅ AÑADIDOS: Campos de CSAT (Customer Satisfaction) según schema.sql
|
|
rating: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
|
|
rating_comment: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
|
rated_at: Mapped[Optional[datetime]] = mapped_column(nullable=True)
|
|
|
|
# Relationships
|
|
tenant: Mapped["Tenant"] = relationship("Tenant", back_populates="tickets")
|
|
|
|
# ✅ ACTUALIZADO: Nombre de relación y optional
|
|
affected_system: Mapped[Optional["System"]] = relationship(
|
|
"System",
|
|
back_populates="tickets"
|
|
)
|
|
|
|
category: Mapped[Optional["Category"]] = relationship(
|
|
"Category",
|
|
back_populates="tickets"
|
|
)
|
|
|
|
created_by_user: Mapped["User"] = relationship(
|
|
"User",
|
|
foreign_keys=[created_by],
|
|
back_populates="created_tickets"
|
|
)
|
|
|
|
assigned_to_user: Mapped[Optional["User"]] = relationship(
|
|
"User",
|
|
foreign_keys=[assigned_to],
|
|
back_populates="assigned_tickets"
|
|
)
|
|
|
|
comments: Mapped[list["TicketComment"]] = relationship(
|
|
"TicketComment",
|
|
back_populates="ticket",
|
|
cascade="all, delete-orphan"
|
|
)
|
|
|
|
attachments: Mapped[list["TicketAttachment"]] = relationship(
|
|
"TicketAttachment",
|
|
back_populates="ticket",
|
|
cascade="all, delete-orphan"
|
|
)
|
|
|
|
# ✅ AÑADIDOS: Constraints según schema.sql
|
|
__table_args__ = (
|
|
UniqueConstraint('tenant_id', 'ticket_number', name='uq_tickets_tenant_number'),
|
|
CheckConstraint('rating >= 1 AND rating <= 5', name='check_rating_range'),
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<Ticket(id={self.id}, number='{self.ticket_number}', status={self.status})>" |