789 lines
26 KiB
Python
789 lines
26 KiB
Python
"""
|
|
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'
|
|
})
|
|
)
|
|
|