AquaDealers supports two product categories with different pricing models. The pricing strategy depends on the product type.
selling_price is the rate the farmer pays. There is no MRP/discount split. The price is directly editable by the dealer at billing time.
Price is derived from MRP and a discount percentage:
unit_price = MRP × (1 − discount_percentage / 100)
Discount cascade (checked in order):
upsertFarmerProductDiscount(). The next time that farmer purchases the same product, their previously-given rate is automatically pre-filled in the product selector.
This means dealers don't need to remember individual farmer rates — the system tracks the last rate used for each farmer+product combination and applies it as the default on the next purchase.
gst_amount = ROUND(unit_price × quantity × gst_rate / 100, 2)
gst_billing_enabled dealer settinggst_rate from the product record. The final bill total is always correct — only the in-progress preview may differ slightly.
subtotal = Σ(unit_price × quantity) for each item gst = Σ(item.gst_amount) for each item total = GREATEST(ROUND(subtotal + gst − discount_amount, 2), 0)
The GREATEST(..., 0) guard ensures the total never goes negative even if the discount exceeds the subtotal + GST.
| Method | Code | Behavior |
|---|---|---|
| Cash | cash | Standard cash payment. Amount entered manually. |
| UPI | upi | Digital payment. Amount entered manually. |
| Credit | credit | Auto-sets amount_paid = 0. Full amount added to farmer's due. |
| Other | other | Bank transfer, cheque, etc. Amount entered manually. |
balance_due = total − amount_paid
Walk-in customers must pay in full. This is enforced in useCheckout.ts (line ~163–164). No credit is allowed for walk-in bills.
Farmer bills allow partial payment. The remaining balance_due is added to the farmer's outstanding total and can be collected later.
Settlement discounts are applied at payment time, separate from per-item discounts on the bill itself. They reduce the effective total the farmer needs to pay.
Farmer dues are trigger-based — recalculated automatically whenever bills, payments, or payment allocations change.
total_due = GREATEST(0, opening_balance + Σ(active_bills.balance_due) − (Σ(payments.amount) − Σ(allocated_payments)) − return_credit_balance )
| Trigger | Fires On | Action |
|---|---|---|
farmer_due_from_bill | bills table INSERT/UPDATE/DELETE | Recalculates total_due from all active bills |
farmer_due_from_payment | payments table INSERT/UPDATE/DELETE | Recalculates total_due factoring payment totals |
farmer_due_from_payment_allocation | payment_allocations table INSERT/UPDATE/DELETE | Recalculates total_due factoring allocated amounts |
GREATEST(0, ...) wrapper ensures a farmer's due never shows as a negative number, even if overpayments exist. Overpayments are tracked but do not create a “credit” balance on the farmer record itself.
When a payment is collected from a farmer, it can be allocated in one of two modes:
Payment targets a single bill. This typically happens at bill creation time when the farmer pays for a specific purchase.
balance_dueThe collect_farmer_payment_v2 RPC iterates unpaid bills ordered by bill_date ASC and allocates until the payment is exhausted.
payment_allocations recordsallocation_ordercredit_limit fieldprojected_due > credit_limitcredit_override_used = true with a reasonAD-{YYYYMMDD}-{sequence}
AD-20260909-001, AD-20260909-002, etc.Walk-in bills are identified by farmer_id = NULL or a designated walk-in farmer record.
Since walk-in customers have no farmer profile, the system cannot track outstanding dues or collect future payments. Enforcing full payment at checkout ensures the dealer doesn't accidentally create uncollectable credit for anonymous customers.