FIFO inventory costing Xero doesn't do. Every sale traced to the exact stock layers it consumed, voids restocked at original cost, freight capitalised into layers, and one balanced COGS journal pushed back each day.
Sales consume receipts oldest-first with deterministic tie-breaks. Two oak batches at $8.50 and $9.20 stay two truths, margins per batch, not a blend.
A voided invoice restocks the precise slices it consumed at their original costs. The audit trail shows every slice, always.
Approved bills land as cost layers automatically. Amend a bill: untouched layers recost, sold ones raise a conflict.
Build finished goods from components, cost rolls up into one finished layer at true consumed value, divided by units built.
Freight and duty spread across open layers by value or quantity. Shares sum exactly; history stays frozen.
Stock valuation reconciles against Xero's trial balance on demand. Drift gets a number, not a vibe.
Approved bills create cost layers. Approved sales consume them. Credit notes restock. Webhooks trigger instant syncs; a poller catches anything missed; every ingest is idempotent by event reference, so replays never duplicate.
Voids reverse themselves. Amendments recost or escalate. You read the conflict log, not tea leaves.
xero-bill:BILL-118layeredxero-inv:INV-214consumed 3 × FGxero-cn:CN-009restockedwebhook retry #7skipped, already seenNightly (or on demand), the day's movements collapse into one balanced manual journal: Dr COGS, Cr Inventory, split by category account. Pushed as a draft with an idempotency key, so you keep the approval step Xero was built for.
Locked periods? The push surfaces Xero's validation instead of forcing past it.
| Spreadsheets | Cin7 / DEAR | This | |
|---|---|---|---|
| Exact FIFO per sale | by hand, badly | yes | yes, layer-level |
| Voids restock original cost | you, at 11pm | yes | automatic + audited |
| Fits a workshop budget | $0 | $300+/mo | $0 self-hosted |
| Time to first number | weeks of formulas | days + onboarding call | about 60 seconds |
| Your server, your data | yes | their cloud | yes, tokens encrypted |
| Multi-org from one login | file copies | yes | yes, isolated & tested |
"Averaging isn't costing. When oak goes from $8.50 to $9.20 and you sell half of each batch, those are two different margins, and I want both numbers, exactly."Why the engine exists · read the whole story
No. Keep invoicing in Xero exactly as-is. The app reads approved invoices and bills and does its own inventory maths locally.
Yes, one-click bootstrap pulls your bill/invoice history and reconstructs layers and consumptions from scratch, then maps every Xero item to a product.
The app talks to api.xero.com and nothing else. Tokens are stored AES-256-GCM encrypted in your own Postgres. There is no telemetry, no phone-home, no vendor cloud.
Because margins differ batch to batch, and average hides that. When material prices move, average costing quietly reprices your history. FIFO keeps every sale tied to what its units truly cost.
Xero retries webhooks for 24 hours, a poller backfills anything missed, and ingest is idempotent, replays can't duplicate. Failures land in a conflict log you can clear item by item.
Documents in foreign currencies convert at the rate Xero recorded on the document; everything posts in your org's base currency, same as Xero's own GL.
# postgres running? then: git clone <your-repo> && cd xero-inventory npm ci cp .env.example .env # add XERO keys + ENCRYPTION_KEY npm run seed # demo workshop, so it's never empty npm start # → http://localhost:3000/setup
Create the account, connect Xero, let it pull your history once.
After that it just runs, and proves itself every time you look.