676 lines
20 KiB
Python
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"
|
|
|