2025-08-12 13:33:25 +03:00

599 lines
24 KiB
Python

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)