# 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 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 `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.