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

6.3 KiB
Raw Permalink Blame History

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.