← 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

What isn't proven yet — and how you'll know when it is

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

WhatWhere
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 documentationsage50/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 resultartifacts/sage50-seed42/erpnext-snapshot-sage50.json · PASS, 1 named delta
Proof record + narrative reportartifacts/sage50-proof-20260823T182642Z.json · sage50/docs/proof-report.md
Gate commitsdarnbooks d970f75 · qbd-migration 9dd2fb2
Live ERPNext targeta 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 →