""" Helper functions for API endpoints """ import uuid from typing import Any, Type from fastapi import HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import Query from datetime import datetime, timedelta from app.models.user import User from app.models.ticket import Ticket from app.models.category import Category from app.services.audit_service import AuditService def validate_uuid_param(value: str, param_name: str = "ID") -> uuid.UUID: """Valida y convierte string a UUID""" try: return uuid.UUID(value) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid {param_name} format" ) def apply_client_permissions(query: Query, model: Type, current_user: User) -> Query: """Aplica filtros de tenant y permisos de cliente""" query = query.where(model.tenant_id == current_user.tenant_id) if current_user.role in ["CLIENT_USER", "CLIENT_ADMIN"]: query = query.where(model.created_by == current_user.id) return query def apply_enum_filter(query: Query, model_field: Any, filter_value: str, enum_class: Type, filter_name: str) -> Query: """Aplica filtro de enum genérico""" if filter_value: try: enum_val = enum_class[filter_value.upper()] return query.where(model_field == enum_val) except KeyError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid {filter_name}: {filter_value}" ) return query async def safe_audit_log(db: AsyncSession, **kwargs): """Registra en auditoría sin fallar la operación principal""" try: await AuditService.log(db=db, **kwargs) await db.commit() except Exception: pass # Silent fail para audit logs async def generate_next_ticket_number(db: AsyncSession, tenant_id: uuid.UUID) -> str: """Genera el siguiente número de ticket único para el tenant""" result = await db.execute( select(Ticket.ticket_number) .where(Ticket.tenant_id == tenant_id) .order_by(Ticket.ticket_number.desc()) .limit(1) ) last_ticket_number = result.scalar_one_or_none() if last_ticket_number: last_number = int(last_ticket_number.split('-')[1]) next_number = last_number + 1 else: next_number = 1 return f"TK-{next_number:06d}" def calculate_sla_deadlines(category: Category = None) -> tuple[datetime, datetime]: """Calcula SLA response y resolution deadlines""" if not category: return None, None now = datetime.utcnow() sla_response_due = now + timedelta(hours=category.sla_response_hours) sla_resolution_due = now + timedelta(hours=category.sla_resolution_hours) return sla_response_due, sla_resolution_due def ticket_to_dict(ticket: Ticket) -> dict: """Convierte un modelo Ticket a diccionario de respuesta""" return { "id": str(ticket.id), "ticket_number": ticket.ticket_number, "subject": ticket.subject, "title": ticket.subject, "description": ticket.description, "status": ticket.status.value, "priority": ticket.priority.value, "category_id": str(ticket.category_id) if ticket.category_id else None, "category_name": ticket.category.name if ticket.category else None, "affected_system_id": str(ticket.affected_system_id) if ticket.affected_system_id else None, "affected_system_name": ticket.affected_system.name if ticket.affected_system else None, "created_by": str(ticket.created_by), "assigned_to": str(ticket.assigned_to) if ticket.assigned_to else None, "assigned_to_name": f"{ticket.assigned_to_user.first_name} {ticket.assigned_to_user.last_name}" if ticket.assigned_to_user else None, "created_at": ticket.created_at, "updated_at": ticket.updated_at, "sla_response_due": ticket.sla_response_due, "sla_resolution_due": ticket.sla_resolution_due, "first_response_at": ticket.first_response_at, "resolved_at": ticket.resolved_at, "tenant_id": str(ticket.tenant_id) }