from rest_framework import viewsets, permissions, status from rest_framework.decorators import action from rest_framework.response import Response from django_filters.rest_framework import DjangoFilterBackend from rest_framework import filters from django.db.models import Q, Count, Avg, Sum from django.utils import timezone from datetime import timedelta from ..models import ( OperatingRoom, ORBlock, SurgicalCase, SurgicalNote, EquipmentUsage, SurgicalNoteTemplate ) from .serializers import ( OperatingRoomSerializer, ORBlockSerializer, SurgicalCaseSerializer, SurgicalNoteSerializer, EquipmentUsageSerializer, SurgicalNoteTemplateSerializer, ORStatsSerializer, CaseSchedulingSerializer, CaseStartSerializer, CaseCompletionSerializer, SurgicalNoteDictationSerializer, EquipmentTrackingSerializer ) from core.utils import AuditLogger class BaseViewSet(viewsets.ModelViewSet): """Base ViewSet with common functionality""" permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] def get_queryset(self): # Filter by tenant if user has one if hasattr(self.request.user, 'tenant') and self.request.user.tenant: return self.queryset.filter(tenant=self.request.user.tenant) return self.queryset def perform_create(self, serializer): if hasattr(self.request.user, 'tenant'): serializer.save(tenant=self.request.user.tenant) else: serializer.save() class OperatingRoomViewSet(BaseViewSet): """ViewSet for OperatingRoom model""" queryset = OperatingRoom.objects.all() serializer_class = OperatingRoomSerializer filterset_fields = ['room_type', 'status', 'floor', 'building', 'is_active'] search_fields = ['room_number', 'name', 'equipment_list', 'capabilities'] ordering_fields = ['room_number', 'name', 'capacity'] ordering = ['room_number'] @action(detail=False, methods=['get']) def available(self, request): """Get available operating rooms""" queryset = self.get_queryset().filter(status='AVAILABLE', is_active=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def maintenance_due(self, request): """Get rooms due for maintenance""" today = timezone.now().date() queryset = self.get_queryset().filter( next_maintenance__lte=today, is_active=True ) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def set_status(self, request, pk=None): """Set operating room status""" room = self.get_object() new_status = request.data.get('status') reason = request.data.get('reason', '') if not new_status: return Response( {'error': 'Status is required'}, status=status.HTTP_400_BAD_REQUEST ) old_status = room.status room.status = new_status room.save() # Log the action AuditLogger.log_action( user=request.user, action='ROOM_STATUS_CHANGED', model='OperatingRoom', object_id=str(room.room_id), details={ 'room_number': room.room_number, 'old_status': old_status, 'new_status': new_status, 'reason': reason } ) return Response({'message': 'Room status updated successfully'}) class ORBlockViewSet(BaseViewSet): """ViewSet for ORBlock model""" queryset = ORBlock.objects.all() serializer_class = ORBlockSerializer filterset_fields = [ 'operating_room', 'primary_surgeon', 'date', 'block_type', 'specialty', 'is_emergency_block' ] search_fields = ['primary_surgeon__first_name', 'primary_surgeon__last_name', 'specialty'] ordering_fields = ['date', 'start_time'] ordering = ['date', 'start_time'] @action(detail=False, methods=['get']) def today(self, request): """Get today's OR blocks""" today = timezone.now().date() queryset = self.get_queryset().filter(date=today) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def by_surgeon(self, request): """Get blocks by surgeon""" surgeon_id = request.query_params.get('surgeon_id') if surgeon_id: queryset = self.get_queryset().filter(primary_surgeon_id=surgeon_id) else: queryset = self.get_queryset() serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class SurgicalCaseViewSet(BaseViewSet): """ViewSet for SurgicalCase model""" queryset = SurgicalCase.objects.all() serializer_class = SurgicalCaseSerializer filterset_fields = [ 'status', 'priority', 'case_type', 'patient', 'primary_surgeon', 'operating_room', 'scheduled_date' ] search_fields = [ 'case_number', 'patient__first_name', 'patient__last_name', 'patient__mrn', 'procedure_name', 'primary_surgeon__first_name', 'primary_surgeon__last_name' ] ordering_fields = ['scheduled_date', 'scheduled_start_time', 'priority'] ordering = ['scheduled_date', 'scheduled_start_time'] @action(detail=False, methods=['post']) def schedule(self, request): """Schedule a surgical case""" serializer = CaseSchedulingSerializer(data=request.data) if serializer.is_valid(): # Create surgical case case = SurgicalCase.objects.create( patient_id=serializer.validated_data['patient_id'], primary_surgeon_id=serializer.validated_data['primary_surgeon_id'], operating_room_id=serializer.validated_data['operating_room_id'], scheduled_date=serializer.validated_data['scheduled_date'], scheduled_start_time=serializer.validated_data['scheduled_start_time'], procedure_name=serializer.validated_data['procedure_name'], procedure_codes=serializer.validated_data['procedure_codes'], case_type=serializer.validated_data['case_type'], priority=serializer.validated_data['priority'], anesthesia_type=serializer.validated_data['anesthesia_type'], special_equipment=serializer.validated_data.get('special_equipment', ''), notes=serializer.validated_data.get('notes', ''), status='SCHEDULED', tenant=getattr(request.user, 'tenant', None) ) # Calculate estimated end time estimated_duration = serializer.validated_data['estimated_duration'] start_datetime = timezone.datetime.combine( case.scheduled_date, case.scheduled_start_time ) end_datetime = start_datetime + timedelta(minutes=estimated_duration) case.scheduled_end_time = end_datetime.time() case.save() # Log the action AuditLogger.log_action( user=request.user, action='CASE_SCHEDULED', model='SurgicalCase', object_id=str(case.case_id), details={ 'case_number': case.case_number, 'procedure': case.procedure_name, 'scheduled_date': str(case.scheduled_date) } ) return Response({ 'message': 'Case scheduled successfully', 'case': SurgicalCaseSerializer(case).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['get']) def today(self, request): """Get today's surgical cases""" today = timezone.now().date() queryset = self.get_queryset().filter(scheduled_date=today) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def emergency(self, request): """Get emergency cases""" queryset = self.get_queryset().filter(priority='EMERGENCY') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def start(self, request, pk=None): """Start a surgical case""" case = self.get_object() serializer = CaseStartSerializer(data=request.data) if serializer.is_valid(): case.status = 'IN_PROGRESS' case.actual_start_time = timezone.now() # Update staff assignments if serializer.validated_data.get('anesthesiologist_id'): case.anesthesiologist_id = serializer.validated_data['anesthesiologist_id'] if serializer.validated_data.get('scrub_nurse_id'): case.scrub_nurse_id = serializer.validated_data['scrub_nurse_id'] if serializer.validated_data.get('circulating_nurse_id'): case.circulating_nurse_id = serializer.validated_data['circulating_nurse_id'] case.position = serializer.validated_data.get('position', '') case.notes = serializer.validated_data.get('notes', '') case.save() # Update room status case.operating_room.status = 'OCCUPIED' case.operating_room.save() # Log the action AuditLogger.log_action( user=request.user, action='CASE_STARTED', model='SurgicalCase', object_id=str(case.case_id), details={ 'case_number': case.case_number, 'procedure': case.procedure_name } ) return Response({'message': 'Case started successfully'}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post']) def complete(self, request, pk=None): """Complete a surgical case""" case = self.get_object() serializer = CaseCompletionSerializer(data=request.data) if serializer.is_valid(): case.status = 'COMPLETED' case.actual_end_time = timezone.now() case.estimated_blood_loss = serializer.validated_data.get('estimated_blood_loss') case.complications = serializer.validated_data.get('complications', '') case.postop_destination = serializer.validated_data.get('postop_destination', '') case.notes = serializer.validated_data.get('notes', '') case.save() # Update room status case.operating_room.status = 'CLEANING' case.operating_room.save() # Log the action AuditLogger.log_action( user=request.user, action='CASE_COMPLETED', model='SurgicalCase', object_id=str(case.case_id), details={ 'case_number': case.case_number, 'procedure': case.procedure_name, 'duration_minutes': case.duration_minutes } ) return Response({'message': 'Case completed successfully'}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=True, methods=['post']) def cancel(self, request, pk=None): """Cancel a surgical case""" case = self.get_object() reason = request.data.get('reason', '') case.status = 'CANCELLED' case.notes = f"Cancelled: {reason}" case.save() # Log the action AuditLogger.log_action( user=request.user, action='CASE_CANCELLED', model='SurgicalCase', object_id=str(case.case_id), details={ 'case_number': case.case_number, 'reason': reason } ) return Response({'message': 'Case cancelled successfully'}) class SurgicalNoteTemplateViewSet(BaseViewSet): """ViewSet for SurgicalNoteTemplate model""" queryset = SurgicalNoteTemplate.objects.all() serializer_class = SurgicalNoteTemplateSerializer filterset_fields = ['procedure_type', 'specialty', 'is_active'] search_fields = ['name', 'description', 'template_content'] ordering_fields = ['name', 'procedure_type'] ordering = ['name'] @action(detail=False, methods=['get']) def by_procedure(self, request): """Get templates by procedure type""" procedure_type = request.query_params.get('procedure_type') if procedure_type: queryset = self.get_queryset().filter( procedure_type=procedure_type, is_active=True ) else: queryset = self.get_queryset().filter(is_active=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class SurgicalNoteViewSet(BaseViewSet): """ViewSet for SurgicalNote model""" queryset = SurgicalNote.objects.all() serializer_class = SurgicalNoteSerializer filterset_fields = [ 'surgical_case', 'surgeon', 'note_type', 'is_signed' ] search_fields = [ 'surgical_case__case_number', 'preoperative_diagnosis', 'postoperative_diagnosis', 'procedure_performed' ] ordering_fields = ['dictated_date', 'signed_date'] ordering = ['-dictated_date'] @action(detail=False, methods=['post']) def dictate(self, request): """Dictate a surgical note""" serializer = SurgicalNoteDictationSerializer(data=request.data) if serializer.is_valid(): case = SurgicalCase.objects.get(id=serializer.validated_data['case_id']) template_id = serializer.validated_data.get('template_id') template = None if template_id: template = SurgicalNoteTemplate.objects.get(id=template_id) # Create surgical note note = SurgicalNote.objects.create( surgical_case=case, template=template, surgeon=request.user, note_type=serializer.validated_data['note_type'], preoperative_diagnosis=serializer.validated_data['preoperative_diagnosis'], postoperative_diagnosis=serializer.validated_data['postoperative_diagnosis'], procedure_performed=serializer.validated_data['procedure_performed'], indications=serializer.validated_data['indications'], findings=serializer.validated_data['findings'], technique=serializer.validated_data['technique'], complications=serializer.validated_data.get('complications', ''), estimated_blood_loss=serializer.validated_data.get('estimated_blood_loss'), specimens=serializer.validated_data.get('specimens', ''), implants=serializer.validated_data.get('implants', ''), drains=serializer.validated_data.get('drains', ''), closure=serializer.validated_data['closure'], postoperative_plan=serializer.validated_data['postoperative_plan'], dictated_date=timezone.now(), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='SURGICAL_NOTE_DICTATED', model='SurgicalNote', object_id=str(note.note_id), details={ 'case_number': case.case_number, 'note_type': note.note_type } ) return Response({ 'message': 'Surgical note dictated successfully', 'note': SurgicalNoteSerializer(note).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['get']) def pending_signature(self, request): """Get notes pending signature""" queryset = self.get_queryset().filter(is_signed=False) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def sign(self, request, pk=None): """Sign a surgical note""" note = self.get_object() note.is_signed = True note.signed_date = timezone.now() note.electronic_signature = f"Electronically signed by {request.user.get_full_name()}" note.save() # Log the action AuditLogger.log_action( user=request.user, action='SURGICAL_NOTE_SIGNED', model='SurgicalNote', object_id=str(note.note_id), details={ 'case_number': note.surgical_case.case_number, 'note_type': note.note_type } ) return Response({'message': 'Surgical note signed successfully'}) class EquipmentUsageViewSet(BaseViewSet): """ViewSet for EquipmentUsage model""" queryset = EquipmentUsage.objects.all() serializer_class = EquipmentUsageSerializer filterset_fields = [ 'surgical_case', 'equipment_name', 'used_by', 'status', 'maintenance_required' ] search_fields = ['equipment_name', 'equipment_id', 'serial_number'] ordering_fields = ['start_time', 'end_time'] ordering = ['-start_time'] @action(detail=False, methods=['post']) def track_usage(self, request): """Track equipment usage""" serializer = EquipmentTrackingSerializer(data=request.data) if serializer.is_valid(): case = SurgicalCase.objects.get(id=serializer.validated_data['case_id']) # Create equipment usage record usage = EquipmentUsage.objects.create( surgical_case=case, equipment_name=serializer.validated_data['equipment_name'], equipment_id=serializer.validated_data['equipment_id'], serial_number=serializer.validated_data.get('serial_number', ''), start_time=serializer.validated_data['start_time'], end_time=serializer.validated_data.get('end_time'), used_by=request.user, maintenance_required=serializer.validated_data.get('maintenance_required', False), issues_reported=serializer.validated_data.get('issues_reported', ''), notes=serializer.validated_data.get('notes', ''), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='EQUIPMENT_USAGE_TRACKED', model='EquipmentUsage', object_id=str(usage.usage_id), details={ 'equipment_name': usage.equipment_name, 'case_number': case.case_number } ) return Response({ 'message': 'Equipment usage tracked successfully', 'usage': EquipmentUsageSerializer(usage).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['get']) def maintenance_required(self, request): """Get equipment requiring maintenance""" queryset = self.get_queryset().filter(maintenance_required=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class ORStatsViewSet(viewsets.ViewSet): """ViewSet for operating room statistics""" permission_classes = [permissions.IsAuthenticated] @action(detail=False, methods=['get']) def dashboard(self, request): """Get OR dashboard statistics""" tenant_filter = {} if hasattr(request.user, 'tenant') and request.user.tenant: tenant_filter['tenant'] = request.user.tenant today = timezone.now().date() # Case statistics cases = SurgicalCase.objects.filter(**tenant_filter) total_cases = cases.count() cases_today = cases.filter(scheduled_date=today).count() emergency_cases = cases.filter( priority='EMERGENCY', scheduled_date=today ).count() completed_cases = cases.filter( status='COMPLETED', scheduled_date=today ).count() cancelled_cases = cases.filter( status='CANCELLED', scheduled_date=today ).count() # Average duration completed_today = cases.filter( status='COMPLETED', actual_end_time__date=today ) durations = [case.duration_minutes for case in completed_today if case.duration_minutes] average_duration = sum(durations) / len(durations) if durations else 0 # Room utilization (mock data for now) rooms = OperatingRoom.objects.filter(**tenant_filter, is_active=True) room_utilization = {} for room in rooms: # Calculate utilization based on scheduled cases room_cases = cases.filter( operating_room=room, scheduled_date=today, status__in=['SCHEDULED', 'IN_PROGRESS', 'COMPLETED'] ) total_minutes = sum([ case.duration_minutes or 120 # default 2 hours for case in room_cases ]) # Assume 8-hour working day (480 minutes) utilization = min((total_minutes / 480) * 100, 100) room_utilization[room.room_number] = round(utilization, 1) # Surgeon productivity (top 5) surgeon_productivity = list( cases.filter(scheduled_date=today, status='COMPLETED') .values('primary_surgeon__first_name', 'primary_surgeon__last_name') .annotate(cases_count=Count('id')) .order_by('-cases_count')[:5] ) # Case types breakdown case_types = cases.filter(scheduled_date=today).values('case_type').annotate( count=Count('id') ).order_by('-count') # Status breakdown status_breakdown = cases.filter(scheduled_date=today).values('status').annotate( count=Count('id') ).order_by('-count') stats = { 'total_cases': total_cases, 'cases_today': cases_today, 'emergency_cases': emergency_cases, 'completed_cases': completed_cases, 'cancelled_cases': cancelled_cases, 'average_duration': round(average_duration, 1), 'room_utilization': room_utilization, 'surgeon_productivity': surgeon_productivity, 'case_types': {item['case_type']: item['count'] for item in case_types}, 'status_breakdown': {item['status']: item['count'] for item in status_breakdown} } serializer = ORStatsSerializer(stats) return Response(serializer.data)