553 lines
17 KiB
Python
553 lines
17 KiB
Python
"""
|
|
Admin configuration for patients app.
|
|
"""
|
|
|
|
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from .models import *
|
|
|
|
|
|
class EmergencyContactInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for emergency contacts.
|
|
"""
|
|
model = EmergencyContact
|
|
extra = 1
|
|
fields = [
|
|
'first_name', 'last_name', 'relationship', 'phone_number',
|
|
'priority', 'is_authorized_for_medical_decisions', 'is_active'
|
|
]
|
|
|
|
|
|
class InsuranceInfoInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for insurance information.
|
|
"""
|
|
model = InsuranceInfo
|
|
extra = 1
|
|
fields = [
|
|
'insurance_type', 'insurance_company', 'policy_number',
|
|
'effective_date', 'is_verified', 'is_active'
|
|
]
|
|
|
|
|
|
class ConsentFormInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for consent forms.
|
|
"""
|
|
model = ConsentForm
|
|
extra = 0
|
|
fields = ['template', 'status', 'effective_date', 'patient_signed_at']
|
|
readonly_fields = ['patient_signed_at']
|
|
|
|
|
|
class PatientNoteInline(admin.TabularInline):
|
|
"""
|
|
Inline admin for patient notes.
|
|
"""
|
|
model = PatientNote
|
|
extra = 0
|
|
fields = ['title', 'category', 'priority', 'is_alert', 'created_at']
|
|
readonly_fields = ['created_at']
|
|
|
|
|
|
@admin.register(PatientProfile)
|
|
class PatientProfileAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for PatientProfile model.
|
|
"""
|
|
list_display = [
|
|
'mrn', 'get_full_name', 'date_of_birth', 'age', 'gender',
|
|
'mobile_number', 'is_active',
|
|
]
|
|
list_filter = [
|
|
'tenant', 'gender', 'marital_status',
|
|
'is_active', 'is_deceased', 'is_vip', 'confidential_patient',
|
|
|
|
]
|
|
search_fields = [
|
|
'mrn', 'id_number', 'first_name', 'last_name', 'email', 'mobile_number',
|
|
]
|
|
ordering = ['last_name', 'first_name']
|
|
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': (
|
|
'tenant', 'mrn', 'first_name', 'last_name', 'middle_name',
|
|
'preferred_name', 'suffix', 'photo'
|
|
)
|
|
}),
|
|
('Demographics', {
|
|
'fields': (
|
|
'date_of_birth', 'gender', 'marital_status'
|
|
)
|
|
}),
|
|
('Contact Information', {
|
|
'fields': (
|
|
'email', 'phone_number', 'mobile_number',
|
|
'address_line_1', 'address_line_2', 'city', 'state',
|
|
'zip_code', 'country'
|
|
)
|
|
}),
|
|
('Language and Communication', {
|
|
'fields': (
|
|
'primary_language', 'interpreter_needed', 'communication_preference'
|
|
)
|
|
}),
|
|
('Employment', {
|
|
'fields': ('employer', 'occupation')
|
|
}),
|
|
('Healthcare Information', {
|
|
'fields': (
|
|
'primary_care_physician', 'referring_physician',
|
|
'allergies', 'medical_alerts'
|
|
)
|
|
}),
|
|
('Advance Directives', {
|
|
'fields': ('has_advance_directive', 'advance_directive_type')
|
|
}),
|
|
('Status and Flags', {
|
|
'fields': (
|
|
'is_active', 'is_deceased', 'date_of_death',
|
|
'is_vip', 'confidential_patient'
|
|
)
|
|
}),
|
|
('Registration', {
|
|
'fields': ( 'registered_by', 'last_visit_date'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('patient_id', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['patient_id', 'created_at', 'updated_at', 'age']
|
|
inlines = [EmergencyContactInline, InsuranceInfoInline, ConsentFormInline, PatientNoteInline]
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related('tenant', 'registered_by')
|
|
|
|
def age(self, obj):
|
|
return obj.age
|
|
age.short_description = 'Age'
|
|
|
|
|
|
@admin.register(EmergencyContact)
|
|
class EmergencyContactAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for EmergencyContact model.
|
|
"""
|
|
list_display = [
|
|
'get_full_name', 'patient', 'relationship', 'phone_number',
|
|
'priority', 'is_authorized_for_medical_decisions', 'is_active'
|
|
]
|
|
list_filter = [
|
|
'relationship', 'priority', 'is_authorized_for_medical_decisions',
|
|
'is_authorized_for_financial_decisions', 'is_authorized_for_information',
|
|
'is_active'
|
|
]
|
|
search_fields = [
|
|
'first_name', 'last_name', 'phone_number', 'email',
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn'
|
|
]
|
|
ordering = ['patient__last_name', 'priority', 'last_name']
|
|
|
|
fieldsets = (
|
|
('Contact Information', {
|
|
'fields': (
|
|
'patient', 'first_name', 'last_name', 'relationship'
|
|
)
|
|
}),
|
|
('Contact Details', {
|
|
'fields': (
|
|
'phone_number', 'mobile_number', 'email'
|
|
)
|
|
}),
|
|
('Address', {
|
|
'fields': (
|
|
'address_line_1', 'address_line_2', 'city', 'state', 'zip_code'
|
|
)
|
|
}),
|
|
('Authorization', {
|
|
'fields': (
|
|
'priority', 'is_authorized_for_medical_decisions',
|
|
'is_authorized_for_financial_decisions', 'is_authorized_for_information'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': ('is_active', 'notes')
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related('patient')
|
|
|
|
|
|
@admin.register(InsuranceInfo)
|
|
class InsuranceInfoAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for InsuranceInfo model.
|
|
"""
|
|
list_display = [
|
|
'patient', 'insurance_company', 'insurance_type', 'policy_number',
|
|
'is_verified', 'is_coverage_active', 'effective_date'
|
|
]
|
|
list_filter = [
|
|
'insurance_type', 'plan_type', 'is_verified', 'is_active',
|
|
'subscriber_relationship', 'effective_date'
|
|
]
|
|
search_fields = [
|
|
'insurance_company', 'plan_name', 'policy_number', 'group_number',
|
|
'subscriber_name', 'patient__first_name', 'patient__last_name', 'patient__mrn'
|
|
]
|
|
ordering = ['patient__last_name', 'insurance_type']
|
|
|
|
fieldsets = (
|
|
('Patient Information', {
|
|
'fields': ('patient',)
|
|
}),
|
|
('Insurance Details', {
|
|
'fields': (
|
|
'insurance_type', 'insurance_company', 'plan_name', 'plan_type'
|
|
)
|
|
}),
|
|
('Policy Information', {
|
|
'fields': ('policy_number', 'group_number')
|
|
}),
|
|
('Subscriber Information', {
|
|
'fields': (
|
|
'subscriber_name', 'subscriber_relationship',
|
|
'subscriber_dob',
|
|
)
|
|
}),
|
|
('Coverage', {
|
|
'fields': ('effective_date', 'termination_date')
|
|
}),
|
|
('Financial Information', {
|
|
'fields': ('copay_amount', 'deductible_amount', 'out_of_pocket_max')
|
|
}),
|
|
('Verification', {
|
|
'fields': ('is_verified', 'verification_date', 'verified_by')
|
|
}),
|
|
('Authorization', {
|
|
'fields': (
|
|
'requires_authorization', 'authorization_number', 'authorization_expiry'
|
|
)
|
|
}),
|
|
('Status', {
|
|
'fields': ('is_active', 'notes')
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related('patient', 'verified_by')
|
|
|
|
def is_coverage_active(self, obj):
|
|
return obj.is_coverage_active
|
|
is_coverage_active.boolean = True
|
|
is_coverage_active.short_description = 'Coverage Active'
|
|
|
|
|
|
@admin.register(ConsentTemplate)
|
|
class ConsentTemplateAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for ConsentTemplate model.
|
|
"""
|
|
list_display = [
|
|
'name', 'category', 'version', 'is_active',
|
|
'effective_date', 'created_by'
|
|
]
|
|
list_filter = [
|
|
'tenant', 'category', 'is_active', 'requires_signature',
|
|
'requires_witness', 'requires_guardian', 'effective_date'
|
|
]
|
|
search_fields = ['name', 'description', 'category']
|
|
ordering = ['category', 'name']
|
|
|
|
fieldsets = (
|
|
('Template Information', {
|
|
'fields': ('tenant', 'name', 'description', 'category')
|
|
}),
|
|
('Content', {
|
|
'fields': ('content',)
|
|
}),
|
|
('Requirements', {
|
|
'fields': (
|
|
'requires_signature', 'requires_witness', 'requires_guardian'
|
|
)
|
|
}),
|
|
('Validity', {
|
|
'fields': ('is_active', 'version', 'effective_date', 'expiry_date')
|
|
}),
|
|
('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(ConsentForm)
|
|
class ConsentFormAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for ConsentForm model.
|
|
"""
|
|
list_display = [
|
|
'patient', 'template', 'status', 'is_valid',
|
|
'patient_signed_at', 'effective_date'
|
|
]
|
|
list_filter = [
|
|
'status', 'template__category', 'patient_signed_at',
|
|
'effective_date', 'created_at'
|
|
]
|
|
search_fields = [
|
|
'patient__first_name', 'patient__last_name', 'patient__mrn',
|
|
'template__name', 'guardian_name', 'witness_name', 'provider_name'
|
|
]
|
|
ordering = ['-created_at']
|
|
|
|
fieldsets = (
|
|
('Consent Information', {
|
|
'fields': ('patient', 'template', 'status')
|
|
}),
|
|
('Patient Signature', {
|
|
'fields': (
|
|
'patient_signature', 'patient_signed_at', 'patient_ip_address'
|
|
)
|
|
}),
|
|
('Guardian Signature', {
|
|
'fields': (
|
|
'guardian_signature', 'guardian_signed_at',
|
|
'guardian_name', 'guardian_relationship'
|
|
)
|
|
}),
|
|
('Witness Signature', {
|
|
'fields': (
|
|
'witness_signature', 'witness_signed_at',
|
|
'witness_name', 'witness_title'
|
|
)
|
|
}),
|
|
('Provider Information', {
|
|
'fields': (
|
|
'provider_name', 'provider_signature', 'provider_signed_at'
|
|
)
|
|
}),
|
|
('Validity', {
|
|
'fields': ('effective_date', 'expiry_date')
|
|
}),
|
|
('Revocation', {
|
|
'fields': ('revoked_at', 'revoked_by', 'revocation_reason')
|
|
}),
|
|
('Notes', {
|
|
'fields': ('notes',)
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('consent_id', 'created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['consent_id', 'created_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related(
|
|
'patient', 'template', 'created_by', 'revoked_by'
|
|
)
|
|
|
|
def is_valid(self, obj):
|
|
return obj.is_valid
|
|
is_valid.boolean = True
|
|
is_valid.short_description = 'Valid'
|
|
|
|
|
|
@admin.register(PatientNote)
|
|
class PatientNoteAdmin(admin.ModelAdmin):
|
|
"""
|
|
Admin configuration for PatientNote model.
|
|
"""
|
|
list_display = [
|
|
'title', 'patient', 'category', 'priority',
|
|
'is_alert', 'is_confidential', 'created_by', 'created_at'
|
|
]
|
|
list_filter = [
|
|
'category', 'priority', 'is_alert', 'is_confidential',
|
|
'is_active', 'created_at'
|
|
]
|
|
search_fields = [
|
|
'title', 'content', 'patient__first_name',
|
|
'patient__last_name', 'patient__mrn'
|
|
]
|
|
ordering = ['-created_at']
|
|
|
|
fieldsets = (
|
|
('Note Information', {
|
|
'fields': ('patient', 'title', 'content')
|
|
}),
|
|
('Classification', {
|
|
'fields': ('category', 'priority')
|
|
}),
|
|
('Visibility', {
|
|
'fields': ('is_confidential', 'is_alert', 'is_active')
|
|
}),
|
|
('Metadata', {
|
|
'fields': ('note_id', 'created_at', 'updated_at', 'created_by'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
)
|
|
|
|
readonly_fields = ['note_id', 'created_at', 'updated_at']
|
|
|
|
def get_queryset(self, request):
|
|
return super().get_queryset(request).select_related('patient', 'created_by')
|
|
|
|
class ClaimDocumentInline(admin.TabularInline):
|
|
"""Inline admin for claim documents."""
|
|
model = ClaimDocument
|
|
extra = 0
|
|
readonly_fields = ['uploaded_at', 'file_size']
|
|
|
|
|
|
class ClaimStatusHistoryInline(admin.TabularInline):
|
|
"""Inline admin for claim status history."""
|
|
model = ClaimStatusHistory
|
|
extra = 0
|
|
readonly_fields = ['changed_at']
|
|
ordering = ['-changed_at']
|
|
fields = [ 'changed_at']
|
|
|
|
|
|
@admin.register(InsuranceClaim)
|
|
class InsuranceClaimAdmin(admin.ModelAdmin):
|
|
"""Admin interface for InsuranceClaim."""
|
|
|
|
list_display = [
|
|
'claim_number', 'patient', 'claim_type', 'priority',
|
|
'billed_amount', 'approved_amount', 'service_date', 'submitted_date'
|
|
]
|
|
list_filter = [
|
|
'claim_type', 'priority', 'service_date',
|
|
'submitted_date', 'processed_date'
|
|
]
|
|
search_fields = [
|
|
'claim_number', 'patient__first_name', 'patient__last_name',
|
|
'service_provider', 'facility_name', 'primary_diagnosis_code'
|
|
]
|
|
ordering = ['-created_at']
|
|
|
|
fieldsets = (
|
|
('Claim Information', {
|
|
'fields': ('claim_number', 'patient', 'insurance_info', 'claim_type', 'status', 'priority')
|
|
}),
|
|
('Service Information', {
|
|
'fields': (
|
|
'service_date', 'service_provider', 'service_provider_license',
|
|
'facility_name', 'facility_license'
|
|
)
|
|
}),
|
|
('Medical Codes', {
|
|
'fields': (
|
|
'primary_diagnosis_code', 'primary_diagnosis_description',
|
|
'secondary_diagnosis_codes', 'procedure_codes'
|
|
)
|
|
}),
|
|
('Financial Information', {
|
|
'fields': (
|
|
'billed_amount', 'approved_amount', 'paid_amount',
|
|
'patient_responsibility', 'discount_amount'
|
|
)
|
|
}),
|
|
('Processing Dates', {
|
|
'fields': ('submitted_date', 'processed_date', 'payment_date')
|
|
}),
|
|
('Saudi-specific Information', {
|
|
'fields': ('saudi_id_number', 'insurance_card_number', 'authorization_number')
|
|
}),
|
|
('Denial/Appeal Information', {
|
|
'fields': ('denial_reason', 'denial_code', 'appeal_date', 'appeal_reason'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Additional Information', {
|
|
'fields': ('notes', 'attachments'),
|
|
'classes': ('collapse',)
|
|
})
|
|
)
|
|
|
|
readonly_fields = ['created_at', 'updated_at']
|
|
inlines = [ClaimDocumentInline, ClaimStatusHistoryInline]
|
|
|
|
def get_queryset(self, request):
|
|
"""Optimize queryset with select_related."""
|
|
return super().get_queryset(request).select_related('patient', 'insurance_info')
|
|
|
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
|
"""Optimize foreign key fields."""
|
|
if db_field.name == "patient":
|
|
kwargs["queryset"] = PatientProfile.objects.select_related('tenant')
|
|
elif db_field.name == "insurance_info":
|
|
kwargs["queryset"] = InsuranceInfo.objects.select_related('patient')
|
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
|
|
|
|
|
@admin.register(ClaimDocument)
|
|
class ClaimDocumentAdmin(admin.ModelAdmin):
|
|
"""Admin interface for ClaimDocument."""
|
|
|
|
list_display = [
|
|
'title', 'claim', 'document_type', 'file_size_display',
|
|
'mime_type', 'uploaded_at', 'uploaded_by'
|
|
]
|
|
list_filter = ['document_type', 'mime_type', 'uploaded_at']
|
|
search_fields = ['title', 'claim__claim_number', 'description']
|
|
ordering = ['-uploaded_at']
|
|
|
|
def file_size_display(self, obj):
|
|
"""Display file size in human readable format."""
|
|
size = obj.file_size
|
|
for unit in ['B', 'KB', 'MB', 'GB']:
|
|
if size < 1024.0:
|
|
return f"{size:.1f} {unit}"
|
|
size /= 1024.0
|
|
return f"{size:.1f} TB"
|
|
|
|
file_size_display.short_description = 'File Size'
|
|
|
|
|
|
@admin.register(ClaimStatusHistory)
|
|
class ClaimStatusHistoryAdmin(admin.ModelAdmin):
|
|
"""Admin interface for ClaimStatusHistory."""
|
|
|
|
list_display = [
|
|
'claim', 'from_status', 'to_status', 'changed_at', 'changed_by'
|
|
]
|
|
list_filter = ['from_status', 'to_status', 'changed_at']
|
|
search_fields = ['claim__claim_number', 'reason', 'notes']
|
|
ordering = ['-changed_at']
|
|
|
|
def get_queryset(self, request):
|
|
"""Optimize queryset with select_related."""
|
|
return super().get_queryset(request).select_related('claim', 'changed_by')
|
|
|
|
|
|
# Custom admin site configuration
|
|
admin.site.site_header = "Hospital Management System - Patients"
|
|
admin.site.site_title = "HMS Patients Admin"
|
|
admin.site.index_title = "Patients Administration"
|
|
|