Backend
API contract, database changes, business logic orchestration, and integration points for the Order Update backend in crelio-app.
Backend
Repo: crelio-app
Files:
finance/views/bill_update.py Github Link
finance/views/bill_update_helpers.py Github Link
API
POST /api-v3/finance/bill/<lab_bill_id>/update/Authentication: Session-based. lab_id is read from the active session.
Request Body
| Key | Type | Required | Description |
|---|---|---|---|
bill | object | ✅ | Core bill fields |
tests | list | ❌ | Test objects with updated amounts/concessions |
paymentList | list | ❌ | Payments to add or update |
billIcd | object | ❌ | ICD code data grouped by bill ID |
modifiers | object | ❌ | Billing modifiers |
billAddType | integer | ❌ | 0 = update only; 1 = also add new tests |
reportMode | integer | ❌ | Report print mode |
aoeFormValues | object | ❌ | AOE form values for new tests |
orgId | integer | ❌ | Organisation ID |
missingFieldConfigUsed | object | ❌ | Missing-field config IDs by category |
Key bill Fields
| Field | Description |
|---|---|
billId | Internal DB primary key |
billTotalAmount | Updated total amount |
billConcession | Discount amount |
billAdvance | Advance paid |
billComments | Free-text notes |
billTime | Bill date + time |
sampleDate | Sample collection date |
sampleDateChangeFlag | 1 if sample date is intentionally being changed |
source | Billing source (cash, insurance, free, etc.) |
paymentUpdateFlag | 1 = payment section was edited |
deletedPaymentId | List of payment IDs to hard-delete |
orgId | Organisation ID |
docId | Referral doctor ID |
mode | Emergency report flag |
billLocked | 1 = lock the bill |
Response
// Success
{ "message": "Bill details updated successfully" }
// Success with new tests added
{
"message": "Bill details updated successfully",
"sampleDetailsList": [ ... ]
}
// Failure
{ "message": "Failed to update bill details" }Execution Flow
Step Reference
| # | Method | What It Does |
|---|---|---|
| 1 | Snapshot | Records original field values for diff comparison |
| 2 | initialize_data() | Extracts and normalises the request payload |
| 3 | validate() | Verifies UserDetails exists |
| 4 | update_bill_tests() | Bulk-updates test amounts and concessions in BillingInfo |
| 5 | handle_payments() | Gated by paymentUpdateFlag; adds, updates, and deletes payments |
| 6 | update_org() | Reconciles org ledger (same-org update or org change) |
| 7 | update_user_details() | Adjusts patient outstanding balance |
| 8 | update_doctor_revenue() | Recalculates doctor cut per test |
| 9 | update_appointments() | Syncs EmrAppointments and HomeCollection records |
| 10 | update_doctor_info() | Updates referral name displayed on the bill |
| 11 | update_bill_fields() | Persists all core bill field updates |
| 12 | update_bill_info_fields() | Persists test-level BillingInfo field changes |
| 13 | update_icd_data() | Delete-then-save ICD codes via BillICDService |
| 14 | update_modifiers() | Replaces billing modifiers |
| 15 | update_lab_reports() | Updates LabReportRelation metadata |
| 16 | update_elasticsearch() | Pushes changes to ES; retried up to 2 times with 1s wait |
| 17 | bill_details.save() | Commits the Billing model |
Payment Handling
Payment processing runs only when paymentUpdateFlag is set in the request.
Adding Payments
Each paymentList item with addFlag = true and no paymentID creates a new Payments record. If no payment list is provided, a fallback CASH payment is created from the current advance amount.
Updating Payments
Items with a paymentID update the existing record in-place (amount, type, bank, card/cheque details).
Deleting Payments
IDs in deletedPaymentId are hard-deleted (excluding refund-type payments). Deleted amounts are tracked separately for patient vs. org payments, and flow into ledger reconciliation.
Payment Conflict Guard
The update returns early with status 6 when all of these are true simultaneously:
- All payment detail fields are populated
- The total amount has changed
- The org has a standard payment type (prepaid or postpaid)
[!WARNING] Status
6means the caller must resolve the payment conflict before retrying. No DB changes are made.
Organisation & Ledger
When a bill is linked to an org, currentDue must stay in sync.
Same Org Update
- Calculates amount difference (old vs. new total)
- Updates
currentDueon the org record - Routes to one of three ledger cases:
- Deleted patient payments → logs the deleted amount
- Standard update → logs the amount difference
- Refund scenario → calculates and logs separate patient + org refund amounts
Org Change
- Old org: ledger is credited back (bill amount reversed)
- New org: ledger is debited (bill amount charged)
currentDueadjusted on both
Org Types
| Code | Type | Ledger Handling |
|---|---|---|
0 | Postpaid | SALES ledger entries; currentDue tracked |
1 | Prepaid | PAYMENT ledger entries; advance-based |
2 | Other | Skipped for most ledger operations |
[!NOTE] Orgs with
manageLedger = 0bypass the Fusion ledger webhook. TheircurrentDueis updated directly in the database.
Doctor Revenue
When a doctor is attached, revenue is recalculated per test:
- Fetches
ListDoctorRelationfor the doctor - For each test, checks
ListTestRelationfor a revenue amount - Applies concession threshold:
- Concession > threshold AND
discardDiscountedRevFlagis set → doctor gets ₹0 - Otherwise:
doctor_amount = base_amount − (concession × sharing % / 100)
- Concession > threshold AND
- If payment type is
FREE→ all amounts and concessions forced to0
Audit Diff
Every bill update records a structured before/after diff in the ActivityLog.
Tracked Fields
| Field | Notes |
|---|---|
source | Billing source |
billTime | Formatted as DD Mon YYYY, HH:MM AM/PM |
billAdditionalCategory | Additional category |
orderNumber | Order / reference number |
billComments | Free-text comments |
billTotalAmount | Compared numerically |
billConcession | Compared numerically |
billReferal | Referral name |
sampleDate | Only tracked if sampleDateChangeFlag = 1 |
mode | Emergency report flag |
orgId | Shows organisation full name |
testAmount per test | Compared numerically |
testConsc per test | Compared numerically |
billAdvance | Compared numerically |
Diff JSON Structure
{
"info": "Bill Information of Bill Id 428 for user John Doe (Id: 1234)",
"update": [
{
"Bill Data": [
{
"Total Amount": { "old_value": 500.0, "new_value": 600.0 },
"Comments": { "old_value": "", "new_value": "Urgent" }
}
]
},
{
"Test Details": [
{
"Test Amount for CBC": { "old_value": 200.0, "new_value": 250.0, "updateFor": "CBC" }
}
]
}
]
}If no fields changed, diff is null — the ActivityLog entry is still created.
Activity Logs Written
| Category | When | Contents |
|---|---|---|
| 17 — Bill Update | Always | Bill ID, patient name, user, timestamp, diff |
| 14 — Payment Added | When payment total increased | Old total, new total, bill ID |
| 15 — Payment Deleted | When payments were deleted | Deleted amount, bill ID, patient name/ID |
Elasticsearch Sync
After the transaction commits, the patient_reports and userdetails indices are updated.
patient_reports Index
| Field | Source |
|---|---|
lastUpdated | Current timestamp |
orgId.* | Full org object |
mode | Emergency report flag |
userDetailsId.totalAmount | Patient outstanding balance |
billId.docId | Doctor ID |
billId.source | Billing source |
billId.billTime | Bill timestamp |
billId.billReferal | Doctor name |
billId.billComments | Comments |
billId.billTotalAmount | Total amount |
billId.orderNumber | Order number |
billId.isComplete | Completion flag |
billId.branch_id | Branch |
billId.billService | Bill service |
sampleDate | Only when sampleDateChangeFlag = 1 |
userdetails Index
| Field | Source |
|---|---|
totalAmount | Patient outstanding (0 if addTestFlag is set) |
source | Billing source |
lastUpdatedTime | Current timestamp |
advanceFlag | Whether advance is applicable |
creditFlag | Whether credit balance exists |
Integration Webhooks
Bill Integration (Async)
After the transaction commits, a Fusion webhook notifies connected integrations:
| Property | Value |
|---|---|
| URL | PY2_WEBHOOK_URL/integration/bill/trigger/ |
| Method | POST via Fusion task queue (task_type=2) |
| Payload | { lab_id, lab_bill_id, lab_user_id } |
This is fire-and-forget — the response is not waited on.
Legacy Test Addition (Synchronous)
When billAddType != 0, a synchronous call is made to the legacy PY2 system:
| Property | Value |
|---|---|
| URL | PY2_WEBHOOK_URL/billAddTest/ |
| Method | POST (form-encoded, session cookies forwarded) |
| Response | Included in the bill update API response as sampleDetailsList |
Error Handling
| Check | Failure Behaviour |
|---|---|
lab_id or lab_bill_id missing | ValidationError → 400 |
| Empty payload | Returns 400 immediately |
Billing record not found | Skips update; 200 with "failed" flag if billAddType != 0 |
UserDetails not found | Exception → full rollback |
BillingInfo not found when updating tests | Exception → full rollback |
| Lab user not found during cash-box update | Exception → full rollback |
| Missing field config error | Silently caught, reported to Sentry — does not roll back |
| Elasticsearch failure | Retried once after 1s; failure does not roll back the transaction |