ServicesCrelio AppArchitectureApp Modules finance
Billing, invoicing, insurance claims, and financial transactions
- Billing - Bill creation, updates, cancellation
- Insurance claims - Claim submission, response processing
- Invoices - Organization invoicing
- Transactions - Payment tracking
- Bill approval workflow - Discount/waiver approvals
- Tax categories - Tax calculations
core/ - BaseModel, utilities
admin/ - Labs, organizations, tests
patient/ - Patient details for billing
- Payment gateway integration (belongs in
payments/)
- Patient demographics (belongs in
patient/)
- Report generation (belongs in
report/)
| Model | Responsibility | Key Fields | Cross-App Relations |
|---|
Billing | Core bill entity | labBillId, totalAmount, pendingAmount, isCancel | patient.UserDetails, admin.Organization |
InsuranceClaim | Insurance claim lifecycle | claim_status, claim_id, payer_id | Billing |
ClaimResponse | ERA/EOB processing | claim_response_data, x12_835_data | InsuranceClaim |
BillApprovalAction | Discount/waiver workflow | action_type, status, approved_by | Billing |
InsuranceGroup | Payer configuration | payer_name, default_rates | admin.Labs |
Invoice | B2B invoicing | invoice_number, total, organization | admin.Organization |
Key Methods:
| Method | Purpose |
|---|
after_save() | Post-save processing |
prepare_and_save_patient_insurances() | Link insurance to bill |
get_bills(lab_id, from_date, to_date, ...) | Fetch bills with filters |
get_bill_details(bill_id, lab_id, ...) | Detailed bill fetch |
pending_amount | Calculate outstanding |
add_attachment(file_details) | Store bill attachments |
included_tests | Property - tests in bill |
get_pending_claims_qs(lab_id, from_date, to_date) | Claims query |
| Method | Purpose |
|---|
create_claim() | Build claim structure |
submit_claim() | Send to clearinghouse |
process_response() | Handle ERA/EOB |
| Method | Purpose |
|---|
request_approval() | Initiate approval workflow |
approve() | Grant approval |
reject() | Deny request |
Billing logic is complex with many validation rules and state transitions. Keeping logic in models ensures:
- Consistent calculations (amounts, taxes, discounts)
- Audit trail through activity logs
- Integration point for claims processing
| Constraint | Enforcement | Location |
|---|
Unique labBillId per lab | Database + generation | Bill creation |
totalAmount = sum of test amounts | Calculated | Before save |
pendingAmount = totalAmount - payments | Computed property | pending_amount |
| Can't cancel after claim submission | Business rule | Validation |
| Field | Values | Meaning |
|---|
isCancel | 0/1 | Bill cancelled |
isWriteOff | 0/1 | Written off |
isComplete | 0/1 | All reports done |
| Status | Meaning |
|---|
DRAFT | Not submitted |
SUBMITTED | Sent to clearinghouse |
ACCEPTED | Payer acknowledged |
PAID | Payment received |
DENIED | Claim rejected |
PARTIAL | Partial payment |
# finance/models/billing.py
@classmethod
def get_bills(cls, lab_id, from_date, to_date, ...):
return cls.objects.filter(
labId_id=lab_id,
registrationDate__range=(from_date, to_date),
isCancel=0
).select_related(
"userDetailsId",
"orgId",
"branch"
).prefetch_related(
"billinginfo_set",
"billingicd_set"
).order_by("-registrationDate")
| Query | Optimization |
|---|
| Bill list with patient | select_related("userDetailsId") |
| Bill with tests | prefetch_related("billinginfo_set") |
| Bill with ICD codes | prefetch_related("billingicd_set") |
| Claims with responses | prefetch_related("claimresponse_set") |
# finance/models/billing.py
class Meta:
indexes = [
models.Index(fields=["labId_id", "labBillId"]),
models.Index(fields=["orgId_id", "registrationDate"]),
models.Index(fields=["labId_id", "isCancel", "isWriteOff", "isComplete"]),
models.Index(fields=["labId_id", "isCancel", "isWriteOff", "lastUpdatedTime"]),
]
| Endpoint | View | Model Method |
|---|
GET /finance/bills | BillListView | Billing.get_bills() |
GET /finance/bill/{id} | BillDetailView | Billing.get_bill_details() |
POST /finance/bill/{id}/update | BillUpdateView | billing.save() |
POST /finance/claim/submit | ClaimSubmitView | InsuranceClaim.submit_claim() |
POST /finance/claim/response | ClaimResponseView | ClaimResponse.process() |
# finance/views/bill_update.py
class BillUpdateView(GenericView):
@transaction.atomic
def post(self, request, bill_id, *args, **kwargs):
bill = Billing.objects.get(pk=bill_id, labId_id=lab_id)
bill.set_values(payload)
bill.save(session=request.session)
# Related updates in same transaction
BillingInfo.objects.filter(billId=bill).update(...)
return JsonResponse({"status": "success"})
| Integration | Direction | Handler Location |
|---|
| QuickBooks | Bidirectional | integration/quickbooks/ |
| Clearinghouse (X12) | Outbound | finance/models/claim.py |
| Tebra | Outbound | integration/tebra/ |
| ERA Processing (835) | Inbound | integration/utils.py |
# integration/utils.py
def parse_x12_and_build_payload(claim_response, lab_id, bill_id=None):
"""Parse X12 835 ERA and build QuickBooks payload"""
raw_text = claim_response.x12_835_data
segments = parse_x12(raw_text)
parsed_data = parse_835(segments)
return build_claim_payload(parsed_data, claim_mapping, lab_id)
# finance/models/billing.py
def after_save(self, *args, **kwargs):
self.prepare_and_save_patient_insurances()
# Activity logging via ActivityLogBase
| Import | Used For |
|---|
patient.models.user_details.UserDetails | Linked patient |
admin.organization.models.organization.Organization | B2B billing |
admin.masters.models.all_tests.AllTests | Test pricing |
report.models.lab_report_relation.LabReportRelation | Report status |
| Table | Volume | Indexes |
|---|
billing | Very high | Composite indexes |
billingInfo | High (many per bill) | billId |
insuranceClaim | Medium | billing_id, claim_status |
- Always filter by
labId_id first
- Use date range filters
- Avoid full table scans with proper indexes
- Use
values() for list views
- Claim submission via Fusion worker
- ERA file processing
- Invoice generation
- Add field to
Billing model
- Create migration
- Update
get_bills() return if needed
- Add to serializer
- Create new model if needed
- Add methods to
Billing or create proxy
- Add view with
@transaction.atomic
- Log activity via
add_activity_log()
- Use
@transaction.atomic for multi-model updates
- Validate amounts in model methods
- Use
Decimal for monetary values
- Calculations in views
- Direct SQL without ORM
- Skipping validation with raw
update()