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

126 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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