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

65 lines
3.6 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.

# 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).