---
name: evolution_invoice_total_reconciliation
description: "Why invoiceadd header/footer/line totals drift — canonical subtotal base, adjPrice precision, and invoiceitems.total caveat"
metadata: 
  node_type: memory
  type: project
  originSessionId: 92f4ecde-f6c3-4349-aa30-52be096c4e02
---

On `invoiceadd`, three total surfaces must agree: header ("Total" top-right, from `invoice.total`), footer block, and per-line SUB-TOTAL column. The **canonical subtotal base is `round(CAST(adjPrice AS DECIMAL(15,4))*qty, 2)`** — used by the save handler's header write (invoiceaddsave.php ~754), by per-rate `ttax`, and (after this work) by footer `ttotal`/`$ttotald` in invoiceadd.inc. Header tax = per-rate `round(rate% * Σ base for that rate)`, summed once across rates (the [[gst_precision_project]] approach).

GOTCHA: `invoiceitems.total` is stored with a DIFFERENT formula — `round((price - discountv)*qty, 4)` in a single-precision FLOAT column (see [[evolution_float_money_columns]]). It is read by GL posting (`accounting.php`, `library/accounting.php` both `sum(invoiceitems.total)`) and several reports, so do NOT casually realign it to the adjPrice base — that shifts the ledger. Reconcile the *screen* via display/JS, not by changing the stored column.

`adjPrice` is `decimal(15,4)` — must stay 4dp everywhere or totals drift. Root cause of a real cent-drift bug (2026-06): JS `updateNew()` (invoiceadd.js) rounded new-line adjPrice to **3dp** while `updateRow`/initial-compute used 4dp; invoiceaddsave.php rounds both price and adjPrice to 4dp symmetrically (it was never the culprit, it just stored the 3dp value the form posted). Fixing updateNew to 4dp reconciled the totals.

**How to apply:** when invoice totals are "out by a couple cents", check (1) adjPrice precision end-to-end is 4dp, (2) which of the two formulas each surface uses. Live JS truncates per-line (`parseInt`) where PHP rounds — another drift source if matching live vs reload exactly.
