""" Tickets endpoints - ServiceManagerWeb """ from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from typing import List, Optional from datetime import datetime from app.core.database import get_db from app.api.deps import get_current_user from app.models.ticket import Ticket, TicketStatus, TicketPriority from app.models.user import User from pydantic import BaseModel import uuid router = APIRouter() # =================================== # SCHEMAS # =================================== class TicketCreate(BaseModel): subject: str description: str category_id: Optional[str] = None system_id: Optional[str] = None priority: str = "MEDIUM" class TicketUpdate(BaseModel): subject: Optional[str] = None description: Optional[str] = None status: Optional[str] = None priority: Optional[str] = None assigned_to: Optional[str] = None class TicketResponse(BaseModel): id: str ticket_number: str subject: str description: str status: str priority: str category_id: Optional[str] = None system_id: Optional[str] = None created_by: str assigned_to: Optional[str] = None created_at: datetime updated_at: datetime class Config: from_attributes = True class TicketCloseRequest(BaseModel): resolution: Optional[str] = None # =================================== # TICKET ENDPOINTS # =================================== @router.post("/", response_model=TicketResponse, status_code=status.HTTP_201_CREATED) async def create_ticket( ticket: TicketCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Crear un nuevo ticket """ try: # Generar número de ticket único result = await db.execute( select(func.count(Ticket.id)).where(Ticket.tenant_id == current_user.tenant_id) ) count = result.scalar() or 0 ticket_number = f"TK-{count + 1:06d}" # Convertir IDs de string a UUID si son proporcionados category_uuid = uuid.UUID(ticket.category_id) if ticket.category_id else None system_uuid = uuid.UUID(ticket.system_id) if ticket.system_id else None db_ticket = Ticket( id=uuid.uuid4(), tenant_id=current_user.tenant_id, ticket_number=ticket_number, subject=ticket.subject, description=ticket.description, category_id=category_uuid, system_id=system_uuid, priority=TicketPriority[ticket.priority.upper()], created_by=current_user.id, status=TicketStatus.NEW, created_at=datetime.utcnow(), updated_at=datetime.utcnow() ) db.add(db_ticket) await db.commit() await db.refresh(db_ticket) # Convertir a respuesta return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, "subject": db_ticket.subject, "description": db_ticket.description, "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, "updated_at": db_ticket.updated_at } except ValueError as e: await db.rollback() raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid UUID format: {str(e)}" ) except Exception as e: await db.rollback() raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Error creating ticket: {str(e)}" ) @router.get("/", response_model=List[TicketResponse]) async def get_tickets( skip: int = 0, limit: int = 100, status_filter: Optional[str] = None, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Obtener tickets del usuario actual """ query = select(Ticket).where( Ticket.tenant_id == current_user.tenant_id, Ticket.created_by == current_user.id ) if status_filter: try: status_enum = TicketStatus[status_filter.upper()] query = query.where(Ticket.status == status_enum) except KeyError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid status: {status_filter}" ) query = query.order_by(Ticket.created_at.desc()).offset(skip).limit(limit) result = await db.execute(query) tickets = result.scalars().all() return [ { "id": str(t.id), "ticket_number": t.ticket_number, "subject": t.subject, "description": t.description, "status": t.status.value, "priority": t.priority.value, "category_id": str(t.category_id) if t.category_id else None, "system_id": str(t.system_id) if t.system_id else None, "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 } for t in tickets ] @router.get("/{ticket_id}", response_model=TicketResponse) async def get_ticket( ticket_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Obtener un ticket específico """ try: ticket_uuid = uuid.UUID(ticket_id) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid ticket ID format" ) query = select(Ticket).where( Ticket.id == ticket_uuid, Ticket.tenant_id == current_user.tenant_id, Ticket.created_by == current_user.id ) result = await db.execute(query) ticket = result.scalars().first() if not ticket: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Ticket {ticket_id} not found" ) return { "id": str(ticket.id), "ticket_number": ticket.ticket_number, "subject": 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, "system_id": str(ticket.system_id) if ticket.system_id else None, "created_by": str(ticket.created_by), "assigned_to": str(ticket.assigned_to) if ticket.assigned_to else None, "created_at": ticket.created_at, "updated_at": ticket.updated_at } @router.patch("/{ticket_id}", response_model=TicketResponse) async def update_ticket( ticket_id: str, ticket_update: TicketUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Actualizar un ticket """ try: ticket_uuid = uuid.UUID(ticket_id) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid ticket ID format" ) query = select(Ticket).where( Ticket.id == ticket_uuid, Ticket.tenant_id == current_user.tenant_id, Ticket.created_by == current_user.id ) result = await db.execute(query) db_ticket = result.scalars().first() if not db_ticket: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Ticket {ticket_id} not found" ) try: update_data = ticket_update.dict(exclude_unset=True) for field, value in update_data.items(): if field == "status" and value: setattr(db_ticket, field, TicketStatus[value.upper()]) elif field == "priority" and value: setattr(db_ticket, field, TicketPriority[value.upper()]) elif field == "assigned_to" and value: setattr(db_ticket, field, uuid.UUID(value)) else: setattr(db_ticket, field, value) db_ticket.updated_at = datetime.utcnow() await db.commit() await db.refresh(db_ticket) return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, "subject": db_ticket.subject, "description": db_ticket.description, "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, "updated_at": db_ticket.updated_at } except Exception as e: await db.rollback() raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Error updating ticket: {str(e)}" ) @router.patch("/{ticket_id}/close", response_model=TicketResponse) async def close_ticket( ticket_id: str, close_request: TicketCloseRequest, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Cerrar un ticket """ try: ticket_uuid = uuid.UUID(ticket_id) except ValueError: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid ticket ID format" ) query = select(Ticket).where( Ticket.id == ticket_uuid, Ticket.tenant_id == current_user.tenant_id, Ticket.created_by == current_user.id ) result = await db.execute(query) db_ticket = result.scalars().first() if not db_ticket: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Ticket {ticket_id} not found" ) try: db_ticket.status = TicketStatus.CLOSED db_ticket.updated_at = datetime.utcnow() await db.commit() await db.refresh(db_ticket) return { "id": str(db_ticket.id), "ticket_number": db_ticket.ticket_number, "subject": db_ticket.subject, "description": db_ticket.description, "status": db_ticket.status.value, "priority": db_ticket.priority.value, "category_id": str(db_ticket.category_id) if db_ticket.category_id else None, "system_id": str(db_ticket.system_id) if db_ticket.system_id else None, "created_by": str(db_ticket.created_by), "assigned_to": str(db_ticket.assigned_to) if db_ticket.assigned_to else None, "created_at": db_ticket.created_at, "updated_at": db_ticket.updated_at } except Exception as e: await db.rollback() raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Error closing ticket: {str(e)}" ) # =================================== # COMMENT ENDPOINTS (placeholder) # =================================== @router.get("/{ticket_id}/comments") async def get_ticket_comments( ticket_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Obtener comentarios de un ticket """ return [] @router.post("/{ticket_id}/comments", status_code=status.HTTP_201_CREATED) async def create_comment( ticket_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Agregar un comentario a un ticket """ raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Comments not yet implemented" ) # =================================== # ATTACHMENT ENDPOINTS (placeholder) # =================================== @router.get("/{ticket_id}/attachments") async def get_ticket_attachments( ticket_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Obtener adjuntos de un ticket """ return [] @router.post("/{ticket_id}/attachments", status_code=status.HTTP_201_CREATED) async def upload_attachment( ticket_id: str, file: UploadFile = File(...), db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """ Subir un archivo adjunto a un ticket """ raise HTTPException( status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="File uploads not yet implemented" )