2025-10-06 15:25:37 +03:00

431 lines
15 KiB
Python

"""
Admin configuration for inpatients app.
"""
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 Ward, Bed, Admission, Transfer, DischargeSummary
@admin.register(Ward)
class WardAdmin(admin.ModelAdmin):
"""
Admin interface for Ward model.
"""
list_display = [
'tenant','name', 'ward_id', 'ward_type', 'specialty', 'total_beds',
'occupancy_display', 'building', 'floor', 'is_active'
]
list_filter = [
'ward_type', 'specialty', 'building', 'floor', 'is_active',
'is_accepting_admissions', 'gender_restrictions'
]
search_fields = ['name', 'ward_id', 'description', 'building']
readonly_fields = ['created_at', 'updated_at', 'occupancy_display']
filter_horizontal = ['attending_physicians']
fieldsets = (
('Basic Information', {
'fields': ('tenant', 'ward_id', 'name', 'description', 'ward_type', 'specialty')
}),
('Capacity', {
'fields': ('total_beds', 'private_rooms', 'semi_private_rooms', 'shared_rooms')
}),
('Location', {
'fields': ('building', 'floor', 'wing')
}),
('Staffing', {
'fields': ('nurse_manager', 'attending_physicians', 'min_nurses_day',
'min_nurses_night', 'nurse_to_patient_ratio')
}),
('Equipment & Features', {
'fields': ('equipment_list', 'special_features'),
'classes': ('collapse',)
}),
('Admission Criteria', {
'fields': ('admission_criteria', 'age_restrictions', 'gender_restrictions'),
'classes': ('collapse',)
}),
('Status', {
'fields': ('is_active', 'is_accepting_admissions', 'closure_reason')
}),
('Contact', {
'fields': ('phone_number', 'extension'),
'classes': ('collapse',)
}),
('Metadata', {
'fields': ('created_at', 'updated_at', 'created_by'),
'classes': ('collapse',)
})
)
def occupancy_display(self, obj):
"""Display occupancy rate with color coding."""
rate = obj.occupancy_rate
if rate >= 90:
color = 'red'
elif rate >= 75:
color = 'orange'
else:
color = 'green'
filled_beds = obj.total_beds - obj.available_beds
return format_html(
'<span style="color: {};">{}% ({}/{})</span>',
color, rate, filled_beds, obj.total_beds
)
occupancy_display.short_description = 'Occupancy'
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
super().save_model(request, obj, form, change)
class BedInline(admin.TabularInline):
"""
Inline admin for beds within ward.
"""
model = Bed
extra = 0
fields = ['bed_number', 'room_number', 'bed_type', 'room_type', 'status',]
@admin.register(Bed)
class BedAdmin(admin.ModelAdmin):
"""
Admin interface for Bed model.
"""
list_display = [
'bed_display', 'ward', 'bed_type', 'room_type', 'status_display',
'current_admission__patient', 'occupied_duration', 'last_cleaned'
]
list_filter = [
'ward', 'bed_type', 'room_type', 'status', 'cleaning_level',
'ward__building', 'ward__floor'
]
search_fields = ['bed_number', 'room_number', 'ward__name', 'current_admission__patient__first_name',
'current_admission__patient__last_name']
readonly_fields = ['created_at', 'updated_at', 'occupancy_duration']
fieldsets = (
('Basic Information', {
'fields': ('ward', 'bed_number', 'room_number', 'bed_type', 'room_type')
}),
('Current Status', {
'fields': ('status', 'current_admission',
'occupied_since', 'reserved_until')
}),
('Equipment & Features', {
'fields': ('equipment', 'features', 'bed_position'),
'classes': ('collapse',)
}),
('Maintenance', {
'fields': ('last_maintenance', 'next_maintenance', 'maintenance_notes'),
'classes': ('collapse',)
}),
('Cleaning', {
'fields': ('last_cleaned', 'cleaned_by', 'cleaning_level'),
'classes': ('collapse',)
}),
('Blocking', {
'fields': ('blocked_reason', 'blocked_by', 'blocked_until'),
'classes': ('collapse',)
}),
('Metadata', {
'fields': ('created_at', 'updated_at', 'created_by'),
'classes': ('collapse',)
})
)
def bed_display(self, obj):
"""Display bed with room information."""
return f"Room {obj.room_number}, Bed {obj.bed_number}"
bed_display.short_description = 'Bed'
def status_display(self, obj):
"""Display status with color coding."""
colors = {
'AVAILABLE': 'green',
'OCCUPIED': 'blue',
'RESERVED': 'orange',
'MAINTENANCE': 'red',
'CLEANING': 'purple',
'OUT_OF_ORDER': 'red',
'BLOCKED': 'gray'
}
color = colors.get(obj.status, 'black')
return format_html(
'<span style="color: {};">{}</span>',
color, obj.get_status_display()
)
status_display.short_description = 'Status'
def occupied_duration(self, obj):
"""Display how long bed has been occupied."""
duration = obj.occupancy_duration
if duration:
days = duration.days
hours = duration.seconds // 3600
return f"{days}d {hours}h"
return "-"
occupied_duration.short_description = 'Occupied For'
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
super().save_model(request, obj, form, change)
@admin.register(Admission)
class AdmissionAdmin(admin.ModelAdmin):
"""
Admin interface for Admission model.
"""
list_display = [
'patient', 'admission_datetime', 'admission_type', 'current_ward',
'current_bed', 'status_display', 'length_of_stay', 'attending_physician'
]
list_filter = [
'admission_type', 'admission_source', 'status', 'priority', 'acuity_level',
'current_ward', 'isolation_required', 'code_status', 'admission_datetime'
]
search_fields = [
'patient__first_name', 'patient__last_name', 'patient__mrn',
'chief_complaint', 'admitting_diagnosis', 'admission_id'
]
readonly_fields = ['admission_id', 'created_at', 'updated_at', 'length_of_stay', 'is_active']
filter_horizontal = ['consulting_physicians']
date_hierarchy = 'admission_datetime'
fieldsets = (
('Patient Information', {
'fields': ('tenant', 'admission_id', 'patient')
}),
('Admission Details', {
'fields': ('admission_datetime', 'admission_type', 'admission_source',
'chief_complaint', 'admitting_diagnosis', 'secondary_diagnoses')
}),
('Providers', {
'fields': ('admitting_physician', 'attending_physician', 'consulting_physicians')
}),
('Location', {
'fields': ('current_ward', 'current_bed')
}),
('Status & Priority', {
'fields': ('status', 'priority', 'acuity_level')
}),
('Insurance & Financial', {
'fields': ('insurance_verified', 'authorization_number', 'estimated_length_of_stay'),
'classes': ('collapse',)
}),
('Discharge Planning', {
'fields': ('discharge_planning_started', 'discharge_planner',
'anticipated_discharge_date', 'discharge_datetime', 'discharge_disposition'),
'classes': ('collapse',)
}),
('Special Requirements', {
'fields': ('isolation_required', 'isolation_type', 'special_needs',
'allergies', 'alerts'),
'classes': ('collapse',)
}),
('Code Status & Directives', {
'fields': ('code_status', 'advance_directive', 'healthcare_proxy'),
'classes': ('collapse',)
}),
('Notes', {
'fields': ('admission_notes',),
'classes': ('collapse',)
}),
('Metadata', {
'fields': ('created_at', 'updated_at', 'created_by', 'length_of_stay', 'is_active'),
'classes': ('collapse',)
})
)
def status_display(self, obj):
"""Display status with color coding."""
colors = {
'PENDING': 'orange',
'ADMITTED': 'green',
'TRANSFERRED': 'blue',
'DISCHARGED': 'gray',
'DECEASED': 'red',
'LEFT_AMA': 'red',
'CANCELLED': 'red'
}
color = colors.get(obj.status, 'black')
return format_html(
'<span style="color: {};">{}</span>',
color, obj.get_status_display()
)
status_display.short_description = 'Status'
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
super().save_model(request, obj, form, change)
@admin.register(Transfer)
class TransferAdmin(admin.ModelAdmin):
"""
Admin interface for Transfer model.
"""
list_display = [
'patient', 'transfer_type', 'from_ward', 'to_ward', 'status_display',
'priority', 'requested_datetime', 'actual_datetime'
]
list_filter = [
'transfer_type', 'status', 'priority', 'patient_condition',
'from_ward', 'to_ward', 'transport_method', 'requested_datetime'
]
search_fields = [
'patient__first_name', 'patient__last_name', 'patient__mrn',
'reason', 'transfer_id'
]
readonly_fields = ['transfer_id', 'created_at', 'updated_at', 'transfer_duration']
filter_horizontal = ['transport_team']
date_hierarchy = 'requested_datetime'
fieldsets = (
('Transfer Information', {
'fields': ('transfer_id', 'admission', 'patient', 'transfer_type')
}),
('Location', {
'fields': ('from_ward', 'from_bed', 'to_ward', 'to_bed')
}),
('Timing', {
'fields': ('requested_datetime', 'scheduled_datetime', 'actual_datetime')
}),
('Details', {
'fields': ('reason', 'priority', 'status')
}),
('Staff', {
'fields': ('requested_by', 'approved_by', 'completed_by')
}),
('Transport', {
'fields': ('transport_method', 'transport_team', 'equipment_needed', 'supplies_needed'),
'classes': ('collapse',)
}),
('Clinical', {
'fields': ('patient_condition', 'vital_signs', 'handoff_report', 'medications_transferred'),
'classes': ('collapse',)
}),
('Issues', {
'fields': ('delay_reason', 'complications'),
'classes': ('collapse',)
}),
('Notes', {
'fields': ('notes',),
'classes': ('collapse',)
}),
('Metadata', {
'fields': ('created_at', 'updated_at', 'transfer_duration'),
'classes': ('collapse',)
})
)
def status_display(self, obj):
"""Display status with color coding."""
colors = {
'REQUESTED': 'orange',
'APPROVED': 'blue',
'SCHEDULED': 'blue',
'IN_PROGRESS': 'purple',
'COMPLETED': 'green',
'CANCELLED': 'red',
'DELAYED': 'orange'
}
color = colors.get(obj.status, 'black')
return format_html(
'<span style="color: {};">{}</span>',
color, obj.get_status_display()
)
status_display.short_description = 'Status'
@admin.register(DischargeSummary)
class DischargeSummaryAdmin(admin.ModelAdmin):
"""
Admin interface for DischargeSummary model.
"""
list_display = [
'patient_name', 'discharge_date', 'length_of_stay', 'discharge_disposition',
'discharging_physician', 'summary_completed', 'summary_signed'
]
list_filter = [
'discharge_disposition', 'readmission_risk', 'summary_completed', 'summary_signed',
'patient_copy_provided', 'discharge_date', 'transportation_method'
]
search_fields = [
'admission__patient__first_name', 'admission__patient__last_name',
'admission__patient__mrn', 'summary_id', 'final_diagnosis'
]
readonly_fields = ['summary_id', 'created_at', 'updated_at']
date_hierarchy = 'discharge_date'
fieldsets = (
('Basic Information', {
'fields': ('summary_id', 'admission', 'discharge_date', 'discharge_time', 'length_of_stay')
}),
('Clinical Summary', {
'fields': ('admission_diagnosis', 'final_diagnosis', 'secondary_diagnoses',
'procedures_performed', 'hospital_course', 'complications')
}),
('Medications', {
'fields': ('discharge_medications', 'medication_changes'),
'classes': ('collapse',)
}),
('Instructions', {
'fields': ('activity_restrictions', 'diet_instructions', 'wound_care', 'special_instructions'),
'classes': ('collapse',)
}),
('Follow-up', {
'fields': ('follow_up_appointments', 'follow_up_instructions', 'warning_signs', 'when_to_call'),
'classes': ('collapse',)
}),
('Discharge Details', {
'fields': ('discharge_disposition', 'discharge_location', 'transportation_arranged', 'transportation_method')
}),
('Equipment & Supplies', {
'fields': ('durable_medical_equipment', 'supplies_provided'),
'classes': ('collapse',)
}),
('Education', {
'fields': ('education_provided', 'education_materials', 'patient_understanding'),
'classes': ('collapse',)
}),
('Planning', {
'fields': ('discharge_planner', 'social_worker_involved', 'case_manager_involved')
}),
('Quality', {
'fields': ('readmission_risk', 'patient_satisfaction')
}),
('Providers', {
'fields': ('discharging_physician', 'primary_nurse')
}),
('Documentation', {
'fields': ('summary_completed', 'summary_signed', 'patient_copy_provided')
}),
('Metadata', {
'fields': ('created_at', 'updated_at', 'created_by'),
'classes': ('collapse',)
})
)
def patient_name(self, obj):
"""Display patient name."""
return obj.admission.patient.get_full_name()
patient_name.short_description = 'Patient'
def save_model(self, request, obj, form, change):
if not change:
obj.created_by = request.user
super().save_model(request, obj, form, change)
# Register Ward with Bed inline
WardAdmin.inlines = [BedInline]