Bckn funcion
This commit is contained in:
@@ -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,
|
||||
)
|
||||
Reference in New Issue
Block a user