from django.db import transaction from django.utils import timezone from ninja import Router from apps.accounts.api import require_permission from apps.solutions.models import Solution, TicketSolutionLink from apps.solutions.schemas import ( AdoptSolutionIn, SolutionIn, SolutionOut, TicketSolutionLinkIn, TicketSolutionLinkOut, TicketSolutionOut, ) from apps.tickets.models import Ticket, TicketEvent router = Router(tags=["solutions"]) @router.post("", response=SolutionOut) def create_solution(request, payload: SolutionIn): require_permission(request, "solution.manage") solution = Solution.objects.create( title=payload.title, problem_signature=payload.problem_signature, related_system=payload.related_system, environment_scope=payload.environment_scope, root_cause=payload.root_cause, solution=payload.solution, risk_note=payload.risk_note, status=payload.status, created_by=request.user if request.user.is_authenticated else None, ) if solution.status == "published": from apps.knowledge.services import index_published_solution index_published_solution(solution) return solution @router.get("", response=list[SolutionOut]) def list_solutions(request, status: str | None = None): require_permission(request, "solution.view") queryset = Solution.objects.all() if status: queryset = queryset.filter(status=status) return queryset[:100] @router.get("/tickets/{ticket_id}", response=list[TicketSolutionOut]) def list_ticket_solutions(request, ticket_id: int): require_permission(request, "solution.view") queryset = TicketSolutionLink.objects.select_related("solution", "ticket").filter( ticket_id=ticket_id ) return [ { "id": link.id, "ticket_id": link.ticket_id, "solution_id": link.solution_id, "link_type": link.link_type, "solution": link.solution, "created_at": link.created_at, } for link in queryset ] @router.get("/{solution_id}", response=SolutionOut) def get_solution(request, solution_id: int): require_permission(request, "solution.view") return Solution.objects.get(id=solution_id) @router.post("/{solution_id}/link-ticket", response=TicketSolutionLinkOut) def link_solution_to_ticket(request, solution_id: int, payload: TicketSolutionLinkIn): require_permission(request, "solution.manage") solution = Solution.objects.get(id=solution_id) ticket = Ticket.objects.get(id=payload.ticket_id) link, _ = TicketSolutionLink.objects.get_or_create( ticket=ticket, solution=solution, link_type=payload.link_type, defaults={ "agent_run_id": payload.agent_run_id, "created_by": request.user if request.user.is_authenticated else None, }, ) if payload.link_type == "adopted": solution.usage_count = solution.ticket_links.filter(link_type="adopted").count() solution.save(update_fields=["usage_count", "updated_at"]) return { "id": link.id, "ticket_id": ticket.id, "solution_id": solution.id, "link_type": link.link_type, "solution_usage_count": solution.usage_count, "created_at": link.created_at, } @router.post("/{solution_id}/adopt-ticket", response=TicketSolutionLinkOut) def adopt_solution_and_close_ticket(request, solution_id: int, payload: AdoptSolutionIn): require_permission(request, "solution.manage") operator = request.user if request.user.is_authenticated else None with transaction.atomic(): solution = Solution.objects.select_for_update().get(id=solution_id) ticket = Ticket.objects.select_for_update().get(id=payload.ticket_id) link, _ = TicketSolutionLink.objects.get_or_create( ticket=ticket, solution=solution, link_type="adopted", defaults={"created_by": operator}, ) now = timezone.now() from_status = ticket.status ticket.status = "closed" ticket.resolution_summary = solution.solution ticket.is_resolved = True ticket.replied_to_customer = payload.replied_to_customer ticket.resolved_at = ticket.resolved_at or now ticket.closed_at = now ticket.save( update_fields=[ "status", "resolution_summary", "is_resolved", "replied_to_customer", "resolved_at", "closed_at", "updated_at", ] ) solution.usage_count = solution.ticket_links.filter(link_type="adopted").count() solution.save(update_fields=["usage_count", "updated_at"]) TicketEvent.objects.create( ticket=ticket, event_type="solution_adopted", from_value={"status": from_status}, to_value={ "status": ticket.status, "solution_id": solution.id, "solution_title": solution.title, "resolution": ticket.resolution_summary, "is_resolved": ticket.is_resolved, "replied_to_customer": ticket.replied_to_customer, }, operator=operator, remark=payload.remark, ) return { "id": link.id, "ticket_id": ticket.id, "solution_id": solution.id, "link_type": link.link_type, "solution_usage_count": solution.usage_count, "created_at": link.created_at, }