← The QuickBooks proof
darnbooks · Sage 50 → ERPNext · export-side proof · 2026-08-23
Sage 50, held to the same penny
The same complete company that proved our QuickBooks Desktop migration — 1,191 transactions over two fiscal years — was written into Sage 50's documented export format: nine CSV files, laid out field for field the way Sage's own documentation specifies. An independent reader then rebuilt the entire books from those nine files alone. They match the canonical ledger to the penny, everywhere.
TRIAL BALANCE337,670.12Dr = Cr, rebuilt from the CSVs
1,191 transactions
1,828 line items
37 ledger accounts
9 Sage 50 CSV files
0 differences at the gate
Scope, stated plainly: this page proves the
whole lane — that Sage 50's documented CSV export format can carry a complete set of books, losslessly, into our pipeline and on into self-hosted ERPNext. The company is synthetic (no client data), the CSV layouts are mapped field by field against Sage's published import/export documentation, and every number below comes from a run you could reproduce from the repository. One thing this page does
not claim: we have not yet round-tripped a live Sage 50 install — that's mapped, not proven. Its status is spelled out
below. The ERPNext leg, though, is done: all 1,191 transactions loaded into a fresh company, read back out, and matched to the penny.
Why Sage 50, why now
On January 1, 2023, Sage stopped selling Sage 50 licenses you keep. Since then it's subscription-only, and every version ages out on a rolling schedule: version 2023 lost support in March 2024, version 2024 goes obsolete in March 2026, and the treadmill forces an upgrade roughly every twelve to fourteen months. The software tens of thousands of small businesses bought outright has become software they rent — and the vendor picks the pace.
If that's why you're reading this: your data is not stuck. Sage 50 ships a real export — File → Select Import/Export, one template per record type — and as this page shows, those files can carry your whole ledger. The destination is ERPNext, the same self-hosted system our QuickBooks proof lands on: your books, on hardware you control, with no renewal clock.
How the proof is built
Same rule as the QuickBooks lane: every system must describe the books in the same neutral format — a normalized JSON snapshot where money is a decimal string, never a float. The generator writes the canonical books that way, and it also writes them a second time as Sage 50's nine export CSVs: chart of accounts, customers, vendors, items, and the five journals (sales, receipts, purchases, payments, general). A separate reader — sage50_read.py, which shares no code path with the emitter's layout logic — parses only the CSVs and produces the same normalized snapshot. The parity checker compares the two files. It doesn't know or care where they came from.
The CSV side is deliberately faithful to Sage's documented behavior: one row per distribution line with header fields repeated, transactions grouped by Transaction Number, positive amounts as debits and negatives as credits, two-digit-year dates. Each layout decision cites Sage's own field-definition pages, and the ones Sage doesn't document are marked as ours in the format map — declared, not smuggled.
The checks, with their actual output
CHECK 1 · PASS
The nine CSVs carry the whole company
The reader rebuilds the books from the CSV files alone — trial balance, AR and AP agings, entity counts — and re-derives the trial balance from its own output as an internal gate before it will write anything. Then the checker compares that snapshot against the canonical books.
$ python3 tools/sage50_read.py --dir artifacts/sage50-seed42/sage50 \
--out sage50-company.json --from 2024-08-01 --as-of 2026-07-31
OK sage50-reader: 1191 txns, 1828 lines, 37 accounts, TB 337670.12 Dr/Cr
$ python3 tools/parity_check.py \
artifacts/sage50-seed42/synthetic-company.json sage50-company.json
Compared: trialBalance, arAging, apAging, counts
RESULT: PASS - snapshots match on all compared fields.
Unlike the QuickBooks lane, there are no "accepted deltas" here — nothing pre-created, nothing waved through. All compared fields match exactly.
CHECK 2 · FAILS WHEN IT SHOULD
Change one cent in one file, and the gate catches it
A gate that can't fail proves nothing. So we changed a single cent on one row of SALES.CSV — an 890.00 invoice line becomes 889.99 — and ran the same pipeline on the edited copy.
$ sed -i '2s/-890.00/-889.99/' scratch/sage50/SALES.CSV # one cent, one row
$ python3 tools/sage50_read.py --dir scratch/sage50 --out perturbed.json \
--from 2024-08-01 --as-of 2026-07-31
OK sage50-reader: 1191 txns, 1828 lines, 37 accounts, TB 337670.11 Dr/Cr
$ python3 tools/parity_check.py \
artifacts/sage50-seed42/synthetic-company.json perturbed.json
RESULT: FAIL - 7 difference(s):
- trialBalance: row '11000 · Accounts Receivable': A=42084.52 vs B=42084.51
- trialBalance: row '32000 · Retained Earnings': A=3137.30 vs B=3137.29
- trialBalance: TotalRow Dr: A=337670.12 vs B=337670.11
- trialBalance: TotalRow Cr: A=337670.12 vs B=337670.11
- arAging: TotalRow >90d: A=12391.00 vs B=12390.99
- arAging: TotalRow total: A=42084.52 vs B=42084.51
- arAging: row 'Redwood Credit Services' present only in B (open balance -0.01)
One cent, seven independent tripwires — including a customer whose fully-paid account now shows a phantom one-cent open balance. The checker's own self-test does the same perturbation in memory on every proof run and must catch it, or the run fails.
CHECK 3 · BYTE-IDENTICAL
The whole run is deterministic
The company generates from seed 42. Run the generator twice, you get byte-identical output — the canonical JSON and all nine CSVs. Run the reader twice on those CSVs, you get byte-identical snapshots. Anyone with the repository can regenerate everything on this page and re-run every gate.
$ sha256sum sage50-company.json artifacts/sage50-seed42/sage50-company.json
6a7594434303a1ab… sage50-company.json (fresh run)
6a7594434303a1ab… artifacts/sage50-seed42/sage50-company.json (committed)
CHECK 4 · READ BACK, PENNY-EXACT
Into ERPNext and back out again
All 1,191 transactions were loaded into a fresh company on a dedicated, disposable test instance, then read back out through the same reader that gates the QuickBooks lane — and compared against the canonical ledger. One difference was expected and declared before the run: payment terms are shared across the whole instance, so the count reads 7 where this company defines 3. The checker accepts it only because it was named in advance; everything else matches to the cent.
$ python3 tools/parity_check.py sage50-company.json erpnext-snapshot-sage50.json \
--named-deltas expected-deltas-sage50-erpnext.json
Parity check
Compared: trialBalance, arAging, apAging, counts
~ named delta (accepted): counts.terms: A=3 vs B=7
RESULT: PASS - snapshots match on all compared fields (1 named delta(s) accepted).
Shared spine with the QuickBooks proof
- Same books. The canonical company file is byte-identical to the QuickBooks lane's (
sha256 a9898bec0567…) — so the two lanes prove the same ledger through two different vendors' formats.
- Same checker. One parity tool gates both lanes. It rejects floats as a contract violation, and its ability to fail is itself tested on every run.
- Same destination. ERPNext, self-hosted — already proven end-to-end in the QuickBooks lane, with the migrated books read back out and verified to the cent.
- Same messy company. The synthetic books include the hazards that break real migrations: partial payments, credit memos, near-duplicate vendor names, a 90-day-stale receivable, a 10× price-entry anomaly. All of it survives the CSV round trip.
What isn't proven yet — and how you'll know when it is
- A live Sage 50 install round trip. The CSV layouts on this page are mapped field by field against Sage's published import/export documentation, with each claim cited to Sage's own pages. We have not yet exported from a running Sage 50 install and fed that file through the pipeline. Until we have, this stays in the "mapped and evaluated" column, not the "proven" column.
We publish gates when they pass, not before. That's the standard the QuickBooks page set, and this lane is held to it.
Chain of evidence
| What | Where |
| Canonical books (generator output, seed 42) | artifacts/sage50-seed42/synthetic-company.json · sha256 a9898bec0567… |
| Sage 50 export set (nine CSVs) | artifacts/sage50-seed42/sage50/*.CSV · per-file sha256 in the generation report |
| Reader output (rebuilt from CSVs alone) | artifacts/sage50-seed42/sage50-company.json · sha256 6a7594434303… |
| Format map, cited to Sage's documentation | sage50/docs/export-format.md |
| Generation report (counts, gates, hashes) | artifacts/sage50-seed42/generation-report.txt |
| ERPNext import ledger (complete) | artifacts/sage50-seed42/import-state.json · 1,191 transactions |
| ERPNext read-back snapshot + parity result | artifacts/sage50-seed42/erpnext-snapshot-sage50.json · PASS, 1 named delta |
| Proof record + narrative report | artifacts/sage50-proof-20260823T182642Z.json · sage50/docs/proof-report.md |
| Gate commits | darnbooks d970f75 · qbd-migration 9dd2fb2 |
| Live ERPNext target | a dedicated, disposable test instance, company “sage50-proof” |
Explaining it in sixty seconds
The problem. Sage stopped selling Sage 50 licenses you keep in January 2023, and every version now ages out on a schedule Sage picks. Businesses want off the treadmill — with their books intact, and proof.
What we built. We already move QuickBooks Desktop books into self-hosted ERPNext with an independent checker that verifies the two systems to the penny. This page adapts that machinery to Sage 50's own documented export format.
How we know it works so far. We wrote the same full test company — two years, 1,191 transactions, the messy stuff included — into Sage 50's nine export CSVs, then rebuilt the books from those files alone. Identical to the cent. Change one cent anywhere and the gate catches it; we show that failure on this page.
The honest caveat. This proves the export format carries the books — all the way into ERPNext and back out, penny-exact. The one thing not yet done is exporting from a live Sage 50 install; we say exactly where that stands, and we publish gates when they pass, not before.
Running Sage 50 and watching the renewal clock?
A 30-minute call now beats a rushed move later. You'll know what your books need before you're up against a date — and what we'd have to prove to you before taking the work.
Book a 30-min call
The QuickBooks proof →