Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

128 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PRD: QBO Closed-Loop Sync — Slice 3: Onboarding & Reconciliation
- **Status:** Draft
- **Owner:** Robert Isaacs
- **Created:** 2026-06-11
- **Design:** `../2026-06-11-qbo-phase2-closed-loop/design.md`
- **Depends on:** Slice 1 (engine, payment applier, mapping ledger); benefits from slice 2 but does not require it
## 1. Problem statement & user value
Nearly every tenant connecting QBO connects to an **established** company
file: customers, invoices, and payment history already live there. Today the
integration auto-provisions customers by display name on first export — which
duplicates "Acme Corporation" when Alga says "Acme Corp" — and offers no way
to link existing QBO invoices, so a re-export of history would double-book it.
Payments recorded before connecting never replay through CDC, so linked
history would read unpaid in Alga forever.
This slice makes first connect safe and deliberate: explicit customer
mapping with auto-match assistance, historical invoice linking without
export, a one-time payment-status backfill for matched history, and a
go-live cutoff that fences which invoices auto-sync.
## 2. Goals
- A customer mapping surface where every Alga client can be linked to an
existing QBO customer, created in QBO on demand, or left for first-export
auto-provision — with exact-name matches bulk-acceptable and fuzzy matches
human-confirmed.
- A re-runnable first-connect wizard: customers → historical invoice matching
(mappings written, nothing exported) → go-live cutoff.
- Matched historical invoices get a one-time payment backfill through
`recordExternalPayment`, so their paid state and AR are correct from day one.
- The go-live cutoff guarantees connecting never sprays history into QBO:
only invoices finalized after the cutoff auto-enqueue.
## 3. Non-goals
- Importing QBO customers as new Alga clients (link-only; creation flows
Alga→QBO).
- Importing unmatched QBO invoices into Alga.
- Continuous two-way customer field sync (name/address propagation beyond the
cached display name from slice 1).
- Multi-realm wizard flows (single/default realm this slice; realm picker
arrives in slice 4).
## 4. Personas & primary flows
- **MSP billing admin (new connection):** completes OAuth, lands in the
wizard. Accepts 40 exact customer matches in one click, reviews 6 fuzzy
ones, links 200 historical invoices by doc number, opts into the payment
backfill, sets go-live to today. First scheduled cycle exports nothing
historical; the books simply agree.
- **MSP billing admin (established connection):** opens the Customers mapping
tab to fix one mis-linked client, or re-runs the wizard from settings after
a QBO file cleanup.
## 5. Functional scope
### 5.1 Customer mapping surface
- `getQboCustomers` server action (realm-scoped, cached like the existing
catalog actions; EE + `billing_settings` read gated).
- A Customers tab in the live mapping manager — a bespoke component (the
generic module pattern fits dropdown mappings; this needs match
suggestions and per-row actions): each Alga client row shows its mapping
state and offers *link to existing* (searchable QBO customer picker),
*create in QBO now* (runs the company-sync adapter immediately), or *leave*.
- Auto-match: normalized display-name comparison (case, punctuation,
whitespace, common suffixes like Inc/LLC folded). Exact matches →
bulk-accept bar ("Accept N exact matches"); near matches → suggested but
individually confirmed; everything else unmatched.
- Mapping writes go to `tenant_external_entity_mappings`
(`alga_entity_type: 'client'`, realm-scoped), the same rows the exporter's
customer resolution already consults.
### 5.2 Reconciliation wizard
- Launches after the first successful OAuth connect (no completed-wizard
record for the realm); re-runnable from QBO settings. Steps:
1. **Customers** — embeds the mapping surface (5.1) in wizard chrome.
2. **Historical invoices** — fetch QBO invoices (paged), candidate-match
against Alga invoices by doc number + total + (when mapped) customer.
Confident matches are listed for one-click bulk link: mapping rows
written with sync-token/total snapshot, **nothing exported**. Ambiguous
candidates (number collision, total mismatch) go to a review list and
are never auto-linked. Includes the **payment backfill** option
(default on): for each linked invoice, query its QBO payments once and
apply through `recordExternalPayment` (skipping invoices already
`paid`), giving history real payment records and correct status.
3. **Go-live cutoff** — set `auto_sync_start_date` (default today):
the slice-1 finalize producer only enqueues invoices finalized on/after
this date. Existing unexported invoices before the cutoff remain
manual-batch only.
- Wizard completion recorded per tenant×realm; settings shows
completed/last-run state next to the re-run entry point.
## 6. Data model & API notes
- Tenant settings: `auto_sync_start_date`, wizard completion record
(per realm). No new tables — match candidates are computed live so the
wizard is naturally idempotent and re-runnable.
- Slice-1 producer gains the cutoff check (one condition).
- QBO API surface: Customer query (paged), Invoice query by date window
(paged), Payment query by invoice — all read-only additions to
`QboClientService`.
## 7. Risks & open questions
- Name normalization quality drives the wizard's first impression; ship the
folding rules with table-driven tests and keep "exact" strict (normalized
equality only — similarity scoring stays suggestion-tier).
- Payment backfill volume: a tenant with years of history triggers many
Payment queries — run inside the wizard as a progress-reporting batch, not
in the 15-minute cycle.
- Doc-number matching assumes Alga invoice numbers were used in QBO
historically; where they weren't, matching legitimately finds nothing —
the wizard must make "0 matches" a normal outcome, not an error.
## 8. Acceptance criteria / definition of done
- Features implemented; automated tests green; slice-1 suites unaffected.
- Live sandbox smoke against a pre-seeded QBO file (existing customers +
invoices + payments): wizard links exact customers in bulk, links history
without exporting, backfills paid status correctly, and a post-wizard
finalize+cycle exports only the new invoice; re-running the wizard is a
no-op.