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

3.6 KiB
Raw Blame History

SCRATCHPAD — QBO Slice 4: Payments Out, Class Tracking, Multi-Realm

Decisions

  • 2026-06-11 — Intuit webhooks excluded (user-confirmed). 15-minute CDC polling is competitive; webhooks get their own plan only if hosted latency becomes a real complaint. Keeps hosted/appliance behavior identical.
  • Deposit account defaults to Undeposited Funds, resolved at delivery time (not stored) so renamed/recreated accounts don't strand a stale ref.
  • Class granularity v1 = per item mapping metadata + tenant default; no per-client/per-invoice overrides.
  • default_realm becomes an explicit tenant setting; phase-1's first-stored-key ordering remains the fallback when unset.

Key file paths

  • Payment producer site: recordExternalPayment (slice-1 extraction from ee/server/src/lib/payments/PaymentService.ts)
  • Echo-suppression mechanism: payment mapping rows in tenant_external_entity_mappings (slice-1 design)
  • Catalog action pattern: packages/integrations/src/actions/qboActions.ts
  • Realm defaulting to swap to setting-backed: getDefaultQboRealmId in packages/integrations/src/lib/qbo/qboClientService.ts
  • Settings connection card to become a list: packages/integrations/src/components/settings/integrations/QboIntegrationSettings.tsx
  • Batch dialog for the realm picker: packages/billing/src/components/billing-dashboard/accounting/AccountingExportsTab.tsx

Gotchas

  • QBO Payment currency must match the customer's currency; mismatches ride the slice-1 currency exception path — multi-currency tenants will hit it.
  • DepositToAccountRef must reference an account of an allowed type (Other Current Asset/Bank); filter in getQboAccounts, not just in the UI.
  • The double-entry scenario (bookkeeper manually re-keys a pushed Stripe payment) is detected at application time as an over-allocation on a settled invoice — exception, never auto-reversal.
  • Disconnecting one realm of several must not deregister the other realm's cycle (singleton keys are per tenant×realm).

Implementation notes (built 2026-06-11)

  • Payment push producer lives INSIDE recordExternalPayment's success path with a provider !== 'quickbooks' echo guard, so any future provider pushes automatically; reverseExternalPayment pushes nothing (refunds out of scope).
  • DepositToAccountRef omitted when unconfigured — QBO defaults to Undeposited Funds natively (no account lookup needed). PaymentRefNum truncated to QBO's 21-char limit.
  • Echo suppression verified end-to-end: push stores String(response.SyncToken) in the payment mapping; the CDC change for the same payment carries the same token → inbound applier no-ops.
  • Double-entry guard: NEW external payments against fully-settled invoices file an accounting_sync_unmapped_payment exception with reason 'over_application' and apply nothing; partial invoices accept normally.
  • Realm UX landed in QboSyncHealthPanel (realm list + make-default via AccountingSyncHealth.realms + setDefaultQboRealm) instead of the integrations connection card — package-direction constraint. Batch dialog realm picker appears only with >1 realm. Wizard intentionally runs against the default realm (resolveDefaultRealm inside the onboarding actions).
  • resolveDefaultRealm (settings.defaultRealm validated against the credential map, else first-stored-key) adopted by ALL billing call sites including the four syncProducers producers (follow-up TODO closed same day).
  • Settings-dir contract suites (Microsoft/Xero/MspSso) have 13 pre-existing, shuffle-sensitive failures — identical on a clean tree (stash-verified).