607 lines
19 KiB
Python
607 lines
19 KiB
Python
"""
|
|
Admin configuration for appointments app.
|
|
"""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.utils import timezone
|
|
from .models import *
|
|
|
|
|
|
class QueueEntryInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for queue entries.
|
|
"""
|
|
model = QueueEntry
|
|
extra = 0
|
|
fields = [
|
|
'patient', 'queue_position', 'priority_score', 'status',
|
|
'assigned_provider', 'joined_at'
|
|
]
|
|
readonly_fields = ['joined_at']
|
|
|
|
|
|
class TelemedicineSessionInline(admin.StackedInline):
|
|
"""
|
|
Inline admin for telemedicine sessions.
|
|
"""
|
|
model = TelemedicineSession
|
|
extra = 0
|
|
fields = [
|
|
'platform', 'meeting_url', 'meeting_id', 'meeting_password',
|
|
'status', 'scheduled_start', 'scheduled_end'
|
|
]
|
|
|
|
|
|
@admin.register(AppointmentRequest)
|
|
class AppointmentRequestAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for AppointmentRequest model.
|
|
"""
|
|
list_display = [
|
|
'patient', 'provider', 'appointment_type', 'specialty',
|
|
'scheduled_datetime', 'status', 'priority', 'is_telemedicine'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'appointment_type', 'specialty', 'status', 'priority',
|
|
'is_telemedicine', 'insurance_verified', 'authorization_required',
|
|
'scheduled_datetime', 'created_at'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'provider__first_name', 'provider__last_name',
|
|
'chief_complaint', 'referring_provider'
|
|
]
|
|
ordering = ['-scheduled_datetime', '-created_at']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'tenant', 'patient', 'provider', 'appointment_type', 'specialty'
|
|
)
|
|
}),
|
|
('Scheduling', {
|
|
'fields': (
|
|
'preferred_date', 'preferred_time', 'duration_minutes',
|
|
'scheduled_datetime', 'scheduled_end_datetime',
|
|
'flexible_scheduling', 'earliest_acceptable_date', 'latest_acceptable_date'
|
|
)
|
|
}),
|
|
('Priority and Urgency', {
|
|
'fields': ('priority', 'urgency_score')
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': (
|
|
'chief_complaint', 'clinical_notes', 'referring_provider'
|
|
)
|
|
}),
|
|
('Insurance and Authorization', {
|
|
'fields': (
|
|
'insurance_verified', 'authorization_required', 'authorization_number'
|
|
)
|
|
}),
|
|
('Location', {
|
|
'fields': ('location', 'room_number')
|
|
}),
|
|
('Telemedicine', {
|
|
'fields': (
|
|
'is_telemedicine', 'telemedicine_platform',
|
|
'meeting_url', 'meeting_id', 'meeting_password'
|
|
)
|
|
}),
|
|
('Status and Workflow', {
|
|
'fields': ('status',)
|
|
}),
|
|
('Check-in Information', {
|
|
'fields': ('checked_in_at', 'checked_in_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Completion Information', {
|
|
'fields': ('completed_at', 'actual_duration_minutes'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Cancellation Information', {
|
|
'fields': ('cancelled_at', 'cancelled_by', 'cancellation_reason'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Special Requirements', {
|
|
'fields': (
|
|
'special_requirements', 'interpreter_needed', 'interpreter_language'
|
|
),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('request_id', 'created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['request_id', 'created_at', 'updated_at']
|
|
inlines = [TelemedicineSessionInline, QueueEntryInline]
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'tenant', 'patient', 'provider', 'created_by', 'checked_in_by', 'cancelled_by'
|
|
)
|
|
|
|
def is_telemedicine(self, obj):
|
|
return obj.is_telemedicine
|
|
is_telemedicine.boolean = True
|
|
is_telemedicine.short_description = 'Telemedicine'
|
|
|
|
|
|
@admin.register(SlotAvailability)
|
|
class SlotAvailabilityAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for SlotAvailability model.
|
|
"""
|
|
list_display = [
|
|
'provider', 'date', 'start_time', 'end_time', 'specialty',
|
|
'max_appointments', 'booked_appointments', 'available_capacity',
|
|
'is_active', 'is_blocked'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'provider', 'specialty', 'availability_type',
|
|
'is_active', 'is_blocked', 'supports_telemedicine',
|
|
'telemedicine_only', 'date'
|
|
]
|
|
search_fields = [
|
|
'provider__first_name', 'provider__last_name',
|
|
'specialty', 'location', 'room_number'
|
|
]
|
|
ordering = ['date', 'start_time']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': ('tenant', 'provider', 'specialty')
|
|
}),
|
|
('Date and Time', {
|
|
'fields': (
|
|
'date', 'start_time', 'end_time', 'duration_minutes'
|
|
)
|
|
}),
|
|
('Capacity', {
|
|
'fields': ('max_appointments', 'booked_appointments')
|
|
}),
|
|
('Location', {
|
|
'fields': ('location', 'room_number')
|
|
}),
|
|
('Availability Type', {
|
|
'fields': ('availability_type',)
|
|
}),
|
|
('Telemedicine', {
|
|
'fields': ('supports_telemedicine', 'telemedicine_only')
|
|
}),
|
|
('Status', {
|
|
'fields': ('is_active', 'is_blocked', 'block_reason')
|
|
}),
|
|
('Recurring Pattern', {
|
|
'fields': (
|
|
'is_recurring', 'recurrence_pattern', 'recurrence_end_date'
|
|
),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Restrictions', {
|
|
'fields': ('patient_restrictions', 'insurance_restrictions'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('slot_id', 'created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['slot_id', 'created_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'tenant', 'provider', 'created_by'
|
|
)
|
|
|
|
def available_capacity(self, obj):
|
|
return obj.available_capacity
|
|
available_capacity.short_description = 'Available'
|
|
|
|
|
|
@admin.register(WaitingQueue)
|
|
class WaitingQueueAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for WaitingQueue model.
|
|
"""
|
|
list_display = [
|
|
'name', 'queue_type', 'specialty', 'current_queue_size',
|
|
'max_queue_size', 'estimated_wait_time_minutes',
|
|
'is_active', 'is_accepting_patients'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'queue_type', 'specialty', 'is_active', 'is_accepting_patients'
|
|
]
|
|
search_fields = ['name', 'description', 'specialty', 'location']
|
|
ordering = ['name']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': ('tenant', 'name', 'description', 'queue_type')
|
|
}),
|
|
('Associated Resources', {
|
|
'fields': ('providers', 'specialty', 'location')
|
|
}),
|
|
('Queue Management', {
|
|
'fields': (
|
|
'max_queue_size', 'average_service_time_minutes', 'priority_weights'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': ('is_active', 'is_accepting_patients')
|
|
}),
|
|
('Operating Hours', {
|
|
'fields': ('operating_hours',),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('queue_id', 'created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['queue_id', 'created_at', 'updated_at']
|
|
inlines = [QueueEntryInline]
|
|
filter_horizontal = ['providers']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'tenant', 'created_by'
|
|
).prefetch_related('providers')
|
|
|
|
def current_queue_size(self, obj):
|
|
return obj.current_queue_size
|
|
current_queue_size.short_description = 'Current Size'
|
|
|
|
def estimated_wait_time_minutes(self, obj):
|
|
return f"{obj.estimated_wait_time_minutes} min"
|
|
estimated_wait_time_minutes.short_description = 'Est. Wait Time'
|
|
|
|
|
|
@admin.register(QueueEntry)
|
|
class QueueEntryAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for QueueEntry model.
|
|
"""
|
|
list_display = [
|
|
'patient', 'queue', 'queue_position', 'priority_score',
|
|
'status', 'wait_time_display', 'assigned_provider', 'joined_at'
|
|
]
|
|
list_filter = [
|
|
'queue', 'status', 'assigned_provider', 'notification_sent',
|
|
'notification_method', 'joined_at'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'queue__name', 'assigned_provider__first_name', 'assigned_provider__last_name'
|
|
]
|
|
ordering = ['queue', 'priority_score', 'joined_at']
|
|
|
|
fieldsets = (
|
|
('Queue Information', {
|
|
'fields': ('queue', 'patient', 'appointment')
|
|
}),
|
|
('Position and Priority', {
|
|
'fields': ('queue_position', 'priority_score')
|
|
}),
|
|
('Timing', {
|
|
'fields': (
|
|
'joined_at', 'estimated_service_time',
|
|
'called_at', 'served_at'
|
|
)
|
|
}),
|
|
('Status and Assignment', {
|
|
'fields': ('status', 'assigned_provider')
|
|
}),
|
|
('Communication', {
|
|
'fields': ('notification_sent', 'notification_method')
|
|
}),
|
|
('Notes', {
|
|
'fields': ('notes',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('entry_id', 'updated_at', 'updated_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['entry_id', 'joined_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'queue', 'patient', 'appointment', 'assigned_provider', 'updated_by'
|
|
)
|
|
|
|
def wait_time_display(self, obj):
|
|
wait_time = obj.wait_time_minutes
|
|
if wait_time is not None:
|
|
return f"{wait_time} min"
|
|
return "-"
|
|
wait_time_display.short_description = 'Wait Time'
|
|
|
|
|
|
@admin.register(TelemedicineSession)
|
|
class TelemedicineSessionAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for TelemedicineSession model.
|
|
"""
|
|
list_display = [
|
|
'appointment', 'platform', 'status', 'scheduled_start',
|
|
'duration_display', 'connection_quality', 'recording_enabled'
|
|
]
|
|
list_filter = [
|
|
'platform', 'status', 'connection_quality', 'recording_enabled',
|
|
'recording_consent', 'encryption_enabled', 'scheduled_start'
|
|
]
|
|
search_fields = [
|
|
'appointment__patient__first_name', 'appointment__patient__last_name',
|
|
'appointment__provider__first_name', 'appointment__provider__last_name',
|
|
'meeting_id'
|
|
]
|
|
ordering = ['-scheduled_start']
|
|
|
|
fieldsets = (
|
|
('Session Information', {
|
|
'fields': ('appointment', 'platform')
|
|
}),
|
|
('Meeting Details', {
|
|
'fields': (
|
|
'meeting_url', 'meeting_id', 'meeting_password'
|
|
)
|
|
}),
|
|
('Configuration', {
|
|
'fields': (
|
|
'waiting_room_enabled', 'recording_enabled', 'recording_consent'
|
|
)
|
|
}),
|
|
('Security', {
|
|
'fields': ('encryption_enabled', 'password_required')
|
|
}),
|
|
('Timing', {
|
|
'fields': (
|
|
'scheduled_start', 'scheduled_end',
|
|
'actual_start', 'actual_end'
|
|
)
|
|
}),
|
|
('Participants', {
|
|
'fields': ('provider_joined_at', 'patient_joined_at')
|
|
}),
|
|
('Technical Information', {
|
|
'fields': ('connection_quality', 'technical_issues')
|
|
}),
|
|
('Recording', {
|
|
'fields': (
|
|
'recording_url', 'recording_duration_minutes'
|
|
),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Notes', {
|
|
'fields': ('session_notes',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('session_id', 'created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['session_id', 'created_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'appointment__patient', 'appointment__provider', 'created_by'
|
|
)
|
|
|
|
def duration_display(self, obj):
|
|
duration = obj.duration_minutes
|
|
if duration is not None:
|
|
return f"{duration} min"
|
|
return "-"
|
|
duration_display.short_description = 'Duration'
|
|
|
|
|
|
@admin.register(AppointmentTemplate)
|
|
class AppointmentTemplateAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for AppointmentTemplate model.
|
|
"""
|
|
list_display = [
|
|
'name', 'specialty', 'appointment_type', 'duration_minutes',
|
|
'advance_booking_days', 'minimum_notice_hours', 'is_active'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'specialty', 'appointment_type', 'is_active',
|
|
'insurance_verification_required', 'authorization_required'
|
|
]
|
|
search_fields = ['name', 'description', 'specialty', 'appointment_type']
|
|
ordering = ['specialty', 'name']
|
|
|
|
fieldsets = (
|
|
('Template Information', {
|
|
'fields': ('tenant', 'name', 'description')
|
|
}),
|
|
('Appointment Configuration', {
|
|
'fields': ('appointment_type', 'specialty', 'duration_minutes')
|
|
}),
|
|
('Scheduling Rules', {
|
|
'fields': ('advance_booking_days', 'minimum_notice_hours')
|
|
}),
|
|
('Requirements', {
|
|
'fields': ('insurance_verification_required', 'authorization_required')
|
|
}),
|
|
('Instructions', {
|
|
'fields': (
|
|
'pre_appointment_instructions', 'post_appointment_instructions'
|
|
)
|
|
}),
|
|
('Forms and Documents', {
|
|
'fields': ('required_forms',)
|
|
}),
|
|
('Status', {
|
|
'fields': ('is_active',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'tenant', 'created_by'
|
|
)
|
|
|
|
|
|
@admin.register(WaitingList)
|
|
class WaitingListAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for WaitingList model.
|
|
"""
|
|
list_display = [
|
|
'patient', 'specialty', 'priority', 'urgency_score',
|
|
'status', 'position', 'days_waiting', 'last_contacted'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'department', 'specialty', 'priority', 'status',
|
|
'appointment_type', 'authorization_required', 'requires_interpreter'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'clinical_indication', 'referring_provider'
|
|
]
|
|
ordering = ['priority', 'urgency_score', 'created_at']
|
|
|
|
fieldsets = (
|
|
('Patient Information', {
|
|
'fields': ('tenant', 'patient', 'department', 'provider')
|
|
}),
|
|
('Service Request', {
|
|
'fields': ('appointment_type', 'specialty', 'clinical_indication')
|
|
}),
|
|
('Priority and Urgency', {
|
|
'fields': ('priority', 'urgency_score', 'diagnosis_codes')
|
|
}),
|
|
('Patient Preferences', {
|
|
'fields': (
|
|
'preferred_date', 'preferred_time', 'flexible_scheduling',
|
|
'earliest_acceptable_date', 'latest_acceptable_date'
|
|
)
|
|
}),
|
|
('Contact Information', {
|
|
'fields': ('contact_method', 'contact_phone', 'contact_email')
|
|
}),
|
|
('Status and Position', {
|
|
'fields': ('status', 'position', 'estimated_wait_time')
|
|
}),
|
|
('Contact History', {
|
|
'fields': (
|
|
'last_contacted', 'contact_attempts', 'max_contact_attempts',
|
|
'appointments_offered', 'appointments_declined'
|
|
)
|
|
}),
|
|
('Requirements', {
|
|
'fields': (
|
|
'requires_interpreter', 'interpreter_language',
|
|
'accessibility_requirements', 'transportation_needed'
|
|
),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Insurance and Authorization', {
|
|
'fields': (
|
|
'insurance_verified', 'authorization_required',
|
|
'authorization_status', 'authorization_number'
|
|
),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Referral Information', {
|
|
'fields': (
|
|
'referring_provider', 'referral_date', 'referral_urgency'
|
|
),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Outcome', {
|
|
'fields': (
|
|
'scheduled_appointment', 'removal_reason', 'removal_notes',
|
|
'removed_at', 'removed_by'
|
|
),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Notes', {
|
|
'fields': ('notes',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('waiting_list_id', 'created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['waiting_list_id', 'created_at', 'updated_at', 'days_waiting']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'tenant', 'patient', 'department', 'provider', 'created_by',
|
|
'removed_by', 'scheduled_appointment'
|
|
)
|
|
|
|
def days_waiting(self, obj):
|
|
return obj.days_waiting
|
|
|
|
days_waiting.short_description = 'Days Waiting'
|
|
|
|
|
|
@admin.register(WaitingListContactLog)
|
|
class WaitingListContactLogAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for WaitingListContactLog model.
|
|
"""
|
|
list_display = [
|
|
'waiting_list_entry', 'contact_date', 'contact_method',
|
|
'contact_outcome', 'appointment_offered', 'patient_response'
|
|
]
|
|
list_filter = [
|
|
'contact_method', 'contact_outcome', 'appointment_offered',
|
|
'patient_response', 'contact_date'
|
|
]
|
|
search_fields = [
|
|
'waiting_list_entry__patient__first_name',
|
|
'waiting_list_entry__patient__last_name',
|
|
'notes'
|
|
]
|
|
ordering = ['-contact_date']
|
|
|
|
fieldsets = (
|
|
('Contact Information', {
|
|
'fields': ('waiting_list_entry', 'contact_method', 'contact_outcome')
|
|
}),
|
|
('Appointment Offer', {
|
|
'fields': (
|
|
'appointment_offered', 'offered_date', 'offered_time',
|
|
'patient_response'
|
|
)
|
|
}),
|
|
('Follow-up', {
|
|
'fields': ('next_contact_date',)
|
|
}),
|
|
('Notes', {
|
|
'fields': ('notes',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('contact_date', 'contacted_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['contact_date']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'waiting_list_entry__patient', 'contacted_by'
|
|
)
|
|
|
|
|