Round-off
Invoice (and bill) totals can drift to fractional rupees when a tax percentage produces an inexact float (e.g. 18% of ₹199.43 = ₹35.8974). The round-off feature snaps the grand total to a whole rupee and posts the difference to a dedicated Round Off ledger.
Turn it on
Via the UI
Settings → Invoice Profile → toggle Round invoice totals automatically, pick a rounding method, Save. The preference is stored in organisations.preferences.roundInvoiceTotals and is applied to every invoice creation path (UI, API, recurring runs).
Via API
PUT /api-py/api/v1/organisations/<org_id>/preferences
{
"preferences": {
"roundInvoiceTotals": true,
"roundingMethod": "nearest" // "nearest" | "up" | "down"
}
}Note: this is an internalendpoint (session JWT, not PAT). The preference itself is read by the service layer on every invoice POST. There's no separate public endpoint for it because org-level settings are an admin action.
How it works
When auto-round is on, the service computes the total as:
pre = subtotal − discount + tax + shipping + adjustment − tds
total = round(pre) # via the chosen method
round_off = total − pre # posted to Round Off ledgerThe grand total stamped on the invoice is the whole rupee. The round_off field carries the delta — positive means we rounded up (income to the org), negative means we rounded down (small expense).
Rounding methods
| Method | Behaviour | Example (₹199.49 → ?) |
|---|---|---|
nearest (default) | Half-up. .50 rounds to +1; .49 rounds to +0. | ₹199 |
up | Ceiling. Any fractional rupee rounds up. | ₹200 |
down | Floor. Any fractional rupee rounds down. | ₹199 |
“Nearest” uses half-up rounding (Indian accounting convention), not Python's banker's rounding which would send 0.5 to the nearest even integer. We pick the convention that matches what your accountant expects.
Caller override on POST /invoices
If the caller passes round_off explicitly, that value wins — auto-round only fires when the field is omitted (null).
# Org pref is on, but caller forces no rounding:
POST /invoices
{ ..., "round_off": 0, ... } // → stays fractional
# Org pref is on, caller leaves to auto:
POST /invoices
{ ..., (no round_off) ... } // → auto-rounded
# Org pref is off, caller forces a specific adjustment:
POST /invoices
{ ..., "round_off": -0.50 ... } // → applied verbatimJournal entry
When the invoice is approved, the JE includes a Round Off line:
# Example: ₹199.43 line + 18% GST
# pre-round total = ₹235.33; auto-rounded to ₹235
Dr Accounts Receivable 235.00
Cr Sales (revenue) 199.43
Cr Output CGST 17.95
Cr Output SGST 17.95
Cr Round Off (income) 0.33 ← hereThe Round Off ledger is auto-resolved by name (case-insensitive match on “Round Off” / “Rounding”). If the org doesn't have such an account configured, the round-off line is skipped silently and the total still balances (the rounded total absorbs it). Best practice: create a Round Off account under Chart of Accounts the first time you turn the feature on.
Update / re-edit path
When you edit a draft invoice (lines or header amounts), the service recomputes round_off from scratch if the previously-stored value looks auto-computed (|round_off| < 1). Manual large overrides are preserved. The math is consistent regardless of which path you used to create the invoice.
What about bills?
Bills have a round_offcolumn but no auto-round preference yet — vendors send you fixed-amount bills, not items you derive a total from, so the math doesn't drift the same way. You can still pass a manual round_off on POST /billsif you need to match a vendor's rounded total exactly.
Cookbook
Detecting whether the org has auto-round on
# Internal API only (admin action)
curl -H "X-Org-Id: $ORG" -H "X-User-Id: $USER" \
https://app.finzbooks.com/api-py/api/v1/organisations/$ORG/preferences
# → { "success": true, "preferences": { "roundInvoiceTotals": true, ... } }Forcing no round-off on a one-off invoice
Pass round_off: 0 explicitly — the caller value wins even when the org pref is on.
Migrating: applying round-off to historical invoices
Existing invoices are not retroactively rounded — auto-round only fires on create + update. To clean up historical fractional totals, run an update on each (a PATCH with no body changes still re-evaluates round_off if the helper detects a previously auto- computed value).