CostLayer: a case study in refusing a default
How an accountant got tired of a tool pricing stock wrong, and built the costing engine he actually wanted.
a story about a number that fought back
A stock valuation disagreed with itself during month-end close, and the software that produced it was perfectly content. Nothing crashed. Nothing flagged. The mistake was mine to find, because the number lands on a journal I have to sign.
It wasn’t even a dramatic number. A dramatic number at least announces itself. This one was wrong the way a slightly fogged mirror is wrong: everything looks plausible until you put something sharp against it.
What the default did
The tool had a costing model baked in, which is fine in principle. On the transactions it couldn’t represent cleanly — a partial sale, a voided invoice, stock moved mid-month — it quietly took a shortcut. No error. No flag. No log entry. Just a number, rounded and formatted, wearing the costume of confidence.
That’s inventory for you. You don’t get a warning, you get a consequence. The wrong number crashes nothing; it compounds politely into a margin you’d rather not explain to anyone whose opinion matters.
Why FIFO specifically
Closing books in Indonesia means PSAK, and PSAK took LIFO out behind the barn years ago. So the honest candidates are FIFO and weighted average. Weighted average is seductive: one blended number, looks clean, pastes into a report without complaint. It’s also how truth gets diluted. Two purchases at different costs become one imaginary middle price, and from that day the books remember a price nobody on this planet ever actually paid.
FIFO states the physical story: you used the oldest stock first. Every purchase is a layer with its own cost. Every sale consumes the oldest layer, in order.
┌─────────────────────────────────────────────────────┐
│ layer 1 · 100 units @ $10.00 → consumed first │
│ layer 2 · 50 units @ $12.50 → next in line │
│ layer 3 · 80 units @ $11.00 → waiting │
└─────────────────────────────────────────────────────┘
Sell 120 units: 100 from layer 1, then 20 from layer 2. COGS = (100 × 10.00) + (20 × 12.50). You can defend every cent of that in front of anyone. The whole trick is that there is no trick.
What the build actually demanded
Refusing the fold meant the boring parts got expensive, and the boring parts are the product.
Voids restock the exact slices they consumed, at the exact money they consumed them at — not a re-derived approximation of it. Landed cost allocation produces unit costs like $48.250768 for a finished stool, and the system refuses to round it to $48.25 mid-pipeline, because that stool cost what it cost and the ledger will carry six decimal places if that’s what the truth weighs.
The concurrency one bit harder than expected. Two terminals sell the same layer at the same minute and the layer table has to arbitrate, so every layer mutation runs inside one transaction with rows locked FOR UPDATE. Whoever wins the lock sells real stock. Whoever waits sells what remains, or gets told there is nothing. There is no third outcome where the database quietly invents one.
The gate it has to pass
Because the point was always Xero: the number that gets pushed has to reconcile against the trial balance, top to bottom. The reconciliation compares Xero’s account against the local FIFO valuation and reports one number: drift = Xero − local. Zero drift after the day’s journal is pushed and posted. Not “close enough”, not “within tolerance” — zero, or the journal doesn’t ship.
If a journal can’t trace itself back to specific layers, it doesn’t get to exist. Drift gets a number, not a vibe.
Honestly
I didn’t build this because costing is fun. Costing is not fun; costing is a machine for turning Tuesdays into arithmetic. I built it because a number I had to sign was being computed innocently wrong, and the tool responsible was never going to feel bad about it.
Now it posts COGS to Xero daily, as a draft, balanced. Someone still reviews it, because Xero’s approval step is a good idea and I’m not against adults in the room. The difference is the number under review is finally the right one, and the person reviewing it sleeps.