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
73 lines
4.0 KiB
Markdown
73 lines
4.0 KiB
Markdown
# 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).
|