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
4.0 KiB
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
cancelledstatus +invoice_cancelledtransaction 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.isCreditmetadata)
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_appliedexists since20241126030648; 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_typebackfilled 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).