Marwan Alwali 2780a2dc7c update
2025-09-16 15:10:57 +03:00

769 lines
22 KiB
Python

"""
Billing app admin configuration.
"""
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 decimal import Decimal
from .models import (
MedicalBill, BillLineItem, InsuranceClaim,
Payment, ClaimStatusUpdate, BillingConfiguration
)
class BillLineItemInline(admin.TabularInline):
"""
Inline admin for bill line items.
"""
model = BillLineItem
extra = 0
fields = [
'line_number', 'service_code', 'service_description',
'quantity', 'unit_price', 'total_price', 'status'
]
readonly_fields = ['total_price']
class InsuranceClaimInline(admin.TabularInline):
"""
Inline admin for insurance claims.
"""
model = InsuranceClaim
extra = 0
fields = [
'claim_number', 'claim_type', 'insurance_info',
'billed_amount', 'paid_amount', 'status'
]
readonly_fields = ['claim_number']
class PaymentInline(admin.TabularInline):
"""
Inline admin for payments.
"""
model = Payment
extra = 0
fields = [
'payment_number', 'payment_date', 'payment_amount',
'payment_method', 'payment_source', 'status'
]
readonly_fields = ['payment_number']
@admin.register(MedicalBill)
class MedicalBillAdmin(admin.ModelAdmin):
"""
Admin interface for medical bills.
"""
list_display = [
'bill_number', 'patient_name', 'bill_type', 'bill_date',
'total_amount', 'paid_amount', 'balance_amount',
'status', 'days_outstanding', 'payment_percentage_display'
]
list_filter = [
'tenant', 'bill_type', 'status', 'bill_date',
'payment_terms', 'collection_status'
]
search_fields = [
'bill_number', 'patient__first_name', 'patient__last_name',
'patient__mrn', 'attending_provider__first_name',
'attending_provider__last_name'
]
readonly_fields = [
'bill_id', 'bill_number', 'total_amount', 'balance_amount',
'days_outstanding', 'payment_percentage', 'is_overdue',
'created_at', 'updated_at'
]
fieldsets = [
('Bill Information', {
'fields': [
'bill_id', 'bill_number', 'tenant'
]
}),
('Patient Information', {
'fields': [
'patient'
]
}),
('Bill Type and Dates', {
'fields': [
'bill_type', 'service_date_from', 'service_date_to',
'bill_date', 'due_date'
]
}),
('Financial Information', {
'fields': [
'subtotal', 'tax_amount', 'discount_amount',
'adjustment_amount', 'total_amount', 'paid_amount',
'balance_amount'
]
}),
('Insurance Information', {
'fields': [
'primary_insurance', 'secondary_insurance'
]
}),
('Bill Status', {
'fields': [
'status', 'payment_terms'
]
}),
('Provider Information', {
'fields': [
'attending_provider', 'billing_provider'
]
}),
('Related Information', {
'fields': [
'encounter', 'admission'
],
'classes': ['collapse']
}),
('Collection Information', {
'fields': [
'collection_status', 'last_statement_date'
],
'classes': ['collapse']
}),
('Notes', {
'fields': [
'notes'
],
'classes': ['collapse']
}),
('Status Information', {
'fields': [
'days_outstanding', 'payment_percentage', 'is_overdue'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
inlines = [BillLineItemInline, InsuranceClaimInline, PaymentInline]
date_hierarchy = 'bill_date'
def patient_name(self, obj):
"""Display patient name."""
return obj.patient.get_full_name()
patient_name.short_description = 'Patient'
def payment_percentage_display(self, obj):
"""Display payment percentage with color coding."""
percentage = obj.payment_percentage
if percentage >= 100:
color = 'green'
elif percentage >= 50:
color = 'orange'
else:
color = 'red'
return format_html(
'<span style="color: {};">{:.1f}%</span>',
color, percentage
)
payment_percentage_display.short_description = 'Paid %'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs.select_related('patient', 'attending_provider')
@admin.register(BillLineItem)
class BillLineItemAdmin(admin.ModelAdmin):
"""
Admin interface for bill line items.
"""
list_display = [
'bill_number', 'line_number', 'service_code',
'service_description_short', 'service_date',
'quantity', 'unit_price', 'total_price',
'rendering_provider_name', 'status'
]
list_filter = [
'medical_bill__tenant', 'service_category', 'status',
'service_date', 'place_of_service'
]
search_fields = [
'medical_bill__bill_number', 'service_code',
'service_description', 'rendering_provider__first_name',
'rendering_provider__last_name'
]
readonly_fields = [
'line_item_id', 'total_price', 'patient', 'tenant',
'created_at', 'updated_at'
]
fieldsets = [
('Line Item Information', {
'fields': [
'line_item_id', 'medical_bill', 'line_number'
]
}),
('Service Information', {
'fields': [
'service_date', 'service_code', 'service_description',
'service_category'
]
}),
('Quantity and Pricing', {
'fields': [
'quantity', 'unit_of_measure', 'unit_price', 'total_price'
]
}),
('Modifiers', {
'fields': [
'modifier_1', 'modifier_2', 'modifier_3', 'modifier_4'
],
'classes': ['collapse']
}),
('Diagnosis Information', {
'fields': [
'primary_diagnosis', 'secondary_diagnoses'
],
'classes': ['collapse']
}),
('Provider Information', {
'fields': [
'rendering_provider', 'supervising_provider'
]
}),
('Place of Service', {
'fields': [
'place_of_service', 'revenue_code'
],
'classes': ['collapse']
}),
('Drug Information', {
'fields': [
'ndc_code', 'drug_quantity', 'drug_unit'
],
'classes': ['collapse']
}),
('Line Item Status', {
'fields': [
'status'
]
}),
('Notes', {
'fields': [
'notes'
],
'classes': ['collapse']
}),
('Related Information', {
'fields': [
'patient', 'tenant'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at'
],
'classes': ['collapse']
})
]
def bill_number(self, obj):
"""Display bill number."""
return obj.medical_bill.bill_number
bill_number.short_description = 'Bill'
def service_description_short(self, obj):
"""Display shortened service description."""
if obj.service_description:
return obj.service_description[:50] + "..." if len(obj.service_description) > 50 else obj.service_description
return "-"
service_description_short.short_description = 'Service'
def rendering_provider_name(self, obj):
"""Display rendering provider name."""
return obj.rendering_provider.get_full_name() if obj.rendering_provider else "-"
rendering_provider_name.short_description = 'Provider'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(medical_bill__tenant=request.user.tenant)
return qs.select_related('medical_bill', 'rendering_provider')
@admin.register(InsuranceClaim)
class InsuranceClaimAdmin(admin.ModelAdmin):
"""
Admin interface for insurance claims.
"""
list_display = [
'claim_number', 'patient_name', 'insurance_company',
'claim_type', 'submission_date', 'billed_amount',
'paid_amount', 'status', 'days_pending',
'payment_percentage_display'
]
list_filter = [
'medical_bill__tenant', 'claim_type', 'status',
'submission_date', 'response_date'
]
search_fields = [
'claim_number', 'medical_bill__bill_number',
'medical_bill__patient__first_name',
'medical_bill__patient__last_name',
'insurance_info__insurance_company'
]
readonly_fields = [
'claim_id', 'claim_number', 'patient', 'tenant',
'days_pending', 'payment_percentage',
'created_at', 'updated_at'
]
fieldsets = [
('Claim Information', {
'fields': [
'claim_id', 'claim_number', 'medical_bill'
]
}),
('Insurance Information', {
'fields': [
'insurance_info', 'claim_type'
]
}),
('Claim Dates', {
'fields': [
'submission_date', 'service_date_from', 'service_date_to'
]
}),
('Financial Information', {
'fields': [
'billed_amount', 'allowed_amount', 'paid_amount',
'patient_responsibility', 'deductible_amount',
'coinsurance_amount', 'copay_amount'
]
}),
('Claim Status', {
'fields': [
'status'
]
}),
('Processing Information', {
'fields': [
'clearinghouse', 'batch_number'
],
'classes': ['collapse']
}),
('Response Information', {
'fields': [
'response_date', 'check_number', 'check_date'
],
'classes': ['collapse']
}),
('Denial Information', {
'fields': [
'denial_reason', 'denial_code'
],
'classes': ['collapse']
}),
('Prior Authorization', {
'fields': [
'prior_auth_number'
],
'classes': ['collapse']
}),
('Resubmission Information', {
'fields': [
'original_claim', 'resubmission_count'
],
'classes': ['collapse']
}),
('Notes', {
'fields': [
'notes'
],
'classes': ['collapse']
}),
('Status Information', {
'fields': [
'days_pending', 'payment_percentage'
],
'classes': ['collapse']
}),
('Related Information', {
'fields': [
'patient', 'tenant'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
inlines = []
date_hierarchy = 'submission_date'
def patient_name(self, obj):
"""Display patient name."""
return obj.patient.get_full_name()
patient_name.short_description = 'Patient'
def insurance_company(self, obj):
"""Display insurance company."""
return obj.insurance_info.insurance_company
insurance_company.short_description = 'Insurance'
def payment_percentage_display(self, obj):
"""Display payment percentage with color coding."""
percentage = obj.payment_percentage
if percentage >= 100:
color = 'green'
elif percentage >= 50:
color = 'orange'
else:
color = 'red'
return format_html(
'<span style="color: {};">{}%</span>',
color, percentage
)
payment_percentage_display.short_description = 'Paid %'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(medical_bill__tenant=request.user.tenant)
return qs.select_related('medical_bill__patient', 'insurance_info')
@admin.register(Payment)
class PaymentAdmin(admin.ModelAdmin):
"""
Admin interface for payments.
"""
list_display = [
'payment_number', 'patient_name', 'payment_date',
'payment_amount', 'payment_method', 'payment_source',
'status', 'received_by_name'
]
list_filter = [
'medical_bill__tenant', 'payment_method', 'payment_source',
'status', 'payment_date', 'deposit_date'
]
search_fields = [
'payment_number', 'medical_bill__bill_number',
'medical_bill__patient__first_name',
'medical_bill__patient__last_name',
'check_number', 'authorization_code'
]
readonly_fields = [
'payment_id', 'payment_number', 'patient', 'tenant',
'net_payment', 'created_at', 'updated_at'
]
fieldsets = [
('Payment Information', {
'fields': [
'payment_id', 'payment_number', 'medical_bill'
]
}),
('Payment Details', {
'fields': [
'payment_date', 'payment_amount', 'payment_method',
'payment_source'
]
}),
('Check Information', {
'fields': [
'check_number', 'bank_name', 'routing_number'
],
'classes': ['collapse']
}),
('Credit Card Information', {
'fields': [
'card_type', 'card_last_four', 'authorization_code',
'transaction_id'
],
'classes': ['collapse']
}),
('Insurance Payment Information', {
'fields': [
'insurance_claim', 'eob_number'
],
'classes': ['collapse']
}),
('Payment Status', {
'fields': [
'status'
]
}),
('Deposit Information', {
'fields': [
'deposit_date', 'deposit_slip'
],
'classes': ['collapse']
}),
('Refund Information', {
'fields': [
'refund_amount', 'refund_date', 'refund_reason'
],
'classes': ['collapse']
}),
('Staff Information', {
'fields': [
'received_by', 'processed_by'
]
}),
('Notes', {
'fields': [
'notes'
],
'classes': ['collapse']
}),
('Calculated Fields', {
'fields': [
'net_payment'
],
'classes': ['collapse']
}),
('Related Information', {
'fields': [
'patient', 'tenant'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at'
],
'classes': ['collapse']
})
]
date_hierarchy = 'payment_date'
def patient_name(self, obj):
"""Display patient name."""
return obj.patient.get_full_name()
patient_name.short_description = 'Patient'
def received_by_name(self, obj):
"""Display received by name."""
return obj.received_by.get_full_name() if obj.received_by else "-"
received_by_name.short_description = 'Received By'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(medical_bill__tenant=request.user.tenant)
return qs.select_related('medical_bill__patient', 'received_by')
@admin.register(ClaimStatusUpdate)
class ClaimStatusUpdateAdmin(admin.ModelAdmin):
"""
Admin interface for claim status updates.
"""
list_display = [
'claim_number', 'patient_name', 'status_date',
'previous_status', 'new_status', 'update_source',
'updated_by_name'
]
list_filter = [
'insurance_claim__medical_bill__tenant', 'new_status',
'previous_status', 'update_source', 'status_date'
]
search_fields = [
'insurance_claim__claim_number',
'insurance_claim__medical_bill__patient__first_name',
'insurance_claim__medical_bill__patient__last_name',
'response_code', 'response_message'
]
readonly_fields = [
'update_id', 'patient', 'tenant', 'created_at'
]
fieldsets = [
('Update Information', {
'fields': [
'update_id', 'insurance_claim'
]
}),
('Status Information', {
'fields': [
'previous_status', 'new_status', 'status_date'
]
}),
('Update Details', {
'fields': [
'update_source'
]
}),
('Response Information', {
'fields': [
'response_code', 'response_message'
],
'classes': ['collapse']
}),
('Financial Updates', {
'fields': [
'allowed_amount', 'paid_amount', 'patient_responsibility'
],
'classes': ['collapse']
}),
('Staff Information', {
'fields': [
'updated_by'
]
}),
('Notes', {
'fields': [
'notes'
],
'classes': ['collapse']
}),
('Related Information', {
'fields': [
'patient', 'tenant'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at'
],
'classes': ['collapse']
})
]
date_hierarchy = 'status_date'
def claim_number(self, obj):
"""Display claim number."""
return obj.insurance_claim.claim_number
claim_number.short_description = 'Claim'
def patient_name(self, obj):
"""Display patient name."""
return obj.patient.get_full_name()
patient_name.short_description = 'Patient'
def updated_by_name(self, obj):
"""Display updated by name."""
return obj.updated_by.get_full_name() if obj.updated_by else "-"
updated_by_name.short_description = 'Updated By'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(insurance_claim__medical_bill__tenant=request.user.tenant)
return qs.select_related('insurance_claim__medical_bill__patient', 'updated_by')
@admin.register(BillingConfiguration)
class BillingConfigurationAdmin(admin.ModelAdmin):
"""
Admin interface for billing configuration.
"""
list_display = [
'tenant_name', 'default_payment_terms', 'tax_rate',
'tax_exempt', 'statement_frequency', 'auto_submit_claims',
'accept_credit_cards', 'payment_portal_enabled'
]
list_filter = [
'tenant', 'default_payment_terms', 'tax_exempt',
'statement_frequency', 'auto_submit_claims',
'accept_credit_cards', 'payment_portal_enabled'
]
search_fields = [
'tenant__name', 'primary_clearinghouse', 'secondary_clearinghouse'
]
readonly_fields = [
'config_id', 'created_at', 'updated_at'
]
fieldsets = [
('Configuration Information', {
'fields': [
'config_id', 'tenant'
]
}),
('Billing Settings', {
'fields': [
'default_payment_terms'
]
}),
('Tax Settings', {
'fields': [
'tax_rate', 'tax_exempt'
]
}),
('Statement Settings', {
'fields': [
'statement_frequency', 'statement_message'
]
}),
('Collection Settings', {
'fields': [
'first_notice_days', 'second_notice_days',
'final_notice_days', 'collections_days'
]
}),
('Interest Settings', {
'fields': [
'apply_interest', 'interest_rate'
],
'classes': ['collapse']
}),
('Payment Settings', {
'fields': [
'accept_credit_cards', 'accept_ach', 'payment_portal_enabled'
]
}),
('Claim Settings', {
'fields': [
'auto_submit_claims', 'claim_submission_frequency'
]
}),
('Clearinghouse Settings', {
'fields': [
'primary_clearinghouse', 'secondary_clearinghouse'
],
'classes': ['collapse']
}),
('Reporting Settings', {
'fields': [
'aging_buckets'
],
'classes': ['collapse']
}),
('Metadata', {
'fields': [
'created_at', 'updated_at', 'created_by'
],
'classes': ['collapse']
})
]
def tenant_name(self, obj):
"""Display tenant name."""
return obj.tenant.name
tenant_name.short_description = 'Organization'
def get_queryset(self, request):
"""Filter by user's tenant."""
qs = super().get_queryset(request)
if hasattr(request.user, 'tenant'):
qs = qs.filter(tenant=request.user.tenant)
return qs.select_related('tenant')
# Customize admin site
admin.site.site_header = "Hospital Management System - Billing"
admin.site.site_title = "Billing Admin"
admin.site.index_title = "Billing Administration"