Plan: QuickBooks → ERPNext migration (5 LLCs)¶
URL: https://mkdocs.justinsforge.com/memory/plans/quickbooks-to-erpnext-migration-2026-06-07/
Date: 2026-06-07 · Status: approved spec, pre-build (no code written yet) · Pick up: Monday 2026-06-09 Parity / why: quickbooks-to-erpnext-parity-2026-06-07 Live-state / incident: erpnext-biz-apps-state-review-2026-06-07
The goal in one line¶
Move all bookkeeping for the 5 LLCs off QuickBooks Online (5 × Simple Start, ~$80/mo, likely jumping to ~$175/mo when the 50%-off promo ends) onto the self-hosted ERPNext on CT 104, with clean books that reconcile to the penny, then cancel QuickBooks. Correctness-gated, not calendar-gated: target end of June, slip a couple weeks if the numbers don't tie out. ~1 year of QB runway, so there is no reason to rush into bad books.
Already done (2026-06-07)¶
- ERPNext stack recovered from an 8-day silent outage and verified healthy.
- All 5 companies confirmed seeded: WIEB (group/parent), JWVR / NOVA / SIP (subs), GOC (40% equity-method). 494-account chart of accounts, FY2026.
- Parity confirmed: ERPNext meets or beats Simple Start on every axis; only the bank feed must be built and only sales tax has no native equivalent (Shopify owns Nova's).
Locked decisions¶
| Decision | Choice |
|---|---|
| History depth | Full transaction history, imported as dated journal entries mirroring the QB General Ledger, reconciled to the penny per entity. |
| Past business costs you paid personally | Booked as owner equity contribution (no cash back). |
| Personal spend that ran through business accounts | Reclassified as owner distribution (caught during reconciliation). |
| Going-forward (business expense on personal card) | Telegram capture on the spot → monthly reimbursement batch (accountable plan). |
| Sales tax | Shopify owns Nova's collection/filing; ERPNext records the liability only. |
| Bank feed | SimpleFIN Bridge (default) ~$15/yr. Flip to Plaid or OFX/CSV import Monday if you prefer (see options in parity doc). |
Open items (need you / your CPA)¶
- Confirm the bank feed pick (default SimpleFIN). Decide Monday.
- CPA sign-off on the equity-contribution + distribution treatment, and confirm they need nothing else from QB before cancel.
- Surface the personally-paid business costs that never hit QB (from your personal card/bank statements). This is the manual dig only you can do; Forge will make the intake easy.
START HERE MONDAY (your manual steps, no code)¶
Do these in order. They unblock everything I build, and #1 is the one thing you cannot do after cancelling QB.
- 1. Export and archive all 5 QB files. In each of the 5 QBO companies, export to Excel/CSV: General Ledger (all dates), Profit & Loss, Balance Sheet, Trial Balance, and the Customer / Vendor / Chart-of-Accounts lists. Drop them in Google Drive at
Business/QB-Archive/<entity>/. This is your permanent backup and my import source. (~1-2 hrs.) - 2. Pick + set up the bank feed. Confirm SimpleFIN (or tell me Plaid / manual OFX). If SimpleFIN: create the account, add each entity's bank + credit-card logins, grab the access token, and drop it in
~/.forge-secrets/(I'll tell you the exact filename). (~30 min.) - 3. Email your CPA the two questions above (equity/distribution treatment + anything they need pre-cancel). Async, just get it moving.
- 4. Ping me to start the build. Once 1-2 are done, I run the infra + foundation tasks below.
Build plan (I execute these when you kick them off)¶
Phased. Each task names its files, what it does, how it's verified, and its commit. You are never asked to write or edit code.
Phase 1: Infra hardening (unblocks everything, do first)¶
Task 1, pin CT 104 to a static IP.
- Files: Finn LXC config /etc/pve/lxc/104.conf (via ssh finn), or a DHCP reservation on the GFiber gateway.
- What: Give CT 104 a fixed 192.168.86.194 so DHCP can't drift again (drift already broke the tunnel once and the secrets file).
- Verification: ssh biz-apps 'hostname -I' returns .194 after pct reboot 104.
- Commit: biz-apps: pin CT 104 to static 192.168.86.194
Task 2, fix the stale ERPNEXT_URL.
- Files: ~/.forge-secrets/erpnext.env
- What: Change ERPNEXT_URL from the dead http://192.168.86.66:8080 to http://192.168.86.194:8080.
- Verification: set -a; . ~/.forge-secrets/erpnext.env; set +a; python3 -c "import os,urllib.request as u; print(u.urlopen(u.Request(os.environ['ERPNEXT_URL']+'/api/method/ping',headers={'Host':'erpnext.justinsforge.com'})).read())" prints {"message":"pong"}.
- Commit: secrets: point ERPNEXT_URL at current CT 104 IP (note: secrets file is outside git; commit is the topic-file/doc update)
Task 3, add ERPNext to health monitoring.
- Files: scripts/forge_canary.sh (or the canary's check list) + scripts/forge_erpnext_healthcheck.py (new).
- What: Hit /api/method/ping through the LAN backend; push a critical notify if it's not pong. This is why the 8-day outage went unseen.
- Verification: python3 scripts/forge_erpnext_healthcheck.py exits 0 + prints OK while up; returns non-zero when the stack is stopped.
- Commit: canary: add ERPNext origin health check
Task 4, create the dedicated ERPNext API user.
- Files: scripts/forge_erpnext_create_bot_user.py (new); ~/.forge-secrets/erpnext.env (swap key).
- What: Create [email protected] (System Manager), generate its API key, migrate Forge scripts off Administrator.
- Verification: a Company list call with the new key returns the 5 companies.
- Commit: erpnext: dedicated forge-bot API user, off Administrator
Phase 2: Foundation in ERPNext (you review the COA)¶
Task 5, reconcile each chart of accounts.
- Files: scripts/forge_erpnext_coa_audit.py (new); input = the QB COA exports from Monday step 1.
- What: Diff each entity's QB chart of accounts against the seeded ERPNext COA; report missing/renamed accounts so they map cleanly before any data lands.
- Verification: script prints a per-entity diff; you eyeball it; zero unmapped accounts after fixes.
- Commit: erpnext: COA audit + map vs QuickBooks per entity
Task 6, add owner-equity accounts per company.
- Files: scripts/forge_erpnext_seed_equity_accounts.py (new).
- What: On each of the 5 companies add Owner's Capital / Contributions, Owner's Distributions / Draws, and Due to/from Owner (shareholder loan). These are where the personal/business cleanup posts.
- Verification: API query shows the 3 accounts on all 5 companies.
- Commit: erpnext: owner-equity accounts on all 5 companies
Task 7, sales-tax liability templates.
- Files: scripts/forge_erpnext_tax_templates.py (new).
- What: Create a Sales Taxes & Charges Template per company that records collected sales tax as a liability (Shopify is the source of truth for Nova; ERPNext just books the number).
- Verification: template exists; a test invoice posts tax to the liability account.
- Commit: erpnext: sales-tax liability templates
Phase 3: Bank feed¶
Task 8, build the bank-feed sync.
- Files: scripts/forge_simplefin_sync.py (new) [or forge_plaid_sync.py if you pick Plaid].
- What: Pull transactions from the feed, create ERPNext Bank Transaction records against the right company + bank account, dedupe on the feed's transaction id (idempotent, re-runs never double-post).
- Verification: first run lands N transactions; immediate re-run adds 0; spot-check 3 against the bank.
- Commit: bank-feed: SimpleFIN → ERPNext Bank Transaction sync
Task 9, schedule + categorize.
- Files: ~/.config/systemd/user/forge-bank-sync.{service,timer} (new); reconciliation/matching notes.
- What: Nightly sync timer; lean on ERPNext's Bank Reconciliation Tool + matching rules for the "land in a queue → click to categorize" flow you liked in QB.
- Verification: systemctl --user status forge-bank-sync.timer active; a manual trigger populates new transactions.
- Commit: bank-feed: nightly sync timer
Phase 4: Full history import + reconcile (the long pole)¶
Task 10, build the GL importer.
- Files: scripts/forge_qb_history_import.py (new).
- What: Parse a QB General Ledger CSV → one dated Journal Entry per QB transaction (preserving date, accounts, debit/credit, memo), tagged with the QB transaction id for dedupe. Also recreate any open AR/AP items as real Sales/Purchase Invoices so aging works (likely few on Simple Start).
- Verification: import one entity; count[Journal Entry] jumps; re-run adds 0.
- Commit: qb-migrate: General Ledger → ERPNext journal entries
Task 11, build the reconciler.
- Files: scripts/forge_qb_reconcile.py (new).
- What: Compare ERPNext's Trial Balance / P&L / Balance Sheet against the QB-exported reports per entity per period; print a variance report. Iterate until every entity is $0 variance. This is the gate.
- Verification: python3 scripts/forge_qb_reconcile.py --entity WIEB prints variance: $0.00 across all three statements.
- Commit: qb-migrate: reconcile ERPNext vs QuickBooks to the penny
Tasks 10-11 are a repeated procedure per entity (WIEB, JWVR, NOVA, SIP, GOC). This is the variable-effort part: how long depends on how messy each QB file is. Budget real calendar time here.
Phase 5: Personal/business cleanup¶
Task 12, reclassify commingled spend.
- Files: worksheet output from forge_qb_reconcile.py; scripts/forge_erpnext_reclass.py (new).
- What: Personal items found in business accounts → post to Owner's Distributions. You review the flagged list; I post the reclass JEs.
- Verification: flagged items net to the distribution account; P&L no longer carries them as expenses.
- Commit: qb-migrate: reclassify personal spend to distributions
Task 13, intake the personally-paid business costs.
- Files: scripts/forge_equity_contribution_intake.py (new); CSV/Telegram intake.
- What: The business costs you fronted personally (surfaced from your statements) → posted as Owner's Capital / Contributions per entity.
- Verification: contributions total matches your submitted list; equity account reflects it.
- Commit: qb-migrate: owner contributions for personally-paid business costs
Phase 6: Go-forward workflow¶
Task 14, Telegram capture tool.
- Files: capture bot brain (scripts/forge_telegram_brain.py or the capture bot's tool module); new tool log_business_expense.
- What: "Paid $43 at Home Depot for Gus Outdoor on my personal card" → creates an Expense Claim in ERPNext against GOC, tagged for reimbursement. Reacts 👀 → 👌 per bot UX.
- Verification: a test Telegram message creates the Expense Claim; it appears in ERPNext.
- Commit: capture-bot: log_business_expense → ERPNext expense claim
Task 15, monthly reimbursement batch.
- Files: scripts/forge_monthly_reimbursement.py (new); forge-reimbursement.timer.
- What: Month-end, list that month's expense claims per entity → one reimbursement summary you approve, then post the payment (accountable plan).
- Verification: run for a test month; summary matches the logged claims.
- Commit: reimbursement: monthly accountable-plan batch
Phase 7: Cutover (you, once reconciled)¶
Task 16, cut over and cancel. - What: All 5 entities reconciled to $0, bank feed flowing, CPA happy → set each QBO company to read-only / export one final time, then cancel the 5 subscriptions. - Verification: ERPNext is the only live book; QB archive is complete; subscriptions cancelled. - Commit: n/a (operational milestone; note it in the daily log).
Final task: Register + document¶
Task 17. Update MEMORY.md index, write memory/general/reference_qb_erpnext_migration.md and reference files for each new script, refresh reference_forge_biz_apps.md, run bash scripts/forge_eval_run.sh, commit.
Effort + timeline (honest)¶
- Build work I do: roughly 3-4 focused days of coding spread across the phases (infra ~half day, foundation ~half day, bank feed ~half day, importer + reconciler ~1.5 days, bot tool + reimbursement ~half day).
- The long pole is reconciliation, Phase 4, repeated × 5 entities, plus your manual surfacing of personally-paid costs and CPA turnaround. That sets the real finish date, not the coding.
- Realistic: cutover late June is achievable if QB's books are reasonably clean; if the commingling is heavy, expect to slip into mid-July. That trade (a few extra weeks of ~$80/mo vs. clean, trusted books) is the right one.
Risks + guardrails¶
- Don't cancel QB until all 5 reconcile to $0 and you've re-exported. Hard gate.
- Bank feed reliability: SimpleFIN/Plaid can drop a bank connection; the nightly timer + the existing canary pattern will alert. OFX/CSV is the always-works fallback.
- Tax treatment is the CPA's call. I implement the mechanics (equity vs distribution); your accountant blesses the classification before cutover.
- Idempotency everywhere: every importer dedupes on a QB/feed transaction id, so re-running is always safe.
Out of scope (this round)¶
- Payroll / Gusto (separate year-end cost-cut; you pay yourself ~$40k once a year, $50/mo Gusto is wasteful but switching mid-year is messy).
- Firefly III personal budgeting.
- The GOC 40% equity-method posting cron.
- Any sales-tax tooling beyond recording the liability.