344 lines
11 KiB
Python
344 lines
11 KiB
Python
"""
|
|
Admin configuration for Insurance Approvals app.
|
|
"""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from django.utils import timezone
|
|
from .models import (
|
|
InsuranceApprovalRequest,
|
|
ApprovalDocument,
|
|
ApprovalStatusHistory,
|
|
ApprovalCommunicationLog,
|
|
ApprovalTemplate
|
|
)
|
|
|
|
|
|
class ApprovalDocumentInline(admin.TabularInline):
|
|
model = ApprovalDocument
|
|
extra = 0
|
|
fields = ('document_type', 'title', 'file', 'file_size_mb', 'uploaded_at', 'uploaded_by')
|
|
readonly_fields = ('file_size_mb', 'uploaded_at', 'uploaded_by')
|
|
|
|
def file_size_mb(self, obj):
|
|
if obj.file_size:
|
|
return f"{obj.file_size_mb} MB"
|
|
return "-"
|
|
file_size_mb.short_description = 'File Size'
|
|
|
|
|
|
class ApprovalStatusHistoryInline(admin.TabularInline):
|
|
model = ApprovalStatusHistory
|
|
extra = 0
|
|
fields = ('from_status', 'to_status', 'reason', 'changed_at', 'changed_by')
|
|
readonly_fields = ('changed_at', 'changed_by')
|
|
can_delete = False
|
|
|
|
|
|
class ApprovalCommunicationLogInline(admin.TabularInline):
|
|
model = ApprovalCommunicationLog
|
|
extra = 0
|
|
fields = ('communication_type', 'subject', 'contact_person', 'communicated_at', 'follow_up_required')
|
|
readonly_fields = ('communicated_at',)
|
|
|
|
|
|
@admin.register(InsuranceApprovalRequest)
|
|
class InsuranceApprovalRequestAdmin(admin.ModelAdmin):
|
|
list_display = (
|
|
'approval_number',
|
|
'patient_link',
|
|
'request_type',
|
|
'status_badge',
|
|
'priority_badge',
|
|
'insurance_company',
|
|
'submitted_date',
|
|
'decision_date',
|
|
'expiration_status',
|
|
'assigned_to'
|
|
)
|
|
list_filter = (
|
|
'status',
|
|
'priority',
|
|
'request_type',
|
|
'is_urgent',
|
|
'is_expedited',
|
|
'requires_peer_review',
|
|
'submitted_date',
|
|
'decision_date'
|
|
)
|
|
search_fields = (
|
|
'approval_number',
|
|
'authorization_number',
|
|
'reference_number',
|
|
'patient__first_name',
|
|
'patient__last_name',
|
|
'patient__mrn',
|
|
'insurance_info__insurance_company',
|
|
'service_description'
|
|
)
|
|
readonly_fields = (
|
|
'approval_id',
|
|
'approval_number',
|
|
'created_at',
|
|
'updated_at',
|
|
'created_by',
|
|
'turnaround_time_days',
|
|
'days_until_expiry'
|
|
)
|
|
|
|
fieldsets = (
|
|
('Identification', {
|
|
'fields': ('approval_id', 'approval_number', 'tenant')
|
|
}),
|
|
('Patient & Insurance', {
|
|
'fields': ('patient', 'insurance_info')
|
|
}),
|
|
('Order Information', {
|
|
'fields': ('content_type', 'object_id', 'request_type', 'service_description')
|
|
}),
|
|
('Medical Codes', {
|
|
'fields': ('procedure_codes', 'diagnosis_codes')
|
|
}),
|
|
('Clinical Information', {
|
|
'fields': ('clinical_justification', 'medical_necessity', 'alternative_treatments_tried')
|
|
}),
|
|
('Requested Services', {
|
|
'fields': (
|
|
'requested_quantity',
|
|
'requested_visits',
|
|
'requested_units',
|
|
'service_start_date',
|
|
'service_end_date'
|
|
)
|
|
}),
|
|
('Status & Priority', {
|
|
'fields': ('status', 'priority', 'is_urgent', 'is_expedited', 'requires_peer_review')
|
|
}),
|
|
('Submission', {
|
|
'fields': ('submission_method', 'submitted_date', 'submitted_by')
|
|
}),
|
|
('Insurance Response', {
|
|
'fields': (
|
|
'decision_date',
|
|
'authorization_number',
|
|
'reference_number',
|
|
'approved_quantity',
|
|
'approved_visits',
|
|
'approved_units',
|
|
'approved_amount',
|
|
'effective_date',
|
|
'expiration_date'
|
|
)
|
|
}),
|
|
('Denial & Appeal', {
|
|
'fields': ('denial_reason', 'denial_code', 'appeal_date', 'appeal_reason', 'appeal_deadline'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Assignment & Tracking', {
|
|
'fields': ('assigned_to', 'requesting_provider')
|
|
}),
|
|
('Communication', {
|
|
'fields': ('last_contact_date', 'last_contact_method', 'last_contact_notes'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Notes', {
|
|
'fields': ('internal_notes', 'insurance_notes'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('created_at', 'updated_at', 'created_by', 'turnaround_time_days', 'days_until_expiry'),
|
|
'classes': ('collapse',)
|
|
})
|
|
)
|
|
|
|
inlines = [ApprovalDocumentInline, ApprovalStatusHistoryInline, ApprovalCommunicationLogInline]
|
|
|
|
def patient_link(self, obj):
|
|
url = reverse('admin:patients_patientprofile_change', args=[obj.patient.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.patient.get_full_name())
|
|
patient_link.short_description = 'Patient'
|
|
|
|
def insurance_company(self, obj):
|
|
return obj.insurance_info.insurance_company
|
|
insurance_company.short_description = 'Insurance'
|
|
|
|
def status_badge(self, obj):
|
|
colors = {
|
|
'DRAFT': 'gray',
|
|
'PENDING_SUBMISSION': 'orange',
|
|
'SUBMITTED': 'blue',
|
|
'UNDER_REVIEW': 'blue',
|
|
'ADDITIONAL_INFO_REQUESTED': 'orange',
|
|
'APPROVED': 'green',
|
|
'PARTIALLY_APPROVED': 'lightgreen',
|
|
'DENIED': 'red',
|
|
'APPEALED': 'purple',
|
|
'APPEAL_APPROVED': 'green',
|
|
'APPEAL_DENIED': 'red',
|
|
'EXPIRED': 'darkgray',
|
|
'CANCELLED': 'gray'
|
|
}
|
|
color = colors.get(obj.status, 'gray')
|
|
return format_html(
|
|
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
|
|
color,
|
|
obj.get_status_display()
|
|
)
|
|
status_badge.short_description = 'Status'
|
|
|
|
def priority_badge(self, obj):
|
|
colors = {
|
|
'ROUTINE': 'green',
|
|
'URGENT': 'orange',
|
|
'STAT': 'red',
|
|
'EMERGENCY': 'darkred'
|
|
}
|
|
color = colors.get(obj.priority, 'gray')
|
|
return format_html(
|
|
'<span style="background-color: {}; color: white; padding: 3px 10px; border-radius: 3px;">{}</span>',
|
|
color,
|
|
obj.get_priority_display()
|
|
)
|
|
priority_badge.short_description = 'Priority'
|
|
|
|
def expiration_status(self, obj):
|
|
if not obj.expiration_date:
|
|
return '-'
|
|
|
|
if obj.is_expired:
|
|
return format_html('<span style="color: red;">Expired</span>')
|
|
elif obj.is_expiring_soon:
|
|
days = obj.days_until_expiry
|
|
return format_html('<span style="color: orange;">Expires in {} days</span>', days)
|
|
else:
|
|
return format_html('<span style="color: green;">Valid</span>')
|
|
expiration_status.short_description = 'Expiration'
|
|
|
|
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(ApprovalDocument)
|
|
class ApprovalDocumentAdmin(admin.ModelAdmin):
|
|
list_display = ('title', 'approval_request', 'document_type', 'file_size_mb', 'uploaded_at', 'uploaded_by')
|
|
list_filter = ('document_type', 'uploaded_at')
|
|
search_fields = ('title', 'description', 'approval_request__approval_number')
|
|
readonly_fields = ('document_id', 'file_size_mb', 'uploaded_at', 'uploaded_by')
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if not change:
|
|
obj.uploaded_by = request.user
|
|
if obj.file:
|
|
obj.file_size = obj.file.size
|
|
obj.mime_type = obj.file.content_type or 'application/octet-stream'
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(ApprovalStatusHistory)
|
|
class ApprovalStatusHistoryAdmin(admin.ModelAdmin):
|
|
list_display = ('approval_request', 'from_status', 'to_status', 'changed_at', 'changed_by')
|
|
list_filter = ('to_status', 'changed_at')
|
|
search_fields = ('approval_request__approval_number', 'reason', 'notes')
|
|
readonly_fields = ('changed_at', 'changed_by')
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if not change:
|
|
obj.changed_by = request.user
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(ApprovalCommunicationLog)
|
|
class ApprovalCommunicationLogAdmin(admin.ModelAdmin):
|
|
list_display = (
|
|
'approval_request',
|
|
'communication_type',
|
|
'subject',
|
|
'contact_person',
|
|
'communicated_at',
|
|
'follow_up_badge'
|
|
)
|
|
list_filter = ('communication_type', 'follow_up_required', 'communicated_at')
|
|
search_fields = (
|
|
'approval_request__approval_number',
|
|
'subject',
|
|
'message',
|
|
'contact_person'
|
|
)
|
|
readonly_fields = ('communication_id', 'communicated_at', 'communicated_by')
|
|
|
|
fieldsets = (
|
|
('Approval Request', {
|
|
'fields': ('approval_request',)
|
|
}),
|
|
('Communication Details', {
|
|
'fields': (
|
|
'communication_id',
|
|
'communication_type',
|
|
'contact_person',
|
|
'contact_number',
|
|
'subject',
|
|
'message',
|
|
'response'
|
|
)
|
|
}),
|
|
('Outcome & Follow-up', {
|
|
'fields': ('outcome', 'follow_up_required', 'follow_up_date')
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('communicated_at', 'communicated_by')
|
|
})
|
|
)
|
|
|
|
def follow_up_badge(self, obj):
|
|
if obj.follow_up_required:
|
|
if obj.follow_up_date and obj.follow_up_date < timezone.now().date():
|
|
return format_html('<span style="color: red;">Overdue</span>')
|
|
return format_html('<span style="color: orange;">Required</span>')
|
|
return format_html('<span style="color: green;">None</span>')
|
|
follow_up_badge.short_description = 'Follow-up'
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if not change:
|
|
obj.communicated_by = request.user
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
@admin.register(ApprovalTemplate)
|
|
class ApprovalTemplateAdmin(admin.ModelAdmin):
|
|
list_display = ('name', 'request_type', 'insurance_company', 'is_active', 'usage_count', 'created_at')
|
|
list_filter = ('request_type', 'is_active', 'created_at')
|
|
search_fields = ('name', 'description', 'insurance_company')
|
|
readonly_fields = ('template_id', 'usage_count', 'created_at', 'updated_at', 'created_by')
|
|
|
|
fieldsets = (
|
|
('Template Information', {
|
|
'fields': ('template_id', 'name', 'description', 'tenant')
|
|
}),
|
|
('Scope', {
|
|
'fields': ('request_type', 'insurance_company')
|
|
}),
|
|
('Template Content', {
|
|
'fields': (
|
|
'clinical_justification_template',
|
|
'medical_necessity_template',
|
|
'required_documents',
|
|
'required_codes'
|
|
)
|
|
}),
|
|
('Status & Usage', {
|
|
'fields': ('is_active', 'usage_count')
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
})
|
|
)
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if not change:
|
|
obj.created_by = request.user
|
|
super().save_model(request, obj, form, change)
|