Bckn funcion

This commit is contained in:
2026-03-17 11:09:30 -06:00
parent 5e58d53416
commit d691b71c44
17 changed files with 406 additions and 224 deletions

View File

@@ -7,6 +7,8 @@ from sqlalchemy.orm import selectinload
from typing import List, Optional
from datetime import datetime, timedelta
import uuid
from app.models.issue import TicketIssue, ticket_issue_tagged_users
from app.api.schemas.issue import IssueCreate, IssueResponse
from app.core.database import get_db
from app.api.deps import get_current_user, get_current_tenant
@@ -411,3 +413,179 @@ async def download_attachment(ticket_id: str, attachment_id: str, db: AsyncSessi
logger.info(f"Returning file: {attachment.original_filename}")
return FileResponse(path=file_path, filename=attachment.original_filename, media_type=attachment.mime_type)
@router.get("/{ticket_id}/issues", response_model=List[IssueResponse])
async def get_ticket_issues(
ticket_id: str,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Obtener asuntos de un ticket."""
ticket_uuid = validate_uuid_param(ticket_id, "ticket ID")
# Verificar que el ticket existe y pertenece al tenant
query = select(Ticket).where(
Ticket.id == ticket_uuid,
Ticket.tenant_id == current_user.tenant_id,
)
if current_user.role.is_client:
query = query.where(Ticket.created_by == current_user.id)
result = await db.execute(query)
ticket_obj = result.scalars().first()
if not ticket_obj:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ticket no encontrado")
issues_query = (
select(TicketIssue)
.where(TicketIssue.ticket_id == ticket_uuid)
.options(
selectinload(TicketIssue.created_by_user),
selectinload(TicketIssue.tagged_users),
)
.order_by(TicketIssue.created_at.desc())
)
result = await db.execute(issues_query)
issues = result.scalars().all()
return [
IssueResponse(
id=issue.id,
ticket_id=issue.ticket_id,
tenant_id=issue.tenant_id,
content=issue.content,
priority=issue.priority.value,
created_by=issue.created_by,
created_by_name=f"{issue.created_by_user.first_name} {issue.created_by_user.last_name}",
tagged_users=[
{"id": u.id, "full_name": f"{u.first_name} {u.last_name}", "email": u.email}
for u in issue.tagged_users
],
attachment_filename=issue.attachment_filename,
attachment_mime_type=issue.attachment_mime_type,
created_at=issue.created_at,
updated_at=issue.updated_at,
)
for issue in issues
]
@router.post("/{ticket_id}/issues", response_model=IssueResponse, status_code=status.HTTP_201_CREATED)
async def create_ticket_issue(
ticket_id: str,
issue: IssueCreate,
file: Optional[UploadFile] = File(None),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
current_tenant: Tenant = Depends(get_current_tenant),
):
"""
Crear un asunto de escalación en un ticket.
Solo puede crearlo el dueño del ticket o el CLIENT_ADMIN del tenant.
"""
ticket_uuid = validate_uuid_param(ticket_id, "ticket ID")
# 1. Verificar que el ticket existe y pertenece al tenant
result = await db.execute(
select(Ticket).where(
Ticket.id == ticket_uuid,
Ticket.tenant_id == current_user.tenant_id,
)
)
ticket_obj = result.scalars().first()
if not ticket_obj:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ticket no encontrado")
# 2. Verificar permisos: solo el creador del ticket o CLIENT_ADMIN
is_ticket_owner = ticket_obj.created_by == current_user.id
is_client_admin = current_user.role == UserRole.CLIENT_ADMIN
if not is_ticket_owner and not is_client_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Solo el creador del ticket o el administrador del tenant pueden crear asuntos",
)
# 3. Validar usuarios etiquetados — deben pertenecer al mismo tenant
tagged_users = []
if issue.tagged_user_ids:
result = await db.execute(
select(User).where(
User.id.in_(issue.tagged_user_ids),
User.tenant_id == current_user.tenant_id,
User.is_active == True,
)
)
tagged_users = result.scalars().all()
found_ids = {u.id for u in tagged_users}
missing = [str(uid) for uid in issue.tagged_user_ids if uid not in found_ids]
if missing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Usuarios no encontrados en el tenant: {missing}",
)
# 4. Manejar adjunto opcional
attachment_path = None
attachment_filename = None
attachment_mime_type = None
if file and file.filename:
file_metadata = await file_handler.save_upload(file, current_tenant.id, ticket_uuid)
attachment_path = file_metadata["file_path"]
attachment_filename = file_metadata["original_filename"]
attachment_mime_type = file_metadata["mime_type"]
# 5. Crear el asunto
new_issue = TicketIssue(
id=uuid.uuid4(),
ticket_id=ticket_uuid,
tenant_id=current_user.tenant_id,
created_by=current_user.id,
content=issue.content,
priority=TicketPriority[issue.priority],
attachment_path=attachment_path,
attachment_filename=attachment_filename,
attachment_mime_type=attachment_mime_type,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow(),
)
new_issue.tagged_users = tagged_users
db.add(new_issue)
await db.commit()
await db.refresh(new_issue, ["created_by_user", "tagged_users"])
# 6. Audit log
await safe_audit_log(
db=db,
tenant_id=current_user.tenant_id,
user_id=current_user.id,
action="ticket.issue.create",
resource_type="ticket_issue",
resource_id=new_issue.id,
new_values={
"ticket_id": str(ticket_uuid),
"priority": issue.priority,
"tagged_users": [str(uid) for uid in issue.tagged_user_ids],
},
)
return IssueResponse(
id=new_issue.id,
ticket_id=new_issue.ticket_id,
tenant_id=new_issue.tenant_id,
content=new_issue.content,
priority=new_issue.priority.value,
created_by=new_issue.created_by,
created_by_name=f"{new_issue.created_by_user.first_name} {new_issue.created_by_user.last_name}",
tagged_users=[
{"id": u.id, "full_name": f"{u.first_name} {u.last_name}", "email": u.email}
for u in new_issue.tagged_users
],
attachment_filename=new_issue.attachment_filename,
attachment_mime_type=new_issue.attachment_mime_type,
created_at=new_issue.created_at,
updated_at=new_issue.updated_at,
)