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

676 lines
20 KiB
Python

"""
Operating Theatre app admin configuration.
"""
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from django.utils.safestring import mark_safe
from .models import (
OperatingRoom, ORBlock, SurgicalCase,
SurgicalNote, EquipmentUsage, SurgicalNoteTemplate
)
class ORBlockInline(admin.TabularInline):
"""
Inline admin for OR blocks.
"""
model = ORBlock
extra = 0
fields = [
'date', 'start_time', 'end_time', 'block_type',
'primary_surgeon', 'service', 'status'
]
readonly_fields = []
class SurgicalCaseInline(admin.TabularInline):
"""
Inline admin for surgical cases.
"""
model = SurgicalCase
extra = 0
fields = [
'case_number', 'patient', 'primary_procedure',
'scheduled_start', 'estimated_duration', 'status'
]
readonly_fields = ['case_number']
class EquipmentUsageInline(admin.TabularInline):
"""
Inline admin for equipment usage.
"""
model = EquipmentUsage
extra = 0
fields = [
'equipment_name', 'equipment_type', 'quantity_used',
'unit_cost', 'total_cost'
]
readonly_fields = ['total_cost']
@admin.register(OperatingRoom)
class OperatingRoomAdmin(admin.ModelAdmin):
"""
Admin interface for operating rooms.
"""
list_display = [
'room_number', 'room_name', 'room_type', 'status',
'floor_number', 'is_active', 'accepts_emergency',
'utilization_today'
]
list_filter = [
'tenant', 'room_type', 'status', 'floor_number',
'is_active', 'accepts_emergency', 'supports_robotic',
'supports_laparoscopic'
]
search_fields = [
'room_number', 'room_name', 'building', 'wing'
]
readonly_fields = [
'room_id', 'created_at', 'updated_at'
]
fieldsets = [
('Room Information', {
'fields': [
'room_id', 'room_number', 'room_name', 'tenant'
]
}),
('Room Type and Status', {
'fields': [
'room_type', 'status', 'is_active', 'accepts_emergency'
]
}),
('Physical Characteristics', {
'fields': [
'floor_number', 'room_size', 'ceiling_height',
'building', 'wing'
]
}),
('Environmental Controls', {
'fields': [
'temperature_min', 'temperature_max',
'humidity_min', 'humidity_max',
'air_changes_per_hour', 'positive_pressure'
],
'classes': ['collapse']
}),
('Imaging Capabilities', {
'fields': [
'has_c_arm', 'has_ct', 'has_mri',
'has_ultrasound', 'has_neuromonitoring'
],
'classes': ['collapse']
}),
('Surgical Capabilities', {
'fields': [
'supports_robotic', 'supports_laparoscopic',
'supports_microscopy', 'supports_laser'
],
'classes': ['collapse']
}),
('Capacity and Scheduling', {
'fields': [
'max_case_duration', 'turnover_time', 'cleaning_time'
],
'classes': ['collapse']
}),
('Staffing Requirements', {
'fields': [
'required_nurses', 'required_techs'
],
'classes': ['collapse']
}),
('Equipment and Features', {
'fields': [
'equipment_list', 'special_features'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
inlines = [ORBlockInline]
def utilization_today(self, obj):
"""Display today's utilization."""
# This would calculate actual utilization
return "75%" # Placeholder
utilization_today.short_description = 'Today Utilization'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs
@admin.register(ORBlock)
class ORBlockAdmin(admin.ModelAdmin):
"""
Admin interface for OR blocks.
"""
list_display = [
'operating_room_number', 'date', 'start_time', 'end_time',
'primary_surgeon_name', 'service', 'status',
'utilization_percentage', 'case_count'
]
list_filter = [
'operating_room__tenant', 'date', 'block_type', 'service',
'status', 'operating_room__room_type'
]
search_fields = [
'operating_room__room_number', 'primary_surgeon__first_name',
'primary_surgeon__last_name', 'service'
]
readonly_fields = [
'block_id', 'allocated_minutes', 'utilization_percentage',
'created_at', 'updated_at'
]
fieldsets = [
('Block Information', {
'fields': [
'block_id', 'operating_room'
]
}),
('Block Timing', {
'fields': [
'date', 'start_time', 'end_time', 'allocated_minutes'
]
}),
('Block Assignment', {
'fields': [
'block_type', 'primary_surgeon', 'assistant_surgeons', 'service'
]
}),
('Block Status', {
'fields': [
'status'
]
}),
('Utilization', {
'fields': [
'used_minutes', 'utilization_percentage'
]
}),
('Special Requirements', {
'fields': [
'special_equipment', 'special_setup'
],
'classes': ['collapse']
}),
('Notes', {
'fields': [
'notes'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
inlines = [SurgicalCaseInline]
date_hierarchy = 'date'
def operating_room_number(self, obj):
"""Display operating room number."""
return obj.operating_room.room_number
operating_room_number.short_description = 'OR'
def primary_surgeon_name(self, obj):
"""Display primary surgeon name."""
return obj.primary_surgeon.get_full_name()
primary_surgeon_name.short_description = 'Primary Surgeon'
def case_count(self, obj):
"""Display number of cases in block."""
return obj.surgical_cases.count()
case_count.short_description = 'Cases'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(operating_room__tenant=request.user.tenant)
return qs.select_related('operating_room', 'primary_surgeon')
@admin.register(SurgicalCase)
class SurgicalCaseAdmin(admin.ModelAdmin):
"""
Admin interface for surgical cases.
"""
list_display = [
'case_number', 'patient_name', 'primary_procedure',
'primary_surgeon_name', 'scheduled_start', 'estimated_duration',
'case_type', 'status', 'operating_room_number'
]
list_filter = [
'or_block__operating_room__tenant', 'case_type', 'status',
'approach', 'anesthesia_type', 'scheduled_start'
]
search_fields = [
'case_number', 'patient__first_name', 'patient__last_name',
'patient__mrn', 'primary_procedure', 'diagnosis'
]
readonly_fields = [
'case_id', 'case_number', 'actual_duration',
'created_at', 'updated_at'
]
fieldsets = [
('Case Information', {
'fields': [
'case_id', 'case_number', 'or_block'
]
}),
('Patient and Team', {
'fields': [
'patient', 'primary_surgeon', 'assistant_surgeons',
'anesthesiologist', 'circulating_nurse', 'scrub_nurse'
]
}),
('Procedure Information', {
'fields': [
'primary_procedure', 'secondary_procedures', 'procedure_codes'
]
}),
('Case Classification', {
'fields': [
'case_type', 'approach', 'anesthesia_type'
]
}),
('Timing', {
'fields': [
'scheduled_start', 'estimated_duration',
'actual_start', 'actual_end', 'actual_duration'
]
}),
('Case Status', {
'fields': [
'status'
]
}),
('Clinical Information', {
'fields': [
'diagnosis', 'diagnosis_codes', 'clinical_notes'
]
}),
('Special Requirements', {
'fields': [
'special_equipment', 'blood_products', 'implants'
],
'classes': ['collapse']
}),
('Patient Positioning', {
'fields': [
'patient_position'
],
'classes': ['collapse']
}),
('Complications and Outcomes', {
'fields': [
'complications', 'estimated_blood_loss'
],
'classes': ['collapse']
}),
('Related Information', {
'fields': [
'encounter', 'admission'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
inlines = [EquipmentUsageInline]
date_hierarchy = 'scheduled_start'
def patient_name(self, obj):
"""Display patient name."""
return obj.patient.get_full_name()
patient_name.short_description = 'Patient'
def primary_surgeon_name(self, obj):
"""Display primary surgeon name."""
return obj.primary_surgeon.get_full_name()
primary_surgeon_name.short_description = 'Primary Surgeon'
def operating_room_number(self, obj):
"""Display operating room number."""
return obj.or_block.operating_room.room_number
operating_room_number.short_description = 'OR'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(or_block__operating_room__tenant=request.user.tenant)
return qs.select_related('patient', 'primary_surgeon', 'or_block__operating_room')
@admin.register(SurgicalNote)
class SurgicalNoteAdmin(admin.ModelAdmin):
"""
Admin interface for surgical notes.
"""
list_display = [
'case_number', 'patient_name', 'surgeon_name',
'procedure_performed_short', 'status', 'signed_datetime'
]
list_filter = [
'surgical_case__or_block__operating_room__tenant',
'status', 'condition', 'disposition', 'signed_datetime'
]
search_fields = [
'surgical_case__case_number', 'surgical_case__patient__first_name',
'surgical_case__patient__last_name', 'surgeon__first_name',
'surgeon__last_name', 'procedure_performed', 'findings'
]
readonly_fields = [
'note_id', 'created_at', 'updated_at'
]
fieldsets = [
('Note Information', {
'fields': [
'note_id', 'surgical_case', 'template_used'
]
}),
('Surgeon Information', {
'fields': [
'surgeon'
]
}),
('Preoperative Information', {
'fields': [
'preoperative_diagnosis', 'planned_procedure', 'indication'
]
}),
('Intraoperative Information', {
'fields': [
'procedure_performed', 'surgical_approach', 'findings', 'technique'
]
}),
('Postoperative Information', {
'fields': [
'postoperative_diagnosis', 'condition', 'disposition'
]
}),
('Complications and Blood Loss', {
'fields': [
'complications', 'estimated_blood_loss', 'blood_transfusion'
],
'classes': ['collapse']
}),
('Specimens and Pathology', {
'fields': [
'specimens'
],
'classes': ['collapse']
}),
('Implants and Devices', {
'fields': [
'implants'
],
'classes': ['collapse']
}),
('Drains and Tubes', {
'fields': [
'drains'
],
'classes': ['collapse']
}),
('Closure', {
'fields': [
'closure'
],
'classes': ['collapse']
}),
('Postoperative Instructions', {
'fields': [
'postop_instructions', 'follow_up'
],
'classes': ['collapse']
}),
('Note Status', {
'fields': [
'status', 'signed_datetime'
]
}),
('Metadata', {
'fields': [
'created_at', 'updated_at'
],
'classes': ['collapse']
})
]
def case_number(self, obj):
"""Display case number."""
return obj.surgical_case.case_number
case_number.short_description = 'Case'
def patient_name(self, obj):
"""Display patient name."""
return obj.surgical_case.patient.get_full_name()
patient_name.short_description = 'Patient'
def surgeon_name(self, obj):
"""Display surgeon name."""
return obj.surgeon.get_full_name()
surgeon_name.short_description = 'Surgeon'
def procedure_performed_short(self, obj):
"""Display shortened procedure performed."""
if obj.procedure_performed:
return obj.procedure_performed[:50] + "..." if len(obj.procedure_performed) > 50 else obj.procedure_performed
return "-"
procedure_performed_short.short_description = 'Procedure'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(surgical_case__or_block__operating_room__tenant=request.user.tenant)
return qs.select_related('surgical_case__patient', 'surgeon')
@admin.register(EquipmentUsage)
class EquipmentUsageAdmin(admin.ModelAdmin):
"""
Admin interface for equipment usage.
"""
list_display = [
'equipment_name', 'equipment_type', 'case_number',
'quantity_used', 'unit_cost', 'total_cost',
'recorded_by_name'
]
list_filter = [
'surgical_case__or_block__operating_room__tenant',
'equipment_type', 'unit_of_measure', 'created_at'
]
search_fields = [
'equipment_name', 'manufacturer', 'model',
'serial_number', 'surgical_case__case_number'
]
readonly_fields = [
'usage_id', 'total_cost', 'duration_minutes',
'created_at', 'updated_at'
]
fieldsets = [
('Usage Information', {
'fields': [
'usage_id', 'surgical_case'
]
}),
('Equipment Information', {
'fields': [
'equipment_name', 'equipment_type', 'manufacturer',
'model', 'serial_number'
]
}),
('Usage Details', {
'fields': [
'quantity_used', 'unit_of_measure'
]
}),
('Timing', {
'fields': [
'start_time', 'end_time', 'duration_minutes'
]
}),
('Cost Information', {
'fields': [
'unit_cost', 'total_cost'
]
}),
('Quality and Safety', {
'fields': [
'lot_number', 'expiration_date', 'sterilization_date'
],
'classes': ['collapse']
}),
('Usage Notes', {
'fields': [
'notes'
],
'classes': ['collapse']
}),
('Staff Information', {
'fields': [
'recorded_by'
]
}),
('Metadata', {
'fields': [
'created_at', 'updated_at'
],
'classes': ['collapse']
})
]
def case_number(self, obj):
"""Display case number."""
return obj.surgical_case.case_number
case_number.short_description = 'Case'
def recorded_by_name(self, obj):
"""Display recorded by name."""
return obj.recorded_by.get_full_name() if obj.recorded_by else "-"
recorded_by_name.short_description = 'Recorded By'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(surgical_case__or_block__operating_room__tenant=request.user.tenant)
return qs.select_related('surgical_case', 'recorded_by')
@admin.register(SurgicalNoteTemplate)
class SurgicalNoteTemplateAdmin(admin.ModelAdmin):
"""
Admin interface for surgical note templates.
"""
list_display = [
'name', 'specialty', 'procedure_type', 'is_active',
'is_default', 'usage_count', 'created_by_name'
]
list_filter = [
'tenant', 'specialty', 'is_active', 'is_default'
]
search_fields = [
'name', 'description', 'procedure_type'
]
readonly_fields = [
'template_id', 'usage_count', 'created_at', 'updated_at'
]
fieldsets = [
('Template Information', {
'fields': [
'template_id', 'name', 'description', 'tenant'
]
}),
('Template Scope', {
'fields': [
'procedure_type', 'specialty'
]
}),
('Preoperative Templates', {
'fields': [
'preoperative_diagnosis_template', 'planned_procedure_template',
'indication_template'
],
'classes': ['collapse']
}),
('Intraoperative Templates', {
'fields': [
'procedure_performed_template', 'surgical_approach_template',
'findings_template', 'technique_template'
],
'classes': ['collapse']
}),
('Postoperative Templates', {
'fields': [
'postoperative_diagnosis_template', 'complications_template'
],
'classes': ['collapse']
}),
('Additional Templates', {
'fields': [
'specimens_template', 'implants_template', 'closure_template',
'postop_instructions_template'
],
'classes': ['collapse']
}),
('Template Status', {
'fields': [
'is_active', 'is_default'
]
}),
('Usage Statistics', {
'fields': [
'usage_count'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
def created_by_name(self, obj):
"""Display created by name."""
return obj.created_by.get_full_name() if obj.created_by else "-"
created_by_name.short_description = 'Created By'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs.select_related('created_by')
# Customize admin site
admin.site.site_header = "Hospital Management System - Operating Theatre"
admin.site.site_title = "Operating Theatre Admin"
admin.site.index_title = "Operating Theatre Administration"