---
name: evolution-cogs-at-sale-value
description: "COGS posted at sale value not cost; Option B (remove sell-side cost journal) shipped, Option A perpetual inventory is an open project"
metadata:
  node_type: memory
  type: project
  originSessionId: 2cddf49d-89aa-41e3-9680-e5f150778183
  modified: 2026-07-27T00:15:01.099Z
---

`commitInvoice` posted its cost journal at **`sum(invoiceitems.total)` — the sale value, not cost**
(DEBIT `inventory.puraccount` / CREDIT `inventory.assaccount`). No cost field was read at all.
Every stocked line showed zero gross profit and inventory was relieved at retail. All tenants,
all time.

Worse structurally: **`commitBill` debits `billitems.accountid`, never `inventory.assaccount`** —
purchases are expensed on receipt, so the asset account was only ever *credited*. Reference tenant
`85-1384947162`: Finished Goods Inventory at **−$177M**, against a single $2,584.89 debit in the
whole history. COGS was double-counted (cost on the bill, retail again on the sale). Evolution was
periodic on the buy side, pseudo-perpetual on the sell side.

Exposure is gated on `puraccount>0 AND assaccount>0`, so free-text/service lines skip it — that is
why text-invoicing tenants looked fine.

**Prod sweep done 2026-07-26 (all 21 active tenants): 13 clean, 8 affected.** Because the journal
debited P&L and credited the balance sheet, **removing it RAISES reported net profit by the leg
amount — ~$38.0M aggregate.** Worst: Olympic Fencing NSW $30.6M (GM 7%→29%), Otter Fencing $6.0M,
Supacote $831k (95% of its entire COGS; GM 61%→98%). Full table in the audit doc.

✅ **None of it reached Xero.** All 8 exposed tenants are `xerosync=1`, but `library/xero.php` only
has putItem/putContact/putInvoice/putCreditNote/putPayment — **no manual-journal or gledger push**,
and gledger has no externalref/sync column. Damage is confined to Evolution's internal
P&L/Balance Sheet reports; **no lodged accounts or BAS affected.**

⚠️ Three unrelated findings surfaced by the sweep, all OUT OF SCOPE but needing to be raised:
**Otter Fencing COGS $70.4M vs income $33.8M** (2× revenue; only $6M is this bug — something else
is broken there); **Supacote's real purchase costs aren't coded to COGS at all** (post-fix GM reads
98%); and **PLC Fencing has half-posted journals** — debits $24,625.97 vs credits $8,546.61, a
$16,079 gap where credit legs were silently lost at post time, the `insertQuery()` echoes-instead-
of-throws failure mode from [[evolution_gl_control_account_validation]]. Every other tenant
balances to the cent. Plus **stocktake journals store NEGATIVE values in both `debit` and `credit`**
(Otter Test Finished Goods: debit −1,663,537.72 AND credit −1,413,028.38) — any report summing
those columns reads stocktakes backwards; must be fixed before Option A derives opening balances.

⚠️ The local **dev** copy of `85-1384947162` does NOT match prod's tenant of the same name (dev
holds ~$190M of this leg, prod Master Test only $21k — the dev copy looks like Olympic-Fencing
data). Don't quote dev figures as production.

**Shipped (Option B, 2026-07-26):** removed the sell-side cost journal from BOTH `accounting.php`
copies and removed the matching credit-note restock mirror (`library/accounting.php` ~L1595) —
they must always move together or a return leaves a one-sided asset balance. Credit-note restock
gate relaxed to `assaccount>0` to match commitInvoice's SOH gate. Also fixed an unaggregated `qty`
under `group by itemid` that fed `stockMovement("out")`: an item on two lines of one invoice
relieved only one line's qty (3,505 units never relieved).

⚠️ **Do NOT "fix" this by swapping `total` for a cost figure** — that keeps the double count and
still never debits the asset. That trap is called out in comments at both sites.

✅ **No restatement problem.** The financial reports that expose this (`reports/finPL*`) were first
committed **2026-06-11** and no customer has ever run one — the GL was accumulating silently. So
voiding the historical legs is junk-data cleanup, not restating published accounts.

✅ **Historical cleanup DONE on all live tenants 2026-07-27.** Script
`cron/gledger_void_cogs_at_sale.php` (follows the `staff_masterid_dedupe.php` convention):
dry-run default, `--apply`, `--tenant=`/`--skip-sandbox`/`--host,--user,--pw`; CSV backup to
`cron/log/` before writing (MyISAM = no rollback); soft-delete `status='0'` + memo prefix
`VOID-COGSFIX:` (idempotent, reversible via `SUBSTRING(memo,14)`). The `claude` prod user has NO
tenant-DB write access (`ERROR 1142`) — Shane runs it on the server.
**Verified after the fact: 0 live legs left in all 20 live tenants; 81,337 rows voided,
+$38,029,263.65 profit impact across 12 tenants** (Olympic 57,232 / $30.6M; Otter 19,002 / $6.0M;
Supacote 3,538 / $831k; Denton 1,206 / $325k; WearPro 104 / $227k; rest <$40k). Reconciles to the
dry run +10 rows / +$429.28 = Otter invoices dated 2026-07-27, i.e. **the old code was still
serving live traffic that morning**. Rehearsed first on the Otter Test (9,180 rows) and Olympic
Test (57,232 rows) sandboxes before the live run. Sandboxes still dirty by design: PLC Fencing –
Sandbox (22 / $6,886.28) and Tomago Test (10 / $641.30).
⚠️ The staging box (`/home/evolution/staging.evolutionerp.com.au/`) writes to the SAME MariaDB
instance as `my.evolutionerp.com.au` — a staging-path run is a production data change.
⚠️ **The live tenant count is 20** (`companies`: 25 with `status='1'` + databasename, 5 of which
are sandboxes via `sandbox_of > 0`) — an earlier note saying 21 was a miscount.

**OPEN:**
- **Confirm the fix actually deployed to prod.** Fix is commit `667bd62b7` (2026-07-26 23:16 UTC),
  `staging` in sync with origin, no `COG:INV` emitters in HEAD — but prod's filesystem isn't
  visible from code-server. Test: re-run `php cron/gledger_void_cogs_at_sale.php --skip-sandbox`
  after a day of trading; non-zero rows = deploy never landed and it's still accumulating.
- Option A (true perpetual inventory) — full spec at `evolution/audits/PERPETUAL_INVENTORY_PROJECT.md`;
  requires changing the BUY side first. 6 decisions there (D1–D6).
- Release notes + docs page not yet written.

See [[evolution_two_accounting_files]] (Approve runs the web-root globals — patch both),
[[evolution_gl_pl_data_model]], [[evolution_invoice_stock_reversal_asymmetry]],
[[evolution_float_money_columns]].
