305 lines
10 KiB
Python
305 lines
10 KiB
Python
"""
|
|
PX Action Center views and viewsets
|
|
"""
|
|
|
|
from django.utils import timezone
|
|
from rest_framework import status, viewsets
|
|
from rest_framework.decorators import action
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
|
|
from apps.accounts.permissions import IsPXAdmin
|
|
from apps.core.services import AuditService
|
|
|
|
from .models import PXAction, PXActionAttachment, PXActionLog, PXActionSLAConfig, RoutingRule
|
|
from .serializers import (
|
|
PXActionAttachmentSerializer,
|
|
PXActionListSerializer,
|
|
PXActionLogSerializer,
|
|
PXActionSerializer,
|
|
PXActionSLAConfigSerializer,
|
|
RoutingRuleSerializer,
|
|
)
|
|
|
|
|
|
class PXActionViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
ViewSet for PX Actions with workflow actions.
|
|
|
|
Permissions:
|
|
- All authenticated users can view actions
|
|
- PX Admins and PX Coordinators can create/manage actions
|
|
"""
|
|
|
|
queryset = PXAction.objects.all()
|
|
permission_classes = [IsAuthenticated]
|
|
filterset_fields = [
|
|
"status",
|
|
"source_type",
|
|
"severity",
|
|
"priority",
|
|
"category",
|
|
"hospital",
|
|
"department",
|
|
"assigned_to",
|
|
"is_overdue",
|
|
"requires_approval",
|
|
]
|
|
search_fields = ["title", "description"]
|
|
ordering_fields = ["created_at", "due_at", "severity", "escalation_level"]
|
|
ordering = ["-created_at"]
|
|
|
|
def get_serializer_class(self):
|
|
"""Use simplified serializer for list view"""
|
|
if self.action == "list":
|
|
return PXActionListSerializer
|
|
return PXActionSerializer
|
|
|
|
def get_queryset(self):
|
|
"""Filter actions based on user role"""
|
|
queryset = (
|
|
super()
|
|
.get_queryset()
|
|
.select_related("hospital", "department", "assigned_to", "approved_by", "closed_by", "content_type")
|
|
.prefetch_related("logs", "attachments")
|
|
)
|
|
|
|
user = self.request.user
|
|
|
|
# Source Users don't have access to PX Actions
|
|
if user.is_source_user():
|
|
return queryset.none()
|
|
|
|
if user.is_px_admin():
|
|
if hasattr(self.request, "tenant_hospital") and self.request.tenant_hospital:
|
|
return queryset.filter(hospital=self.request.tenant_hospital)
|
|
return queryset
|
|
|
|
# Hospital Admins see actions for their hospital
|
|
if user.is_hospital_admin() and user.hospital:
|
|
return queryset.filter(hospital=user.hospital)
|
|
|
|
# Department Managers see actions for their department
|
|
if user.is_department_manager() and user.department:
|
|
return queryset.filter(department=user.department)
|
|
|
|
# Others see actions for their hospital
|
|
if user.hospital:
|
|
return queryset.filter(hospital=user.hospital)
|
|
|
|
return queryset.none()
|
|
|
|
def perform_create(self, serializer):
|
|
"""Log action creation"""
|
|
action = serializer.save()
|
|
|
|
from apps.px_action_center.services import RoutingRuleEvaluationService
|
|
|
|
RoutingRuleEvaluationService.evaluate_and_route(action)
|
|
|
|
AuditService.log_from_request(
|
|
event_type="action_created",
|
|
description=f"PX Action created: {action.title}",
|
|
request=self.request,
|
|
content_object=action,
|
|
metadata={"source_type": action.source_type, "category": action.category, "severity": action.severity},
|
|
)
|
|
|
|
@action(detail=True, methods=["post"])
|
|
def assign(self, request, pk=None):
|
|
"""Assign action to user"""
|
|
action = self.get_object()
|
|
user_id = request.data.get("user_id")
|
|
|
|
if not user_id:
|
|
return Response({"error": "user_id is required"}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
from apps.accounts.models import User
|
|
|
|
try:
|
|
user = User.objects.get(id=user_id)
|
|
action.assigned_to = user
|
|
action.assigned_at = timezone.now()
|
|
action.save(update_fields=["assigned_to", "assigned_at"])
|
|
|
|
# Create log
|
|
PXActionLog.objects.create(
|
|
action=action,
|
|
log_type="assignment",
|
|
message=f"Assigned to {user.get_full_name()}",
|
|
created_by=request.user,
|
|
)
|
|
|
|
AuditService.log_from_request(
|
|
event_type="assignment",
|
|
description=f"Action assigned to {user.get_full_name()}",
|
|
request=request,
|
|
content_object=action,
|
|
)
|
|
|
|
return Response({"message": "Action assigned successfully"})
|
|
except User.DoesNotExist:
|
|
return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
|
|
|
@action(detail=True, methods=["post"])
|
|
def change_status(self, request, pk=None):
|
|
"""Change action status"""
|
|
action = self.get_object()
|
|
new_status = request.data.get("status")
|
|
note = request.data.get("note", "")
|
|
|
|
if not new_status:
|
|
return Response({"error": "status is required"}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
old_status = action.status
|
|
action.status = new_status
|
|
|
|
# Handle status-specific logic
|
|
if new_status == "pending_approval":
|
|
# Check if evidence is required
|
|
if action.requires_approval:
|
|
evidence_count = action.attachments.filter(is_evidence=True).count()
|
|
if evidence_count == 0:
|
|
return Response(
|
|
{"error": "Evidence is required before requesting approval"}, status=status.HTTP_400_BAD_REQUEST
|
|
)
|
|
|
|
elif new_status == "approved":
|
|
# Only PX Admins can approve
|
|
if not request.user.is_px_admin():
|
|
return Response({"error": "Only PX Admins can approve actions"}, status=status.HTTP_403_FORBIDDEN)
|
|
action.approved_by = request.user
|
|
action.approved_at = timezone.now()
|
|
|
|
elif new_status == "closed":
|
|
action.closed_at = timezone.now()
|
|
action.closed_by = request.user
|
|
|
|
action.save()
|
|
|
|
# Create log
|
|
PXActionLog.objects.create(
|
|
action=action,
|
|
log_type="status_change",
|
|
message=note or f"Status changed from {old_status} to {new_status}",
|
|
created_by=request.user,
|
|
old_status=old_status,
|
|
new_status=new_status,
|
|
)
|
|
|
|
AuditService.log_from_request(
|
|
event_type="status_change",
|
|
description=f"Action status changed from {old_status} to {new_status}",
|
|
request=request,
|
|
content_object=action,
|
|
metadata={"old_status": old_status, "new_status": new_status},
|
|
)
|
|
|
|
return Response({"message": "Status updated successfully"})
|
|
|
|
@action(detail=True, methods=["post"])
|
|
def add_note(self, request, pk=None):
|
|
"""Add note to action"""
|
|
action = self.get_object()
|
|
note = request.data.get("note")
|
|
|
|
if not note:
|
|
return Response({"error": "note is required"}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
# Create log
|
|
log = PXActionLog.objects.create(action=action, log_type="note", message=note, created_by=request.user)
|
|
|
|
serializer = PXActionLogSerializer(log)
|
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
|
|
@action(detail=True, methods=["post"])
|
|
def escalate(self, request, pk=None):
|
|
"""Manually escalate action"""
|
|
action = self.get_object()
|
|
|
|
# Queue escalation task
|
|
from apps.px_action_center.tasks import escalate_action
|
|
|
|
escalate_action.delay(str(action.id))
|
|
|
|
return Response({"message": "Action queued for escalation"})
|
|
|
|
@action(detail=True, methods=["post"])
|
|
def reject(self, request, pk=None):
|
|
"""Reject action and reopen with reason"""
|
|
action = self.get_object()
|
|
reason = request.data.get("reason")
|
|
|
|
if not reason:
|
|
return Response({"error": "reason is required"}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
if action.status not in ["pending_approval", "approved"]:
|
|
return Response(
|
|
{"error": "Can only reject actions in pending_approval or approved status"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
old_status = action.status
|
|
action.status = "open"
|
|
action.rejected_at = timezone.now()
|
|
action.rejected_by = request.user
|
|
action.rejection_reason = reason
|
|
action.save()
|
|
|
|
PXActionLog.objects.create(
|
|
action=action,
|
|
log_type="rejection",
|
|
message=f"Action rejected: {reason}",
|
|
created_by=request.user,
|
|
old_status=old_status,
|
|
new_status="open",
|
|
)
|
|
|
|
AuditService.log_from_request(
|
|
event_type="action_rejected",
|
|
description=f"Action rejected and reopened: {action.title}",
|
|
request=request,
|
|
content_object=action,
|
|
metadata={"old_status": old_status, "reason": reason},
|
|
)
|
|
|
|
return Response({"message": "Action rejected and reopened successfully"})
|
|
|
|
|
|
class PXActionSLAConfigViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
ViewSet for PX Action SLA Configurations.
|
|
|
|
Permissions:
|
|
- Only PX Admins can manage SLA configurations
|
|
"""
|
|
|
|
queryset = PXActionSLAConfig.objects.all()
|
|
serializer_class = PXActionSLAConfigSerializer
|
|
permission_classes = [IsPXAdmin]
|
|
filterset_fields = ["hospital", "department", "is_active"]
|
|
search_fields = ["name"]
|
|
ordering = ["hospital", "name"]
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().select_related("hospital", "department")
|
|
|
|
|
|
class RoutingRuleViewSet(viewsets.ModelViewSet):
|
|
"""
|
|
ViewSet for Routing Rules.
|
|
|
|
Permissions:
|
|
- Only PX Admins can manage routing rules
|
|
"""
|
|
|
|
queryset = RoutingRule.objects.all()
|
|
serializer_class = RoutingRuleSerializer
|
|
permission_classes = [IsPXAdmin]
|
|
filterset_fields = ["source_type", "severity", "hospital", "is_active"]
|
|
search_fields = ["name", "description"]
|
|
ordering = ["-priority", "name"]
|
|
|
|
def get_queryset(self):
|
|
return super().get_queryset().select_related("hospital", "department", "assign_to_user", "assign_to_department")
|