Release v1.7.1 - Mejoras en SLA, Auditoria y Multi-tenant
✨ Características Nuevas: - Cálculo automático de SLA en tickets basado en categoría - Auto-asignación de tickets según configuración de categoría - Auditoría completa en operaciones de categorías (create/update/delete) - Visualización de estado SLA en listado y detalle de tickets 🐛 Correcciones: - Fix actualización de status en tenants (manejo correcto de enum TenantStatus) - Corrección de campos contact_phone y contact_email en tenants - Corrección de modelo TicketResponse (agregar campos SLA y usar ConfigDict) - Eliminación de archivo changelog duplicado 🔧 Mejoras de Infraestructura: - Agregar montaje de backend en workers y beat para imports correctos - Mejorar path handling en sla_tasks.py para Docker - Scripts de testing integrados (test_frontend_integration, test_manual, test_tenant_update) - Agregar database.py en workers/app/core para sesiones async 📝 Frontend: - Actualizar UI de tenants con nuevos campos (email, teléfono, status enum) - Agregar columna de SLA en listado de tickets - Mostrar información detallada de SLA en vista de ticket individual - Indicadores visuales de estado de SLA (vencido, cumplido, en plazo)
This commit is contained in:
@@ -4,7 +4,7 @@ Tickets endpoints - ServiceManagerWeb
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
@@ -45,6 +45,8 @@ class TicketUpdate(BaseModel):
|
||||
assigned_to: Optional[str] = None
|
||||
|
||||
class TicketResponse(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: str
|
||||
ticket_number: str
|
||||
subject: str
|
||||
@@ -58,9 +60,10 @@ class TicketResponse(BaseModel):
|
||||
assigned_to: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
sla_response_due: Optional[datetime] = None
|
||||
sla_resolution_due: Optional[datetime] = None
|
||||
first_response_at: Optional[datetime] = None
|
||||
resolved_at: Optional[datetime] = None
|
||||
|
||||
class TicketCloseRequest(BaseModel):
|
||||
resolution: Optional[str] = None
|
||||
@@ -108,6 +111,7 @@ async def create_ticket(
|
||||
system_uuid = uuid.UUID(ticket.affected_system_id) if ticket.affected_system_id else None
|
||||
|
||||
# Validar categoría
|
||||
category = None
|
||||
if category_uuid:
|
||||
category = await db.get(Category, category_uuid)
|
||||
if not category:
|
||||
@@ -125,6 +129,21 @@ async def create_ticket(
|
||||
detail=f"El sistema con ID {ticket.affected_system_id} no existe."
|
||||
)
|
||||
|
||||
# Calcular SLA deadlines basados en la categoría
|
||||
from datetime import timedelta
|
||||
sla_response_due = None
|
||||
sla_resolution_due = None
|
||||
assigned_to_user = None
|
||||
|
||||
if category:
|
||||
now = datetime.utcnow()
|
||||
sla_response_due = now + timedelta(hours=category.sla_response_hours)
|
||||
sla_resolution_due = now + timedelta(hours=category.sla_resolution_hours)
|
||||
|
||||
# Auto-asignar si la categoría tiene configurado auto_assign_to
|
||||
if category.auto_assign_to:
|
||||
assigned_to_user = category.auto_assign_to
|
||||
|
||||
db_ticket = Ticket(
|
||||
id=uuid.uuid4(),
|
||||
tenant_id=current_user.tenant_id,
|
||||
@@ -135,7 +154,10 @@ async def create_ticket(
|
||||
affected_system_id=system_uuid,
|
||||
priority=TicketPriority[ticket.priority.upper()],
|
||||
created_by=current_user.id,
|
||||
assigned_to=assigned_to_user,
|
||||
status=TicketStatus.NEW,
|
||||
sla_response_due=sla_response_due,
|
||||
sla_resolution_due=sla_resolution_due,
|
||||
created_at=datetime.utcnow(),
|
||||
updated_at=datetime.utcnow()
|
||||
)
|
||||
@@ -251,7 +273,7 @@ async def get_tickets(
|
||||
result = await db.execute(query)
|
||||
tickets = result.scalars().all()
|
||||
|
||||
# ✅ CORREGIDO: Usar affected_system_id
|
||||
# ✅ CORREGIDO: Usar affected_system_id y agregar campos SLA
|
||||
return [
|
||||
{
|
||||
"id": str(t.id),
|
||||
@@ -266,7 +288,11 @@ async def get_tickets(
|
||||
"created_by": str(t.created_by),
|
||||
"assigned_to": str(t.assigned_to) if t.assigned_to else None,
|
||||
"created_at": t.created_at,
|
||||
"updated_at": t.updated_at
|
||||
"updated_at": t.updated_at,
|
||||
"sla_response_due": t.sla_response_due,
|
||||
"sla_resolution_due": t.sla_resolution_due,
|
||||
"first_response_at": t.first_response_at,
|
||||
"resolved_at": t.resolved_at
|
||||
}
|
||||
for t in tickets
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user