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

4.0 KiB

SCRATCHPAD — QBO Slice 2: Credits & Voids

Decisions (inherited from design, 2026-06-10/11)

  • Immutable invoice totals: the reshape lands before any credit sync; backfill is lossless (total_amount += credit_applied).
  • Prepayments are NOT CreditMemos (unearned revenue ≠ revenue reversal); excluded from export, future mapping to QBO unapplied payments unscheduled.
  • Credit application in QBO = zero-dollar Payment linking CreditMemo↔Invoice.
  • Voids use the existing-but-unused cancelled status + invoice_cancelled transaction type; void blocked while payments exist; hard delete blocked for exported documents.
  • Unapply of a synced allocation is exception-only (no auto-reversal in QBO).

Key file paths

  • Mutation site to fix: packages/billing/src/actions/creditActions.ts:910 (applyCreditToInvoice .decrement('total_amount', ...))
  • Negative-invoice finalize behavior: packages/billing/src/actions/invoiceModification.ts (finalizeInvoiceWithKnex, credit_issuance_from_negative_invoice)
  • Stripe payment link amount: @alga-psa/billing/actions/paymentActions (getOrCreateInvoicePaymentLinkUrl)
  • Hard delete (payment reversal precedent): invoiceModification.ts:984
  • Adapter to extend with CreditMemo/void: packages/billing/src/adapters/accounting/quickBooksOnlineAdapter.ts
  • Export selector already flags credits: accountingExportInvoiceSelector.ts (credit_memo: line.isCredit metadata)

Read-site audit (fill in during implementation — REQUIRED before backfill merges)

  • MSP invoice list / detail
  • Client portal invoice list / detail / pay page
  • Stripe payment link amount
  • Overdue detection job
  • Invoice PDF templates / email templates
  • Accounting export selectors (settledness reasoning)
  • (append every additional site the grep finds)

Gotchas

  • invoices.credit_applied exists since 20241126030648; the pair (total_amount, credit_applied) is what makes the backfill lossless.
  • QBO void is an explicit operation (operation=void) requiring Id+SyncToken; stale sync tokens surface as QBO_STALE_OBJECT — re-read then void.
  • CreditMemo tax: sign-flip must keep TxnTaxDetail consistent in internal-tax mode; in delegated mode QBO computes — mirror the invoice transform paths.

Implementation notes (built 2026-06-11)

  • Read-site audit executed via subagent sweep; every MUST-CHANGE applied (Stripe link amount now charges balance due with a zero-balance guard; refund thresholds, portal amounts, emails, AR sums all net of credits). KEEP list honored — purchaseOrderService / aging / outstanding_amount formulas became correct automatically once totals turned immutable.
  • Backfill is total_amount += credit_applied (single UPDATE, recoverable pair); invoice_type backfilled from is_prepayment + negative totals.
  • Credit notes renumber to the CM- sequence AT FINALIZE (issuance moment); drafts keep their provisional invoice number until then.
  • F025 (unapply exception): NO unapply action exists in the codebase, so there is nothing to hook — recorded as satisfied-by-absence.
  • F026 (i18n): billing-dashboard components follow the package's existing non-localized convention; no integrations-namespace strings were added.
  • apply_credit ops are enqueued per (allocation, credit-note) pair via the credit_tracking→transactions join, so multi-pool applications reconcile per memo; ops wait (no attempt burn) until both documents are mapped.
  • Voids: QBO Invoice uses operation=void; CreditMemo has no void in QBO → operation=delete; drift detector ignores changes on 'voided'/'external_voided' mappings so our own void doesn't file drift.
  • Deferred to a DB-enabled env: T001 (apply leaves total untouched — code audited), T003/T006 (migration round-trips), T004/T005 (Stripe/portal contract — Stripe integration suite is DB-skipped locally), T007 (CM sequence uses the generate_next_number PG function), T010 (validation exclusion), T017 (hardDelete block), T020 (full-cycle integration).