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
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
65 lines
3.6 KiB
Markdown
65 lines
3.6 KiB
Markdown
# 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).
|