""" Complaints 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.core.services import AuditService from .models import Complaint, ComplaintAttachment, ComplaintUpdate, Inquiry from .serializers import ( ComplaintAttachmentSerializer, ComplaintListSerializer, ComplaintSerializer, ComplaintUpdateSerializer, InquirySerializer, ) class ComplaintViewSet(viewsets.ModelViewSet): """ ViewSet for Complaints with workflow actions. Permissions: - All authenticated users can view complaints - PX Admins and Hospital Admins can create/manage complaints """ queryset = Complaint.objects.all() permission_classes = [IsAuthenticated] filterset_fields = [ 'status', 'severity', 'priority', 'category', 'source', 'hospital', 'department', 'physician', 'assigned_to', 'is_overdue' ] search_fields = ['title', 'description', 'patient__mrn', 'patient__first_name', 'patient__last_name'] ordering_fields = ['created_at', 'due_at', 'severity'] ordering = ['-created_at'] def get_serializer_class(self): """Use simplified serializer for list view""" if self.action == 'list': return ComplaintListSerializer return ComplaintSerializer def get_queryset(self): """Filter complaints based on user role""" queryset = super().get_queryset().select_related( 'patient', 'hospital', 'department', 'physician', 'assigned_to', 'resolved_by', 'closed_by' ).prefetch_related('attachments', 'updates') user = self.request.user # PX Admins see all complaints if user.is_px_admin(): return queryset # Hospital Admins see complaints for their hospital if user.is_hospital_admin() and user.hospital: return queryset.filter(hospital=user.hospital) # Department Managers see complaints for their department if user.is_department_manager() and user.department: return queryset.filter(department=user.department) # Others see complaints for their hospital if user.hospital: return queryset.filter(hospital=user.hospital) return queryset.none() def perform_create(self, serializer): """Log complaint creation and trigger resolution satisfaction survey""" complaint = serializer.save() AuditService.log_from_request( event_type='complaint_created', description=f"Complaint created: {complaint.title}", request=self.request, content_object=complaint, metadata={ 'category': complaint.category, 'severity': complaint.severity, 'patient_mrn': complaint.patient.mrn } ) # TODO: Optionally create PX Action (Phase 6) # from apps.complaints.tasks import create_action_from_complaint # create_action_from_complaint.delay(str(complaint.id)) @action(detail=True, methods=['post']) def assign(self, request, pk=None): """Assign complaint to user""" complaint = 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) complaint.assigned_to = user complaint.assigned_at = timezone.now() complaint.save(update_fields=['assigned_to', 'assigned_at']) # Create update ComplaintUpdate.objects.create( complaint=complaint, update_type='assignment', message=f"Assigned to {user.get_full_name()}", created_by=request.user ) AuditService.log_from_request( event_type='assignment', description=f"Complaint assigned to {user.get_full_name()}", request=request, content_object=complaint ) return Response({'message': 'Complaint 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 complaint status""" complaint = 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 = complaint.status complaint.status = new_status # Handle status-specific logic if new_status == 'resolved': complaint.resolved_at = timezone.now() complaint.resolved_by = request.user elif new_status == 'closed': complaint.closed_at = timezone.now() complaint.closed_by = request.user # Trigger resolution satisfaction survey from apps.complaints.tasks import send_complaint_resolution_survey send_complaint_resolution_survey.delay(str(complaint.id)) complaint.save() # Create update ComplaintUpdate.objects.create( complaint=complaint, update_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"Complaint status changed from {old_status} to {new_status}", request=request, content_object=complaint, 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 complaint""" complaint = self.get_object() note = request.data.get('note') if not note: return Response( {'error': 'note is required'}, status=status.HTTP_400_BAD_REQUEST ) # Create update update = ComplaintUpdate.objects.create( complaint=complaint, update_type='note', message=note, created_by=request.user ) serializer = ComplaintUpdateSerializer(update) return Response(serializer.data, status=status.HTTP_201_CREATED) class ComplaintAttachmentViewSet(viewsets.ModelViewSet): """ViewSet for Complaint Attachments""" queryset = ComplaintAttachment.objects.all() serializer_class = ComplaintAttachmentSerializer permission_classes = [IsAuthenticated] filterset_fields = ['complaint'] ordering = ['-created_at'] def get_queryset(self): queryset = super().get_queryset().select_related('complaint', 'uploaded_by') user = self.request.user # Filter based on complaint access if user.is_px_admin(): return queryset if user.is_hospital_admin() and user.hospital: return queryset.filter(complaint__hospital=user.hospital) if user.hospital: return queryset.filter(complaint__hospital=user.hospital) return queryset.none() class InquiryViewSet(viewsets.ModelViewSet): """ViewSet for Inquiries""" queryset = Inquiry.objects.all() serializer_class = InquirySerializer permission_classes = [IsAuthenticated] filterset_fields = ['status', 'category', 'hospital', 'department', 'assigned_to'] search_fields = ['subject', 'message', 'contact_name', 'patient__mrn'] ordering_fields = ['created_at'] ordering = ['-created_at'] def get_queryset(self): """Filter inquiries based on user role""" queryset = super().get_queryset().select_related( 'patient', 'hospital', 'department', 'assigned_to', 'responded_by' ) user = self.request.user # PX Admins see all inquiries if user.is_px_admin(): return queryset # Hospital Admins see inquiries for their hospital if user.is_hospital_admin() and user.hospital: return queryset.filter(hospital=user.hospital) # Department Managers see inquiries for their department if user.is_department_manager() and user.department: return queryset.filter(department=user.department) # Others see inquiries for their hospital if user.hospital: return queryset.filter(hospital=user.hospital) return queryset.none() @action(detail=True, methods=['post']) def respond(self, request, pk=None): """Respond to inquiry""" inquiry = self.get_object() response_text = request.data.get('response') if not response_text: return Response( {'error': 'response is required'}, status=status.HTTP_400_BAD_REQUEST ) inquiry.response = response_text inquiry.responded_at = timezone.now() inquiry.responded_by = request.user inquiry.status = 'resolved' inquiry.save() return Response({'message': 'Response submitted successfully'})