Skip to content

Retroactive Accounting Migration

If a tenant has been running on Fexl Lite since before the accounting layer landed — or imported their data from a different POS — their journal_entries table starts empty even though the invoices, expenses, inventory_transactions, and customer_payments tables are full. The Trial Balance is empty. The P&L shows zeros. The Balance Sheet has no Cash & Equivalents number to draw on. The Retroactive Migration is the one-button fix: it walks the historical operational tables and posts the journal entries that should have been written at the time.

Updated 4 May 2026·For v1.6.100·4 min read
settings · accounting · run retroactive migration button + progress dialog

When you need it

Run the migration if:

  • The Trial Balance shows the empty-state hint “Run the retroactive migration from Settings → Accounting to populate journal entries.”
  • The P&L shows zero revenue / zero COGS even though the Invoices page lists hundreds of completed invoices.
  • A tenant was upgraded from a pre-accounting build of Fexl Lite (before v1.6.0).
  • A tenant imported data from another system into the operational tables without populating journal_entries.

If you started on a recent Fexl Lite version with a fresh tenant, you don’t need this — the chokepoint that posts JEs has been firing on every flow since day one of your usage, and the books are already complete.

What the migration does

Per tenant, the migration walks each operational table in chronological order and posts the JE that the matching flow would have posted at the time:

  1. Invoices (non-cancelled, non-comp) — for each invoice, posts the standard cash + revenue + COGS + inventory pattern. Backdates the JE to the invoice’s date.
  2. Refunds — posts the reverse-revenue + cash-out + re-ingress JE for every completed return.
  3. Expenses — posts DR 6xxx / CR 1010-xxx (or 1300 for prepaid) for every expense row. For prepaid expenses, the recognition schedule is replayed too — every recognized=true row gets its DR 6xxx / CR 1300 JE posted at the schedule’s month.
  4. Restocks — for every PO receive and manual restock, posts DR 1200 / CR 2010 (or 1010-xxx if cash-paid).
  5. Customer payments — debt settlements (DR 1010-xxx / CR 1100), credit usage (DR 2100 / CR 1100), credit withdrawals (DR 2100 / CR 1010-xxx).
  6. Cash drawer transactions — every cash-in, cash-out, and transfer that already has a row in cash_drawer_transactions.

Comp invoices are deliberately skipped — they have no JE by design, and the migration honours that.

Idempotency

How to run it

1

Open Settings → Accounting

In the desktop or web app, SettingsAccounting. The Retroactive Migration card is on this page.

2

Read the warning, then click Run

The card shows a warning that this writes new rows to journal_entries for the entire history — clicking through is an explicit acknowledgement. The button is permission-gated to accounting:migrate; sales-agent roles don’t see it.

3

Wait for the progress dialog

A progress dialog shows per-table progress: “Invoices: 1,247 / 1,500”, “Expenses: 320 / 320”, etc. Larger tenants take a few minutes; smaller ones complete in seconds.

4

Verify with the Trial Balance

Once the dialog closes, open the Trial Balance. The chip at the top should read Balanced. Run a P&L for a wide date range and confirm revenue, COGS, and expense numbers are now non-zero and match what the invoices, products, and expenses pages have shown all along.

What can go wrong, and the recovery path

What the migration does not do

  • It does not invent missing data. If an invoice was deleted from the operational table, no JE will be posted for it. The migration walks what’s there.
  • It does not validate amounts upstream. If invoices.total doesn’t equal Σ items.subtotal − discounts + VAT + shipping, the migration uses what’s on the invoice header, even if the items don’t tie. Fix data shape problems before running.
  • It does not split bonus-vs-sold COGS retroactively if is_bonus wasn’t tracked at the time — older imports may post all COGS to 5010 even for items that should have hit 5020. That’s a reporting limitation, not a migration bug.
  • It does not run on cancelled records. Cancelled invoices are skipped — they wouldn’t have posted a JE the first time around either.