431 lines
15 KiB
Python
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]
|