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
6.6 KiB
6.6 KiB
PRD — Contract Purchase Order Support (Invoice + Limit Advisory)
- Slug:
contract-purchase-order-support - Date:
2026-01-05 - Status: Draft
Summary
Add purchase order (PO) support to contract billing in two ways:
- When an invoice is created from a PO-enabled client contract assignment, the invoice stores a snapshot of the PO number and surfaces it in the UI + default PDF header.
- When a PO has an authorized spend amount, the system computes advisory overage warnings (do not block) and provides batch-invoicing upfront handling (allow vs skip) only when an overage is possible.
Problem
MSPs frequently must include customer PO numbers on invoices to avoid AP rejections. Today, Alga stores PO context on the client contract assignment (client_contracts) but invoices do not reliably surface or export it, and PO authorized-spend limits are not enforced/advised during billing.
Goals
- Invoice records created from contract billing include an invoice-level
po_numbersnapshot (immutably tied to that invoice). - PO number appears on:
- Invoice metadata in-app
- Default invoice PDF header (when present)
- Accounting export/sync “reference” surfaces (QBO + Xero + CSV adapters where applicable)
- PO authorized spend (
po_amount) is treated as an advisory total spend cap:- Warn (do not block) when a newly generated invoice would exceed remaining PO amount.
- “Consume” PO amount only for finalized invoices; “unconsume” automatically when invoice is unfinalized (and if an invoice is cancelled/voided).
- Batch invoicing prompts upfront for overage handling when (and only when) at least one invoice could overrun a PO limit:
- Allow overages
- Skip invoices that would overrun
Non-goals
- Supporting more than one PO per invoice.
- Full PO lifecycle management (creation, multi-PO allocation, splitting charges across POs).
- Blocking invoice generation due to PO limit overage.
- Retroactively changing historical invoices when contract PO values are edited later.
Users and Primary Flows
- Billing admin / finance ops
- Configure contract assignment PO fields (
po_required,po_number, optionalpo_amount). - Generate invoice(s) (single or batch).
- Review invoice details and PDF; PO is visible.
- Export invoices to QBO/Xero; PO reference is available externally.
- Configure contract assignment PO fields (
UX / UI Notes
- Invoice detail view:
- Display PO number when present.
- Display PO “authorized” amount (from contract assignment) and “consumed/remaining” summary when
po_amountis present. - If invoice would exceed remaining PO amount, show a warning banner with overage amount; provide an explicit “Proceed anyway” confirmation.
- Batch invoicing:
- If none of the invoices being generated can overrun a PO limit, proceed without any extra prompts.
- If one or more invoices could overrun, present a single prompt up front:
- Allow overages (generate all)
- Skip overages (generate only invoices that fit)
- Summarize results (generated count, skipped count + reasons).
Requirements
Functional Requirements
- Invoice PO snapshot
- Add
invoices.po_numberpopulated at invoice creation time from the associatedclient_contracts.po_number. - Add
invoices.client_contract_idpopulated for invoices created from contract billing (single-assignment invoice scope; independent of whether the client has other active assignments). - Invoices created without contract context (pure manual, etc.) may leave these fields null.
- Add
- PO-required behavior
- If
client_contracts.po_required = true, invoice generation remains blocked whenpo_numberis missing.
- If
- PO limit advisory (authorized total spend)
- Finalized mapping (based on current UI + invoice modification logic):
- Treat invoice as finalized when
finalized_atis set ORstatusis one ofsent,paid,overdue,prepayment,partially_applied. - Do not count
draft,pending, orcancelledtoward consumption.
- Treat invoice as finalized when
- If
client_contracts.po_amountis set, compute:consumed: sum ofinvoices.total_amountfor invoices tied to the sameclient_contract_idwhere invoice is finalized per the mapping above.remaining:po_amount - consumed.overage:max(0, invoice.total_amount - remaining).
- Warn (do not block) if
overage > 0.
- Finalized mapping (based on current UI + invoice modification logic):
- PDF header
- Default invoice template includes PO number in the header when present.
- Accounting exports
- Include PO number on exported invoices in the external system’s “reference/memo” surface(s):
- QBO API export: include in
PrivateNote(or other supported memo field). - QBO CSV export: include in
Memo. - Xero API export: include in
referencewhile preserving the invoice identifier (format TBD). - Xero CSV export: include in
Referencewhile preserving existing matching identifiers (format TBD).
- QBO API export: include in
- Include PO number on exported invoices in the external system’s “reference/memo” surface(s):
Non-functional Requirements
- No new external dependencies.
- Overage checks should be query-efficient (indexes on new invoice columns; avoid N+1 in batch mode).
Data / API / Integrations
- DB migration(s)
- Add
invoices.po_number(text, nullable). - Add
invoices.client_contract_id(uuid, nullable; FK optional depending on schema conventions). - Add indexes to support lookups by
client_contract_idand status.
- Add
- Invoice view models / API responses should include the new invoice-level PO fields for UI + exports.
- Accounting export adapters should load invoice
po_numberand include it in outbound payloads.
Security / Permissions
- No new permissions beyond existing invoice read/generate/export permissions.
- Ensure client portal visibility rules remain unchanged (only show PO number where the invoice is visible).
Observability
- Not in scope beyond existing logs/errors.
Rollout / Migration
- Ship a migration adding invoice columns and backfill as “best effort” if feasible (optional).
- Ensure invoice generation still works when PO columns are absent (if runtime schema checks are used elsewhere).
Open Questions
- Formatting rules for external-system references (length limits, delimiter choice, and preserving matching keys).
Acceptance Criteria (Definition of Done)
- Creating an invoice from a PO-enabled contract assignment stores
invoices.po_numberand shows it in invoice UI + default PDF header. - When
po_amountis set, invoice preview/generation shows an overage warning when applicable and allows an explicit override. - Batch invoicing prompts for overage handling only when an overage is possible, and can skip overage invoices when requested.
- QBO + Xero exports include PO reference content in their respective memo/reference field(s) without breaking existing matching/mapping.