Marwan Alwali 23158e9fbf update
2025-09-08 03:00:23 +03:00

1576 lines
56 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Billing app forms for hospital management system.
Provides forms for medical billing, insurance claims, and revenue cycle management.
"""
from django import forms
from django.core.exceptions import ValidationError
from django.utils import timezone
from decimal import Decimal
from datetime import date, datetime
from .models import (
MedicalBill, BillLineItem, InsuranceClaim, Payment,
ClaimStatusUpdate, BillingConfiguration
)
from patients.models import PatientProfile, InsuranceInfo
from accounts.models import User
from emr.models import Encounter
from inpatients.models import Admission
class MedicalBillForm(forms.ModelForm):
"""
Form for creating and editing medical bills.
"""
class Meta:
model = MedicalBill
fields = [
'patient', 'bill_type', 'service_date_from', 'service_date_to',
'bill_date', 'due_date', 'attending_provider', 'billing_provider',
'primary_insurance', 'secondary_insurance', 'encounter', 'admission',
'subtotal', 'tax_amount', 'discount_amount', 'adjustment_amount',
'payment_terms', 'notes'
]
widgets = {
'patient': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'bill_type': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'service_date_from': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'service_date_to': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'bill_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'due_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'attending_provider': forms.Select(attrs={
'class': 'form-select'
}),
'billing_provider': forms.Select(attrs={
'class': 'form-select'
}),
'primary_insurance': forms.Select(attrs={
'class': 'form-select'
}),
'secondary_insurance': forms.Select(attrs={
'class': 'form-select'
}),
'encounter': forms.Select(attrs={
'class': 'form-select'
}),
'admission': forms.Select(attrs={
'class': 'form-select'
}),
'subtotal': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'tax_amount': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'discount_amount': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'adjustment_amount': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01'
}),
'payment_terms': forms.Select(attrs={
'class': 'form-select'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Additional notes about this bill...'
})
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if self.user and hasattr(self.user, 'tenant'):
# Filter choices by tenant
self.fields['patient'].queryset = PatientProfile.objects.filter(
tenant=self.user.tenant
)
self.fields['attending_provider'].queryset = User.objects.filter(
tenant=self.user.tenant,
is_active=True
)
self.fields['billing_provider'].queryset = User.objects.filter(
tenant=self.user.tenant,
is_active=True
)
self.fields['encounter'].queryset = Encounter.objects.filter(
tenant=self.user.tenant
)
self.fields['admission'].queryset = Admission.objects.filter(
tenant=self.user.tenant
)
def clean(self):
cleaned_data = super().clean()
service_date_from = cleaned_data.get('service_date_from')
service_date_to = cleaned_data.get('service_date_to')
bill_date = cleaned_data.get('bill_date')
due_date = cleaned_data.get('due_date')
# Validate service date range
if service_date_from and service_date_to:
if service_date_from > service_date_to:
raise ValidationError({
'service_date_to': 'Service end date must be after start date.'
})
# Validate bill date
if bill_date and service_date_to:
if bill_date < service_date_to:
raise ValidationError({
'bill_date': 'Bill date cannot be before service end date.'
})
# Validate due date
if due_date and bill_date:
if due_date < bill_date:
raise ValidationError({
'due_date': 'Due date cannot be before bill date.'
})
return cleaned_data
class BillLineItemForm(forms.ModelForm):
"""
Form for creating and editing bill line items.
"""
class Meta:
model = BillLineItem
fields = [
'line_number', 'service_date', 'service_code', 'service_description',
'service_category', 'quantity', 'unit_of_measure', 'unit_price',
'modifier_1', 'modifier_2', 'modifier_3', 'modifier_4',
'primary_diagnosis', 'secondary_diagnoses', 'rendering_provider',
'supervising_provider', 'place_of_service', 'revenue_code',
'ndc_code', 'drug_quantity', 'drug_unit', 'notes'
]
widgets = {
'line_number': forms.NumberInput(attrs={
'class': 'form-control',
'min': '1'
}),
'service_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'service_code': forms.TextInput(attrs={
'class': 'form-control',
'required': True,
'placeholder': 'CPT/HCPCS code'
}),
'service_description': forms.TextInput(attrs={
'class': 'form-control',
'required': True,
'placeholder': 'Service description'
}),
'service_category': forms.Select(attrs={
'class': 'form-select'
}),
'quantity': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0.01',
'required': True
}),
'unit_of_measure': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'UN (Units)'
}),
'unit_price': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0',
'required': True
}),
'modifier_1': forms.TextInput(attrs={
'class': 'form-control',
'maxlength': '2'
}),
'modifier_2': forms.TextInput(attrs={
'class': 'form-control',
'maxlength': '2'
}),
'modifier_3': forms.TextInput(attrs={
'class': 'form-control',
'maxlength': '2'
}),
'modifier_4': forms.TextInput(attrs={
'class': 'form-control',
'maxlength': '2'
}),
'primary_diagnosis': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'ICD-10 code'
}),
'secondary_diagnoses': forms.Textarea(attrs={
'class': 'form-control',
'rows': 2,
'placeholder': 'Additional ICD-10 codes (comma-separated)'
}),
'rendering_provider': forms.Select(attrs={
'class': 'form-select'
}),
'supervising_provider': forms.Select(attrs={
'class': 'form-select'
}),
'place_of_service': forms.TextInput(attrs={
'class': 'form-control',
'maxlength': '2'
}),
'revenue_code': forms.TextInput(attrs={
'class': 'form-control',
'maxlength': '4'
}),
'ndc_code': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'NDC code for drugs'
}),
'drug_quantity': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
}),
'drug_unit': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'ML, GM, etc.'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Line item notes...'
})
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
if self.user and hasattr(self.user, 'tenant'):
# Filter provider choices by tenant
self.fields['rendering_provider'].queryset = User.objects.filter(
tenant=self.user.tenant,
is_active=True
)
self.fields['supervising_provider'].queryset = User.objects.filter(
tenant=self.user.tenant,
is_active=True
)
def clean(self):
cleaned_data = super().clean()
quantity = cleaned_data.get('quantity')
unit_price = cleaned_data.get('unit_price')
# Validate quantity and unit price
if quantity and quantity <= 0:
raise ValidationError({
'quantity': 'Quantity must be greater than zero.'
})
if unit_price and unit_price < 0:
raise ValidationError({
'unit_price': 'Unit price cannot be negative.'
})
return cleaned_data
class InsuranceClaimForm(forms.ModelForm):
"""
Form for creating and editing insurance claims.
"""
class Meta:
model = InsuranceClaim
fields = [
'insurance_info', 'claim_type', 'service_date_from', 'service_date_to',
'billed_amount', 'clearinghouse', 'batch_number', 'prior_auth_number',
'notes'
]
widgets = {
'insurance_info': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'claim_type': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'service_date_from': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'service_date_to': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'billed_amount': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0',
'required': True
}),
'clearinghouse': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Clearinghouse name'
}),
'batch_number': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Batch number'
}),
'prior_auth_number': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Prior authorization number'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Claim notes...'
})
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
self.medical_bill = kwargs.pop('medical_bill', None)
super().__init__(*args, **kwargs)
if self.medical_bill:
# Filter insurance info by patient
self.fields['insurance_info'].queryset = InsuranceInfo.objects.filter(
patient=self.medical_bill.patient,
is_active=True
)
# Set default values from medical bill
self.fields['service_date_from'].initial = self.medical_bill.service_date_from
self.fields['service_date_to'].initial = self.medical_bill.service_date_to
self.fields['billed_amount'].initial = self.medical_bill.total_amount
def clean(self):
cleaned_data = super().clean()
service_date_from = cleaned_data.get('service_date_from')
service_date_to = cleaned_data.get('service_date_to')
billed_amount = cleaned_data.get('billed_amount')
# Validate service date range
if service_date_from and service_date_to:
if service_date_from > service_date_to:
raise ValidationError({
'service_date_to': 'Service end date must be after start date.'
})
# Validate billed amount
if billed_amount and billed_amount <= 0:
raise ValidationError({
'billed_amount': 'Billed amount must be greater than zero.'
})
return cleaned_data
class PaymentForm(forms.ModelForm):
"""
Form for creating and editing payments.
"""
class Meta:
model = Payment
fields = [
'payment_date', 'payment_amount', 'payment_method', 'payment_source',
'check_number', 'bank_name', 'routing_number', 'card_type',
'card_last_four', 'authorization_code', 'transaction_id',
'insurance_claim', 'eob_number', 'received_by', 'processed_by',
'notes'
]
widgets = {
'payment_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date',
'required': True
}),
'payment_amount': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0.01',
'required': True
}),
'payment_method': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'payment_source': forms.Select(attrs={
'class': 'form-select',
'required': True
}),
'check_number': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Check number'
}),
'bank_name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Bank name'
}),
'routing_number': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Routing number'
}),
'card_type': forms.Select(attrs={
'class': 'form-select'
}),
'card_last_four': forms.TextInput(attrs={
'class': 'form-control',
'maxlength': '4',
'placeholder': 'Last 4 digits'
}),
'authorization_code': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Authorization code'
}),
'transaction_id': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Transaction ID'
}),
'insurance_claim': forms.Select(attrs={
'class': 'form-select'
}),
'eob_number': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'EOB number'
}),
'received_by': forms.Select(attrs={
'class': 'form-select'
}),
'processed_by': forms.Select(attrs={
'class': 'form-select'
}),
'notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': 'Payment notes...'
})
}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
self.medical_bill = kwargs.pop('medical_bill', None)
super().__init__(*args, **kwargs)
if self.user and hasattr(self.user, 'tenant'):
# Filter choices by tenant
self.fields['received_by'].queryset = User.objects.filter(
tenant=self.user.tenant,
is_active=True
)
self.fields['processed_by'].queryset = User.objects.filter(
tenant=self.user.tenant,
is_active=True
)
if self.medical_bill:
# Filter insurance claims by medical bill
self.fields['insurance_claim'].queryset = InsuranceClaim.objects.filter(
medical_bill=self.medical_bill
)
def clean(self):
cleaned_data = super().clean()
payment_amount = cleaned_data.get('payment_amount')
payment_method = cleaned_data.get('payment_method')
payment_date = cleaned_data.get('payment_date')
# Validate payment amount
if payment_amount and payment_amount <= 0:
raise ValidationError({
'payment_amount': 'Payment amount must be greater than zero.'
})
# Validate payment date
if payment_date and payment_date > timezone.now().date():
raise ValidationError({
'payment_date': 'Payment date cannot be in the future.'
})
# Method-specific validations
if payment_method == 'check':
check_number = cleaned_data.get('check_number')
if not check_number:
raise ValidationError({
'check_number': 'Check number is required for check payments.'
})
elif payment_method == 'credit_card':
card_last_four = cleaned_data.get('card_last_four')
authorization_code = cleaned_data.get('authorization_code')
if not card_last_four:
raise ValidationError({
'card_last_four': 'Last 4 digits are required for credit card payments.'
})
if not authorization_code:
raise ValidationError({
'authorization_code': 'Authorization code is required for credit card payments.'
})
return cleaned_data
class BillingSearchForm(forms.Form):
"""
Form for searching and filtering billing records.
"""
search = forms.CharField(
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Search bills, patients, providers...'
})
)
bill_type = forms.ChoiceField(
required=False,
choices=[('', 'All Types')] + [
('INPATIENT', 'Inpatient'),
('OUTPATIENT', 'Outpatient'),
('EMERGENCY', 'Emergency'),
('SURGERY', 'Surgery'),
('LABORATORY', 'Laboratory'),
('RADIOLOGY', 'Radiology'),
('PHARMACY', 'Pharmacy'),
('PROFESSIONAL', 'Professional Services'),
],
widget=forms.Select(attrs={
'class': 'form-select'
})
)
status = forms.ChoiceField(
required=False,
choices=[('', 'All Statuses')] + [
('DRAFT', 'Draft'),
('PENDING', 'Pending'),
('SUBMITTED', 'Submitted'),
('PARTIAL_PAID', 'Partially Paid'),
('PAID', 'Paid'),
('OVERDUE', 'Overdue'),
('COLLECTIONS', 'Collections'),
('WRITTEN_OFF', 'Written Off'),
],
widget=forms.Select(attrs={
'class': 'form-select'
})
)
date_from = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
date_to = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
amount_min = forms.DecimalField(
required=False,
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
})
)
amount_max = forms.DecimalField(
required=False,
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'min': '0'
})
)
overdue_only = forms.BooleanField(
required=False,
widget=forms.CheckboxInput(attrs={
'class': 'form-check-input'
})
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
class ClaimSearchForm(forms.Form):
"""
Form for searching and filtering insurance claims.
"""
search = forms.CharField(
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Search claims, patients, insurance...'
})
)
claim_type = forms.ChoiceField(
required=False,
choices=[('', 'All Types')] + [
('PRIMARY', 'Primary Claim'),
('SECONDARY', 'Secondary Claim'),
('TERTIARY', 'Tertiary Claim'),
('CORRECTED', 'Corrected Claim'),
('VOID', 'Void Claim'),
('REPLACEMENT', 'Replacement Claim'),
],
widget=forms.Select(attrs={
'class': 'form-select'
})
)
status = forms.ChoiceField(
required=False,
choices=[('', 'All Statuses')] + [
('DRAFT', 'Draft'),
('SUBMITTED', 'Submitted'),
('PENDING', 'Pending'),
('PROCESSING', 'Processing'),
('PAID', 'Paid'),
('DENIED', 'Denied'),
('REJECTED', 'Rejected'),
('APPEALED', 'Appealed'),
],
widget=forms.Select(attrs={
'class': 'form-select'
})
)
submission_date_from = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
submission_date_to = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
pending_only = forms.BooleanField(
required=False,
widget=forms.CheckboxInput(attrs={
'class': 'form-check-input'
})
)
class PaymentSearchForm(forms.Form):
"""
Form for searching and filtering payments.
"""
search = forms.CharField(
required=False,
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Search payments, patients, check numbers...'
})
)
payment_method = forms.ChoiceField(
required=False,
choices=[('', 'All Methods')] + [
('CASH', 'Cash'),
('CHECK', 'Check'),
('CREDIT_CARD', 'Credit Card'),
('DEBIT_CARD', 'Debit Card'),
('BANK_TRANSFER', 'Bank Transfer'),
('ACH', 'ACH Transfer'),
('WIRE', 'Wire Transfer'),
('MONEY_ORDER', 'Money Order'),
],
widget=forms.Select(attrs={
'class': 'form-select'
})
)
payment_source = forms.ChoiceField(
required=False,
choices=[('', 'All Sources')] + [
('PATIENT', 'Patient'),
('INSURANCE', 'Insurance'),
('GUARANTOR', 'Guarantor'),
('GOVERNMENT', 'Government'),
('CHARITY', 'Charity'),
('OTHER', 'Other'),
],
widget=forms.Select(attrs={
'class': 'form-select'
})
)
status = forms.ChoiceField(
required=False,
choices=[('', 'All Statuses')] + [
('PENDING', 'Pending'),
('PROCESSED', 'Processed'),
('CLEARED', 'Cleared'),
('BOUNCED', 'Bounced'),
('REVERSED', 'Reversed'),
('REFUNDED', 'Refunded'),
],
widget=forms.Select(attrs={
'class': 'form-select'
})
)
date_from = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
date_to = forms.DateField(
required=False,
widget=forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
})
)
# from django import forms
# from django.core.exceptions import ValidationError
# from django.utils import timezone
# from django.contrib.auth.models import User
# from crispy_forms.helper import FormHelper
# from crispy_forms.layout import Layout, Fieldset, Submit, Row, Column, HTML, Div
# from crispy_forms.bootstrap import FormActions
# from decimal import Decimal
# import json
#
# from .models import (
# Bill, BillItem, InsuranceClaim, Payment, PaymentMethod,
# InsuranceProvider, ClaimDenial, PaymentPlan
# )
# from patients.models import Patient
#
#
# class MedicalBillingForm(forms.ModelForm):
# """
# Form for medical billing creation
# """
# patient_search = forms.CharField(
# required=False,
# widget=forms.TextInput(attrs={
# 'class': 'form-control',
# 'placeholder': 'Search patient by name, ID, or insurance...',
# 'data-toggle': 'patient-search'
# })
# )
# insurance_verification = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# auto_submit_primary = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# auto_submit_secondary = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# generate_patient_statement = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# class Meta:
# model = Bill
# fields = [
# 'patient', 'encounter', 'bill_type', 'service_date',
# 'diagnosis_codes', 'procedure_codes', 'provider',
# 'facility', 'insurance_verification', 'auto_submit_primary',
# 'auto_submit_secondary', 'generate_patient_statement'
# ]
# widgets = {
# 'patient': forms.Select(attrs={'class': 'form-control'}),
# 'encounter': forms.Select(attrs={'class': 'form-control'}),
# 'bill_type': forms.Select(attrs={'class': 'form-control'}),
# 'service_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
# 'diagnosis_codes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
# 'procedure_codes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
# 'provider': forms.Select(attrs={'class': 'form-control'}),
# 'facility': forms.Select(attrs={'class': 'form-control'})
# }
#
# def __init__(self, *args, **kwargs):
# tenant = kwargs.pop('tenant', None)
# super().__init__(*args, **kwargs)
#
# if tenant:
# self.fields['patient'].queryset = Patient.objects.filter(tenant=tenant)
# self.fields['provider'].queryset = User.objects.filter(
# tenant=tenant,
# groups__name__in=['Doctors', 'Nurses', 'Specialists']
# )
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Patient Information',
# 'patient_search',
# 'patient',
# 'encounter'
# ),
# Fieldset(
# 'Service Details',
# Row(
# Column('bill_type', css_class='form-group col-md-6 mb-0'),
# Column('service_date', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# Row(
# Column('provider', css_class='form-group col-md-6 mb-0'),
# Column('facility', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'diagnosis_codes',
# 'procedure_codes'
# ),
# Fieldset(
# 'Billing Options',
# HTML('<div class="form-check">'),
# 'insurance_verification',
# HTML(
# '<label class="form-check-label" for="id_insurance_verification">Verify insurance eligibility</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'auto_submit_primary',
# HTML(
# '<label class="form-check-label" for="id_auto_submit_primary">Auto-submit to primary insurance</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'auto_submit_secondary',
# HTML(
# '<label class="form-check-label" for="id_auto_submit_secondary">Auto-submit to secondary insurance</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'generate_patient_statement',
# HTML(
# '<label class="form-check-label" for="id_generate_patient_statement">Generate patient statement</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Create Bill', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:bill_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
# def clean_diagnosis_codes(self):
# codes = self.cleaned_data.get('diagnosis_codes')
# if codes:
# # Validate ICD-10 codes format
# code_list = [code.strip() for code in codes.split(',')]
# for code in code_list:
# if not self.validate_icd10_code(code):
# raise ValidationError(f'Invalid ICD-10 code format: {code}')
# return codes
#
# def clean_procedure_codes(self):
# codes = self.cleaned_data.get('procedure_codes')
# if codes:
# # Validate CPT codes format
# code_list = [code.strip() for code in codes.split(',')]
# for code in code_list:
# if not self.validate_cpt_code(code):
# raise ValidationError(f'Invalid CPT code format: {code}')
# return codes
#
# def validate_icd10_code(self, code):
# """Validate ICD-10 code format"""
# # Basic ICD-10 validation (simplified)
# return len(code) >= 3 and code[0].isalpha()
#
# def validate_cpt_code(self, code):
# """Validate CPT code format"""
# # Basic CPT validation (simplified)
# return len(code) == 5 and code.isdigit()
#
#
# class BillItemForm(forms.ModelForm):
# """
# Form for bill item creation/editing
# """
# quantity = forms.DecimalField(
# max_digits=10,
# decimal_places=2,
# initial=1.00,
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# )
# unit_price = forms.DecimalField(
# max_digits=10,
# decimal_places=2,
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# )
# discount_amount = forms.DecimalField(
# max_digits=10,
# decimal_places=2,
# initial=0.00,
# required=False,
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# )
#
# class Meta:
# model = BillItem
# fields = [
# 'service_code', 'description', 'quantity', 'unit_price',
# 'discount_amount', 'modifier_codes', 'revenue_code'
# ]
# widgets = {
# 'service_code': forms.TextInput(attrs={'class': 'form-control'}),
# 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 2}),
# 'modifier_codes': forms.TextInput(attrs={'class': 'form-control'}),
# 'revenue_code': forms.TextInput(attrs={'class': 'form-control'})
# }
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Service Information',
# Row(
# Column('service_code', css_class='form-group col-md-6 mb-0'),
# Column('revenue_code', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'description',
# 'modifier_codes'
# ),
# Fieldset(
# 'Pricing',
# Row(
# Column('quantity', css_class='form-group col-md-4 mb-0'),
# Column('unit_price', css_class='form-group col-md-4 mb-0'),
# Column('discount_amount', css_class='form-group col-md-4 mb-0'),
# css_class='form-row'
# )
# ),
# FormActions(
# Submit('submit', 'Save Item', css_class='btn btn-primary'),
# HTML('<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>')
# )
# )
#
# def clean(self):
# cleaned_data = super().clean()
# quantity = cleaned_data.get('quantity')
# unit_price = cleaned_data.get('unit_price')
# discount_amount = cleaned_data.get('discount_amount', Decimal('0.00'))
#
# if quantity and unit_price:
# total_amount = quantity * unit_price
# if discount_amount > total_amount:
# raise ValidationError('Discount amount cannot exceed total amount.')
#
# return cleaned_data
#
#
# class InsuranceClaimForm(forms.ModelForm):
# """
# Form for insurance claim submission
# """
# claim_type = forms.ChoiceField(
# choices=[
# ('primary', 'Primary Insurance'),
# ('secondary', 'Secondary Insurance'),
# ('tertiary', 'Tertiary Insurance')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# submit_electronically = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# prior_authorization = forms.CharField(
# required=False,
# widget=forms.TextInput(attrs={'class': 'form-control'})
# )
# referral_number = forms.CharField(
# required=False,
# widget=forms.TextInput(attrs={'class': 'form-control'})
# )
#
# class Meta:
# model = InsuranceClaim
# fields = [
# 'bill', 'insurance_provider', 'claim_type', 'policy_number',
# 'group_number', 'subscriber_id', 'prior_authorization',
# 'referral_number', 'submit_electronically'
# ]
# widgets = {
# 'bill': forms.Select(attrs={'class': 'form-control'}),
# 'insurance_provider': forms.Select(attrs={'class': 'form-control'}),
# 'policy_number': forms.TextInput(attrs={'class': 'form-control'}),
# 'group_number': forms.TextInput(attrs={'class': 'form-control'}),
# 'subscriber_id': forms.TextInput(attrs={'class': 'form-control'})
# }
#
# def __init__(self, *args, **kwargs):
# tenant = kwargs.pop('tenant', None)
# super().__init__(*args, **kwargs)
#
# if tenant:
# self.fields['bill'].queryset = Bill.objects.filter(tenant=tenant)
# self.fields['insurance_provider'].queryset = InsuranceProvider.objects.filter(tenant=tenant)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Claim Information',
# Row(
# Column('bill', css_class='form-group col-md-6 mb-0'),
# Column('claim_type', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'insurance_provider'
# ),
# Fieldset(
# 'Insurance Details',
# Row(
# Column('policy_number', css_class='form-group col-md-6 mb-0'),
# Column('group_number', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'subscriber_id'
# ),
# Fieldset(
# 'Authorization',
# Row(
# Column('prior_authorization', css_class='form-group col-md-6 mb-0'),
# Column('referral_number', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# )
# ),
# Fieldset(
# 'Submission Options',
# HTML('<div class="form-check">'),
# 'submit_electronically',
# HTML('<label class="form-check-label" for="id_submit_electronically">Submit electronically</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Submit Claim', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:claim_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
# class PaymentProcessingForm(forms.ModelForm):
# """
# Form for payment processing
# """
# payment_amount = forms.DecimalField(
# max_digits=10,
# decimal_places=2,
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# )
# payment_date = forms.DateField(
# initial=timezone.now().date(),
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# reference_number = forms.CharField(
# required=False,
# widget=forms.TextInput(attrs={'class': 'form-control'})
# )
# notes = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# send_receipt = forms.BooleanField(
# required=False,
# initial=True,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# class Meta:
# model = Payment
# fields = [
# 'bill', 'payment_method', 'payment_source', 'payment_amount',
# 'payment_date', 'reference_number', 'notes', 'send_receipt'
# ]
# widgets = {
# 'bill': forms.Select(attrs={'class': 'form-control'}),
# 'payment_method': forms.Select(attrs={'class': 'form-control'}),
# 'payment_source': forms.Select(attrs={'class': 'form-control'})
# }
#
# def __init__(self, *args, **kwargs):
# tenant = kwargs.pop('tenant', None)
# bill = kwargs.pop('bill', None)
# super().__init__(*args, **kwargs)
#
# if tenant:
# self.fields['bill'].queryset = Bill.objects.filter(tenant=tenant)
# self.fields['payment_method'].queryset = PaymentMethod.objects.filter(tenant=tenant)
#
# if bill:
# self.fields['bill'].initial = bill
# self.fields['payment_amount'].initial = bill.balance_due
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Payment Information',
# Row(
# Column('bill', css_class='form-group col-md-6 mb-0'),
# Column('payment_source', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# Row(
# Column('payment_method', css_class='form-group col-md-6 mb-0'),
# Column('payment_amount', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# Row(
# Column('payment_date', css_class='form-group col-md-6 mb-0'),
# Column('reference_number', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'notes'
# ),
# Fieldset(
# 'Options',
# HTML('<div class="form-check">'),
# 'send_receipt',
# HTML('<label class="form-check-label" for="id_send_receipt">Send payment receipt</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Process Payment', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:payment_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
# def clean_payment_amount(self):
# payment_amount = self.cleaned_data.get('payment_amount')
# bill = self.cleaned_data.get('bill')
#
# if payment_amount and bill:
# if payment_amount > bill.balance_due:
# raise ValidationError('Payment amount cannot exceed balance due.')
# if payment_amount <= 0:
# raise ValidationError('Payment amount must be greater than zero.')
#
# return payment_amount
#
#
# class DenialManagementForm(forms.ModelForm):
# """
# Form for denial management
# """
# denial_reason = forms.ChoiceField(
# choices=[
# ('invalid_code', 'Invalid Procedure/Diagnosis Code'),
# ('not_covered', 'Service Not Covered'),
# ('prior_auth', 'Prior Authorization Required'),
# ('duplicate', 'Duplicate Claim'),
# ('incomplete', 'Incomplete Information'),
# ('timely_filing', 'Timely Filing Limit Exceeded'),
# ('other', 'Other')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# appeal_deadline = forms.DateField(
# required=False,
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# corrective_action = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
# )
# resubmit_claim = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
# file_appeal = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# class Meta:
# model = ClaimDenial
# fields = [
# 'claim', 'denial_reason', 'denial_description', 'denied_amount',
# 'appeal_deadline', 'corrective_action', 'resubmit_claim', 'file_appeal'
# ]
# widgets = {
# 'claim': forms.Select(attrs={'class': 'form-control'}),
# 'denial_description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
# 'denied_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# }
#
# def __init__(self, *args, **kwargs):
# tenant = kwargs.pop('tenant', None)
# super().__init__(*args, **kwargs)
#
# if tenant:
# self.fields['claim'].queryset = InsuranceClaim.objects.filter(tenant=tenant)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Denial Information',
# 'claim',
# Row(
# Column('denial_reason', css_class='form-group col-md-6 mb-0'),
# Column('denied_amount', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'denial_description',
# 'appeal_deadline'
# ),
# Fieldset(
# 'Corrective Actions',
# 'corrective_action',
# HTML('<div class="form-check">'),
# 'resubmit_claim',
# HTML('<label class="form-check-label" for="id_resubmit_claim">Resubmit claim with corrections</label>'),
# HTML('</div>'),
# HTML('<div class="form-check">'),
# 'file_appeal',
# HTML('<label class="form-check-label" for="id_file_appeal">File appeal</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Process Denial', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:denial_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
# class PaymentPlanForm(forms.ModelForm):
# """
# Form for payment plan setup
# """
# plan_type = forms.ChoiceField(
# choices=[
# ('monthly', 'Monthly Payments'),
# ('weekly', 'Weekly Payments'),
# ('biweekly', 'Bi-weekly Payments'),
# ('custom', 'Custom Schedule')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# number_of_payments = forms.IntegerField(
# min_value=2,
# max_value=60,
# widget=forms.NumberInput(attrs={'class': 'form-control'})
# )
# payment_amount = forms.DecimalField(
# max_digits=10,
# decimal_places=2,
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# )
# first_payment_date = forms.DateField(
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# auto_payment = forms.BooleanField(
# required=False,
# widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
# )
#
# class Meta:
# model = PaymentPlan
# fields = [
# 'bill', 'plan_type', 'total_amount', 'number_of_payments',
# 'payment_amount', 'first_payment_date', 'auto_payment'
# ]
# widgets = {
# 'bill': forms.Select(attrs={'class': 'form-control'}),
# 'total_amount': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# }
#
# def __init__(self, *args, **kwargs):
# tenant = kwargs.pop('tenant', None)
# super().__init__(*args, **kwargs)
#
# if tenant:
# self.fields['bill'].queryset = Bill.objects.filter(tenant=tenant, balance_due__gt=0)
#
# # Set minimum date to tomorrow
# tomorrow = timezone.now().date() + timezone.timedelta(days=1)
# self.fields['first_payment_date'].widget.attrs['min'] = tomorrow.isoformat()
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Payment Plan Details',
# 'bill',
# Row(
# Column('plan_type', css_class='form-group col-md-6 mb-0'),
# Column('total_amount', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# Row(
# Column('number_of_payments', css_class='form-group col-md-6 mb-0'),
# Column('payment_amount', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'first_payment_date'
# ),
# Fieldset(
# 'Options',
# HTML('<div class="form-check">'),
# 'auto_payment',
# HTML('<label class="form-check-label" for="id_auto_payment">Enable automatic payments</label>'),
# HTML('</div>')
# ),
# FormActions(
# Submit('submit', 'Create Payment Plan', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:payment_plan_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
# def clean(self):
# cleaned_data = super().clean()
# total_amount = cleaned_data.get('total_amount')
# number_of_payments = cleaned_data.get('number_of_payments')
# payment_amount = cleaned_data.get('payment_amount')
#
# if total_amount and number_of_payments and payment_amount:
# calculated_total = payment_amount * number_of_payments
# if abs(calculated_total - total_amount) > Decimal('0.01'):
# raise ValidationError('Payment amount × number of payments must equal total amount.')
#
# return cleaned_data
#
#
# class CollectionsForm(forms.Form):
# """
# Form for collections management
# """
# collection_action = forms.ChoiceField(
# choices=[
# ('letter_1', 'Send First Collection Letter'),
# ('letter_2', 'Send Second Collection Letter'),
# ('letter_3', 'Send Final Collection Letter'),
# ('phone_call', 'Schedule Phone Call'),
# ('external_agency', 'Send to External Agency'),
# ('write_off', 'Write Off Balance')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# collection_notes = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
# )
# follow_up_date = forms.DateField(
# required=False,
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# write_off_reason = forms.ChoiceField(
# choices=[
# ('uncollectible', 'Uncollectible'),
# ('patient_deceased', 'Patient Deceased'),
# ('bankruptcy', 'Bankruptcy'),
# ('small_balance', 'Small Balance'),
# ('other', 'Other')
# ],
# required=False,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Collection Action',
# 'collection_action',
# 'collection_notes',
# 'follow_up_date'
# ),
# Fieldset(
# 'Write-off Details',
# 'write_off_reason',
# HTML('<small class="form-text text-muted">Required only for write-off actions</small>')
# ),
# FormActions(
# Submit('submit', 'Execute Collection Action', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:collections_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
# class InsuranceVerificationForm(forms.Form):
# """
# Form for insurance verification
# """
# verification_type = forms.ChoiceField(
# choices=[
# ('eligibility', 'Eligibility Verification'),
# ('benefits', 'Benefits Verification'),
# ('authorization', 'Prior Authorization'),
# ('referral', 'Referral Verification')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# service_date = forms.DateField(
# widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
# )
# procedure_codes = forms.CharField(
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
# verification_notes = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 4})
# )
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# Fieldset(
# 'Verification Details',
# Row(
# Column('verification_type', css_class='form-group col-md-6 mb-0'),
# Column('service_date', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'procedure_codes',
# 'verification_notes'
# ),
# FormActions(
# Submit('submit', 'Verify Insurance', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:verification_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
# class BulkBillingForm(forms.Form):
# """
# Form for bulk billing operations
# """
# action = forms.ChoiceField(
# choices=[
# ('submit_claims', 'Submit Claims'),
# ('generate_statements', 'Generate Patient Statements'),
# ('send_reminders', 'Send Payment Reminders'),
# ('update_status', 'Update Status'),
# ('apply_adjustments', 'Apply Adjustments')
# ],
# required=True,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# bill_ids = forms.CharField(
# widget=forms.HiddenInput()
# )
# new_status = forms.ChoiceField(
# choices=[
# ('pending', 'Pending'),
# ('submitted', 'Submitted'),
# ('paid', 'Paid'),
# ('denied', 'Denied'),
# ('cancelled', 'Cancelled')
# ],
# required=False,
# widget=forms.Select(attrs={'class': 'form-control'})
# )
# adjustment_amount = forms.DecimalField(
# max_digits=10,
# decimal_places=2,
# required=False,
# widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'})
# )
# adjustment_reason = forms.CharField(
# required=False,
# widget=forms.Textarea(attrs={'class': 'form-control', 'rows': 3})
# )
#
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
#
# self.helper = FormHelper()
# self.helper.layout = Layout(
# 'bill_ids',
# Fieldset(
# 'Bulk Operation',
# 'action',
# 'new_status',
# Row(
# Column('adjustment_amount', css_class='form-group col-md-6 mb-0'),
# css_class='form-row'
# ),
# 'adjustment_reason'
# ),
# FormActions(
# Submit('submit', 'Execute Bulk Operation', css_class='btn btn-primary'),
# HTML('<a href="{% url \'billing:bill_list\' %}" class="btn btn-secondary">Cancel</a>')
# )
# )
#
#
#