QuickBooks Billing Integration — Status & Open Decisions

Orbit (Mars Work Order System) · Spec 29 follow-up · last synced 2026-08-13 · foundation merged (PR #211)

Internal review page. This is a living status page for the finance, tax, billing-ops, and engineering reviewers working the QuickBooks Online (QBO) billing integration. It tracks what is built, which accounting-policy decisions are settled, and what is still open. Use the feedback section at the bottom to answer the open questions directly. Facts here are sourced from Ron's GL mapping and the billing-decisions record — please flag anything that has drifted.

1. Current state — what's built & working

Merged to main (PR #211). The charge-driven billing foundation is now merged: the invoice-number counter, the invoices/invoice_lines aggregate (migration 0032), the charge-driven invoice builder, and the GL resolver (charge description → income account, from Ron's map) all landed on main. It ships behind the QBO_BILLING_ENABLED flag (inert) — the live path is untouched. CI deployed it to the sandbox, and migration 0032 is applied to the real sandbox database.

We've built an integration that posts completed Orbit work orders into QuickBooks Online as customer invoices, validated against the QBO sandbox. It is a one-directional push: we create invoices. It is not two-way — we do not update, void, or read payment status back yet. The new charge-driven invoice model (multi-line invoices, Mars-minted invoice numbers, the invoice/line data model) is built and validated behind a feature flag, with the live path untouched. The full backend suite is green at 569 tests.

CapabilityDirectionStatus
Create a new invoiceWriteLive & validated against the QBO sandbox
Query / read (ping, invoice lookup tooling)ReadLive
Charge-driven multi-line invoice model (invoices + invoice_lines, invoice-number counter)WriteBuilt behind flag — Steps A–C, live path untouched
Read QBO payment status back onto the work orderReadCoded, not wired
Create hauler bill (AP / payables)WriteParked — deferred, no dependency on invoicing
Update an existing invoiceWriteScaffolding only — sent invoices are not edited
Void / delete / credit memoWriteNot built

Build detail: migration 0032 (invoice_number_counter, invoices, invoice_lines); next_invoice_number and build_invoice_payload_from_lines are implemented and tested. The GL resolver / revenue coding (Step E) is now built. Not yet built: Step D (wire the charge-driven path into the post endpoint), the live item catalog (12 QBO items pending finance → QBO_ITEM_IDS), tax (Decision 3), and AP / status-read-back.

2. The decisions — live status

Four accounting-policy decisions plus one billing-ops confirmation gate the go-live for customer invoicing. Statuses below are current as of the last sync.

#DecisionStatusOwner / date
1 Revenue account per charge description ✓ Answered Ron · 2026-08-13
2 Charge-description naming / disambiguation ⚠ Mostly Ron · 2026-08-13
3 Tax — Avalara integration path + no double-file vs cieTrade Open David · pending
4 Hauler-bill (AP) expense account ✓ GL provided AP parked Ron · 2026-08-13
Invoice grouping / billing cadence Confirm Billing ops · pending

Decision 1 — Revenue account per charge description ✓ Answered

Ron supplied the full charge description → GL account map (MARS QBO GL Codes.xlsx). The key reframe: revenue splits by line of business, not by material. Our earlier Service Type × Material assumption was wrong — material is not a GL dimension. Each of 137 charge descriptions maps to one of 12 income accounts:

40001 Commercial · 40002 Industrial · 40003 Disposal · 40004 Portable Toilets · 40005 Mobile Units · 40006 Recycling · 40007 Fuel Surcharge · 40008 Equipment Sales · 40900 Credit Card Processing Fee · 41000 Vendor Late Fees · 42000 IT Income · 64000 Donations.

The QBO item catalog is built from this map, keyed on charge description, each pointing at its income account. Recommended grain: one item per income account (12 items), with the charge description as the line text — this keeps revenue split by line of business on the P&L.

Open confirm: item grain — 12 items (one per income account) vs 137 items (one per charge description)? Recommendation: 12.

Decision 2 — Charge-description naming / disambiguation ⚠ Mostly

Largely answered by Ron's map — the naming is the existing charge-description list, and Ron owns it. What remains is disambiguating 5 charge descriptions that map to two revenue accounts (Commercial vs Industrial) and so can't be auto-coded on an invoice:

Ron's fix: add line-of-business-specific charge descriptions (e.g. Miscellaneous – Commercial / Miscellaneous – Industrial) so each narrows to a single account. Until then, a line with one of these is disambiguated by the job's line of business at post time, or blocked — never silently defaulted.

Open: who adds the 5 LOB-specific charge descriptions, and by when?

Decision 3 — Tax via Avalara Open

Direction is set (David): we standardize on Avalara (AvaTax), which both calculates and files sales tax (New Mexico is the only live state today; cieTrade already has an Avalara connection). Tax is not computed by QuickBooks' own engine or by Mars — Avalara owns tax. Avalara sources tax by ship-to address plus a tax code per item, and a sale must be recorded in Avalara for Avalara to file it. What remains open is the integration path and one financial-risk guardrail:

⚠️ The one that bites — don't double-file Exactly one system may record each sale to Avalara, or we over-remit. cieTrade already sends transactions to Avalara. So: are the invoices we post from Mars→QBO the same sales cieTrade already runs through Avalara, or a separate stream? This is the only tax item with real financial risk and the one nobody has looked at yet.
Open: integration path — native QBO ↔ Avalara connector (least code) vs Mars-calls-AvaTax-directly vs tax-computed-upstream-in-cieTrade?
Open: the cieTrade boundary — which system is the tax system-of-record per sale, so we don't double-file?

Upside: tax is a liability, not revenue, and with the connector Mars does essentially nothing for tax — so this doesn't block the Mars build; the open work is operational (connector setup + the cieTrade boundary). Item tax codes and any non-tax whole-invoice fees (fuel surcharge already has income account 40007) are secondary specifics.

Decision 4 — Hauler-bill (AP) expense account ✓ GL provided

Answered on the accounting side: Ron's map gives an Expense GL per charge description too (50001 Commercial, 50002 Industrial, 50003 Disposal, …, plus 50000 Adjustments, 50008 COGS Equipment Sales, 50009 Other Cost of Services, 51000 Vendor Late Fees). When AP is turned on, hauler costs code by the same charge-description lookup. AP posting remains parked — nothing on the customer-invoicing path depends on it. (4 descriptions are expense-ambiguous — a disambiguation for when AP is built, not now.)

Invoice grouping — confirm Confirm

Invoices will be one per customer per billing period, with each service site as a sub-account (QBO sub-customer) — one bill for the customer, per-site reporting preserved, and it interacts with tax (sourced by service address).

Confirm: does one-invoice-per-customer-per-period with sites as sub-customers match how customers should be billed — and what is the cadence (monthly)?

3. What each decision unblocks

DecisionUnblocks
1 — revenue by line of business doneThe QBO item catalog (Step E) — items keyed to the 12 income accounts. Pending only the 12-vs-137 grain confirm.
2 — charge-desc disambiguation mostlyClean auto-coding of every invoice line. The 5 LOB-specific descriptions close the last gap so no line is blocked or hand-routed.
3 — Avalara path + boundary openThe tax wiring (Step E2) and go-live for customer invoicing. The double-file boundary is the go-live financial-risk gate.
4 — expense GL providedThe parked AP / hauler-bill path, when it is eventually built. Blocks nothing today.
Invoice grouping confirmFinal invoice grouping / draft-staging behaviour — which invoice a completed WO's charges attach to.

Remaining to unblock go-live: the 5 LOB-specific charge descriptions (Decision 2), the tax integration path + cieTrade boundary (Decision 3), and the grouping confirmation. Decisions 1 and 4 are answered by Ron's map.

4. Answer the open questions

Reviewers: please record answers to the open items below. There are five:

  1. Item grain — 12 items (one per income account) or 137 (one per charge description)?
  2. Tax integration path — QBO↔Avalara connector, Mars-direct, or cieTrade-upstream?
  3. cieTrade boundary — which system is the tax system-of-record per sale?
  4. Owner of the 5 LOB-specific descriptions — who adds them, and by when?
  5. Invoice-grouping cadence — confirm one-per-customer-per-period + sub-customers, and the cycle.

Structured feedback form

Live form not yet wired.

A Microsoft Form embed slot is reserved above. Until it is connected, use the email fallback below.

Email your answers

The email button pre-fills the five open questions so you can answer inline.

5. Glossary

Income accountThe GL revenue line a sale books to; what shows on the P&L. Here, one of the 12 line-of-business accounts.
Charge descriptionThe billed charge type (137 of them); the key Ron's map codes to a revenue GL and an expense GL.
Line of businessThe GL dimension revenue splits by (Commercial, Industrial, Disposal, …) — not material.
ItemThe template on each invoice line; carries the income account and (with Avalara) a tax code.
Sub-customerA child customer under a parent; multi-site billing — one bill, per-site tracking.
Bill (AP)A vendor bill: what we owe a hauler. Each line names an expense account. Parked.
Avalara / AvaTaxThird party that calculates and files sales tax; replaces QuickBooks' own tax calc.