""" 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('