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

6.6 KiB
Raw Permalink Blame History

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:

  1. 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.
  2. 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_number snapshot (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
    1. Configure contract assignment PO fields (po_required, po_number, optional po_amount).
    2. Generate invoice(s) (single or batch).
    3. Review invoice details and PDF; PO is visible.
    4. Export invoices to QBO/Xero; PO reference is available externally.

UX / UI Notes

  • Invoice detail view:
    • Display PO number when present.
    • Display PO “authorized” amount (from contract assignment) and “consumed/remaining” summary when po_amount is 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_number populated at invoice creation time from the associated client_contracts.po_number.
    • Add invoices.client_contract_id populated 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.
  • PO-required behavior
    • If client_contracts.po_required = true, invoice generation remains blocked when po_number is missing.
  • PO limit advisory (authorized total spend)
    • Finalized mapping (based on current UI + invoice modification logic):
      • Treat invoice as finalized when finalized_at is set OR status is one of sent, paid, overdue, prepayment, partially_applied.
      • Do not count draft, pending, or cancelled toward consumption.
    • If client_contracts.po_amount is set, compute:
      • consumed: sum of invoices.total_amount for invoices tied to the same client_contract_id where 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.
  • PDF header
    • Default invoice template includes PO number in the header when present.
  • Accounting exports
    • Include PO number on exported invoices in the external systems “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 reference while preserving the invoice identifier (format TBD).
      • Xero CSV export: include in Reference while preserving existing matching identifiers (format TBD).

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_id and status.
  • Invoice view models / API responses should include the new invoice-level PO fields for UI + exports.
  • Accounting export adapters should load invoice po_number and 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_number and shows it in invoice UI + default PDF header.
  • When po_amount is 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.