330 lines
12 KiB
Python
330 lines
12 KiB
Python
"""
|
|
Django admin configuration for appointments app.
|
|
"""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.translation import gettext_lazy as _
|
|
from simple_history.admin import SimpleHistoryAdmin
|
|
|
|
from .models import (
|
|
Provider,
|
|
Room,
|
|
Schedule,
|
|
Appointment,
|
|
AppointmentReminder,
|
|
AppointmentConfirmation,
|
|
Session,
|
|
SessionParticipant,
|
|
)
|
|
|
|
|
|
@admin.register(Provider)
|
|
class ProviderAdmin(admin.ModelAdmin):
|
|
"""Admin interface for Provider model."""
|
|
|
|
list_display = ['user', 'is_available', 'max_daily_appointments', 'tenant', 'created_at']
|
|
list_filter = ['is_available', 'tenant', 'created_at']
|
|
search_fields = ['user__username', 'user__first_name', 'user__last_name', 'user__email']
|
|
readonly_fields = ['id', 'created_at', 'updated_at']
|
|
filter_horizontal = ['specialties']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('user', 'tenant', 'is_available')
|
|
}),
|
|
(_('Configuration'), {
|
|
'fields': ('specialties', 'max_daily_appointments')
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
|
|
@admin.register(Room)
|
|
class RoomAdmin(admin.ModelAdmin):
|
|
"""Admin interface for Room model."""
|
|
|
|
list_display = ['room_number', 'name', 'clinic', 'is_available', 'tenant']
|
|
list_filter = ['is_available', 'clinic', 'tenant']
|
|
search_fields = ['name', 'room_number']
|
|
readonly_fields = ['id', 'created_at', 'updated_at']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('name', 'room_number', 'clinic', 'tenant', 'is_available')
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
|
|
@admin.register(Schedule)
|
|
class ScheduleAdmin(admin.ModelAdmin):
|
|
"""Admin interface for Schedule model."""
|
|
|
|
list_display = ['provider', 'day_of_week', 'start_time', 'end_time', 'slot_duration', 'is_active']
|
|
list_filter = ['day_of_week', 'is_active', 'provider']
|
|
search_fields = ['provider__user__username', 'provider__user__first_name', 'provider__user__last_name']
|
|
readonly_fields = ['id', 'created_at', 'updated_at']
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('provider', 'day_of_week', 'is_active')
|
|
}),
|
|
(_('Time Configuration'), {
|
|
'fields': ('start_time', 'end_time', 'slot_duration')
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
|
|
@admin.register(Appointment)
|
|
class AppointmentAdmin(SimpleHistoryAdmin):
|
|
"""Admin interface for Appointment model."""
|
|
|
|
list_display = ['appointment_number', 'patient', 'provider', 'scheduled_date', 'scheduled_time',
|
|
'status', 'package_info', 'finance_cleared', 'consent_verified']
|
|
list_filter = ['status', 'clinic', 'scheduled_date', 'finance_cleared', 'consent_verified', 'tenant']
|
|
search_fields = ['appointment_number', 'patient__mrn', 'patient__first_name_en',
|
|
'patient__last_name_en', 'provider__user__username']
|
|
readonly_fields = ['id', 'appointment_number', 'created_at', 'updated_at', 'is_past']
|
|
date_hierarchy = 'scheduled_date'
|
|
|
|
fieldsets = (
|
|
(_('Identification'), {
|
|
'fields': ('appointment_number', 'tenant')
|
|
}),
|
|
(_('Core Information'), {
|
|
'fields': ('patient', 'clinic', 'provider', 'room', 'service_type')
|
|
}),
|
|
(_('Scheduling'), {
|
|
'fields': ('scheduled_date', 'scheduled_time', 'duration')
|
|
}),
|
|
(_('Package Information'), {
|
|
'fields': ('package_purchase', 'session_number_in_package'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Status'), {
|
|
'fields': ('status', 'finance_cleared', 'consent_verified')
|
|
}),
|
|
(_('Confirmation'), {
|
|
'fields': ('confirmation_sent_at', 'confirmation_method'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Timestamps'), {
|
|
'fields': ('arrival_at', 'start_at', 'end_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Rescheduling'), {
|
|
'fields': ('reschedule_reason', 'reschedule_count'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Cancellation'), {
|
|
'fields': ('cancel_reason', 'cancelled_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Additional Information'), {
|
|
'fields': ('notes',),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'is_past', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
def package_info(self, obj):
|
|
"""Display package information if appointment is part of a package."""
|
|
if obj.package_purchase:
|
|
return f"{obj.package_purchase.package.name_en} (Session {obj.session_number_in_package}/{obj.package_purchase.total_sessions})"
|
|
return "-"
|
|
package_info.short_description = _('Package')
|
|
|
|
|
|
@admin.register(AppointmentReminder)
|
|
class AppointmentReminderAdmin(admin.ModelAdmin):
|
|
"""Admin interface for AppointmentReminder model."""
|
|
|
|
list_display = ['appointment', 'reminder_type', 'scheduled_for', 'status', 'sent_at']
|
|
list_filter = ['reminder_type', 'status', 'scheduled_for']
|
|
search_fields = ['appointment__appointment_number', 'appointment__patient__mrn']
|
|
readonly_fields = ['id', 'created_at', 'updated_at']
|
|
date_hierarchy = 'scheduled_for'
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('appointment', 'reminder_type', 'status')
|
|
}),
|
|
(_('Scheduling'), {
|
|
'fields': ('scheduled_for', 'sent_at')
|
|
}),
|
|
(_('Message'), {
|
|
'fields': ('message',),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
|
|
@admin.register(AppointmentConfirmation)
|
|
class AppointmentConfirmationAdmin(admin.ModelAdmin):
|
|
"""Admin interface for AppointmentConfirmation model."""
|
|
|
|
list_display = ['appointment', 'status', 'confirmation_method', 'sent_at', 'confirmed_at',
|
|
'expires_at', 'is_valid']
|
|
list_filter = ['status', 'confirmation_method', 'sent_at', 'confirmed_at', 'expires_at']
|
|
search_fields = ['appointment__appointment_number', 'appointment__patient__mrn', 'token']
|
|
readonly_fields = ['id', 'token', 'is_expired', 'is_valid', 'created_at', 'updated_at']
|
|
date_hierarchy = 'created_at'
|
|
|
|
fieldsets = (
|
|
(None, {
|
|
'fields': ('appointment', 'token', 'status', 'confirmation_method')
|
|
}),
|
|
(_('Timing'), {
|
|
'fields': ('sent_at', 'confirmed_at', 'expires_at', 'is_expired', 'is_valid')
|
|
}),
|
|
(_('Confirmation Details'), {
|
|
'fields': ('confirmed_by_ip', 'confirmed_by_user_agent'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Reminders'), {
|
|
'fields': ('reminder_count', 'last_reminder_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
|
|
class SessionParticipantInline(admin.TabularInline):
|
|
"""Inline admin for session participants."""
|
|
model = SessionParticipant
|
|
extra = 0
|
|
readonly_fields = ['appointment_number', 'created_at']
|
|
fields = ['patient', 'appointment_number', 'status', 'finance_cleared', 'consent_verified',
|
|
'arrival_at', 'attended_at']
|
|
|
|
def has_add_permission(self, request, obj=None):
|
|
"""Allow adding participants through the inline."""
|
|
return True
|
|
|
|
|
|
@admin.register(Session)
|
|
class SessionAdmin(SimpleHistoryAdmin):
|
|
"""Admin interface for Session model."""
|
|
|
|
list_display = ['session_number', 'session_type', 'provider', 'clinic', 'scheduled_date',
|
|
'scheduled_time', 'capacity_display', 'status', 'tenant']
|
|
list_filter = ['session_type', 'status', 'clinic', 'scheduled_date', 'tenant']
|
|
search_fields = ['session_number', 'provider__user__first_name', 'provider__user__last_name',
|
|
'service_type']
|
|
readonly_fields = ['id', 'session_number', 'current_capacity', 'available_spots',
|
|
'is_full', 'capacity_percentage', 'created_at', 'updated_at']
|
|
date_hierarchy = 'scheduled_date'
|
|
inlines = [SessionParticipantInline]
|
|
|
|
fieldsets = (
|
|
(_('Identification'), {
|
|
'fields': ('session_number', 'tenant', 'session_type')
|
|
}),
|
|
(_('Core Information'), {
|
|
'fields': ('provider', 'clinic', 'room', 'service_type')
|
|
}),
|
|
(_('Scheduling'), {
|
|
'fields': ('scheduled_date', 'scheduled_time', 'duration')
|
|
}),
|
|
(_('Capacity'), {
|
|
'fields': ('max_capacity', 'current_capacity', 'available_spots',
|
|
'is_full', 'capacity_percentage')
|
|
}),
|
|
(_('Status'), {
|
|
'fields': ('status', 'start_at', 'end_at')
|
|
}),
|
|
(_('Notes'), {
|
|
'fields': ('group_notes',),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Cancellation'), {
|
|
'fields': ('cancel_reason', 'cancelled_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
def capacity_display(self, obj):
|
|
"""Display capacity as current/max."""
|
|
return f"{obj.current_capacity}/{obj.max_capacity}"
|
|
capacity_display.short_description = _('Capacity')
|
|
|
|
def get_queryset(self, request):
|
|
"""Optimize queryset with related objects."""
|
|
qs = super().get_queryset(request)
|
|
return qs.select_related('provider__user', 'clinic', 'room', 'tenant')
|
|
|
|
|
|
@admin.register(SessionParticipant)
|
|
class SessionParticipantAdmin(SimpleHistoryAdmin):
|
|
"""Admin interface for SessionParticipant model."""
|
|
|
|
list_display = ['appointment_number', 'patient', 'session', 'status',
|
|
'finance_cleared', 'consent_verified', 'arrival_at', 'attended_at']
|
|
list_filter = ['status', 'finance_cleared', 'consent_verified', 'session__scheduled_date',
|
|
'session__clinic']
|
|
search_fields = ['appointment_number', 'patient__mrn', 'patient__first_name_en',
|
|
'patient__last_name_en', 'session__session_number']
|
|
readonly_fields = ['id', 'appointment_number', 'can_check_in', 'created_at', 'updated_at']
|
|
date_hierarchy = 'created_at'
|
|
|
|
fieldsets = (
|
|
(_('Identification'), {
|
|
'fields': ('appointment_number', 'session', 'patient')
|
|
}),
|
|
(_('Status'), {
|
|
'fields': ('status', 'can_check_in')
|
|
}),
|
|
(_('Prerequisites'), {
|
|
'fields': ('finance_cleared', 'consent_verified')
|
|
}),
|
|
(_('Timestamps'), {
|
|
'fields': ('confirmation_sent_at', 'arrival_at', 'attended_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Notes'), {
|
|
'fields': ('individual_notes',),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Cancellation'), {
|
|
'fields': ('cancel_reason', 'cancelled_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('No-Show'), {
|
|
'fields': ('no_show_reason', 'no_show_notes'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
(_('Metadata'), {
|
|
'fields': ('id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
def get_queryset(self, request):
|
|
"""Optimize queryset with related objects."""
|
|
qs = super().get_queryset(request)
|
|
return qs.select_related('session__provider__user', 'session__clinic', 'patient')
|