Skip to content

PRD: Purchase Orders

ModuleInventory (CORE-06)PRD IDPRD-PO-001
StatusShippedOwnerInventory squad
Date2026-02-24Versionv1.0
Packages@nx/inventory · @nx/core · @nx/finance · apps/clientURDPO · POI

TL;DR

Lets a merchant record what they buy from a vendor as a purchase order (PO), drive it through a clear lifecycle, and receive the goods into stock with a verifiable audit trail. Receipt increases stock at a location, writes immutable movement records, and can post a finance transaction so a purchase shows up in the books. The result: every stock increase is tied back to a vendor, a price, and a discount/tax breakdown - no more ad-hoc, untraceable stock-ins.

1. Context & Problem

Merchants restock by buying goods from vendors, and those goods must land in inventory with a paper trail an owner can audit. Without a purchase-order document, stock can only be moved through ad-hoc tickets: a stock increase cannot be tied back to a vendor, a unit price, or a discount/tax breakdown, and there is no controlled state between "ordered" and "received". This makes cost tracking, vendor reconciliation, and tax reporting unreliable - a hard blocker for the HKD/SME bookkeeping KICKO targets.

This increment builds the PO workflow on top of the existing vendor directory and inventory stock/tracking primitives, and connects goods receipt to the finance layer.

2. Goals & Non-Goals

Goals

  • A PO document owned by a vendor, created in a single operation (vendor + line items; vendor required).
  • A controlled lifecycle with line edits allowed only while drafting.
  • Goods receipt that increases stock and writes immutable movement records, supporting full and incremental receipts.
  • Accurate line and PO totals with explicit manual-vs-system discount/tax.
  • Finance linkage: a received PO can post a finance transaction with an optional payment.

Non-Goals

  • Opening-balance / migration stock import - owned by Opening Balance Import.
  • Multi-currency stock valuation.
  • A submit-to-vendor / external approval gate.

3. Success Metrics

MetricTarget / signal
Traceability100% of vendor stock-ins flow through a PO (not ad-hoc tickets)
Receipt accuracyStock-on-hand after receipt = ordered/received qty; zero stock rows without a matching movement record
Finance coverageReceived POs that post a finance transaction (where a cost is recorded)
Cycle timeMedian time DRAFT → RECEIVED per merchant trends down

4. Personas & Use Cases

PersonaGoal in this feature
OwnerControl purchasing, see cost & vendor history, reconcile with finance
Inventory staffCreate POs, edit lines, receive goods into the right location
Accountant (via Finance)See purchases posted as finance transactions

Core scenarios: create a PO for a vendor → adjust lines while drafting → move to processing → receive goods (full or partial) → stock rises with an audit trail and an optional finance posting → close the PO.

5. User Stories

  • As inventory staff, I want to create a PO against a vendor with its line items in one step, so the order is tied to a supplier and a price.
  • As inventory staff, I want to edit PO lines while the PO is in DRAFT, so I can correct quantities and prices before committing.
  • As inventory staff, I want to receive goods against a PO, so stock increases at the chosen location with a movement record.
  • As inventory staff, I want to receive incrementally or replace a received quantity, so partial deliveries are handled correctly.
  • As an owner, I want a received PO to record a finance transaction (optionally with a payment), so purchases reflect in the books.
  • As an owner, I want to cancel a non-terminal PO, so mistaken orders don't pollute stock or finance.

6. Functional Requirements

#RequirementURD ref
FR-1Create a PO owned by a vendor (vendor required) with line items in one aggregate operationURD-PO-001..003
FR-2Lifecycle DRAFT → PROCESSING → RECEIVED → COMPLETED → CLOSED; cancel from any non-terminal state; PROCESSING → DRAFT revertURD-PO-004..006
FR-3Line items editable only in DRAFT; aggregate create/edit (PO + lines) in a single callURD-PO-007 · URD-POI-001
FR-4Goods receipt increments stock at the location and writes immutable movement recordsURD-PO-008..009
FR-5Two receipt modes: OVERRIDE (replace received qty) and ACCUMULATIVE (add to received qty)URD-PO-010
FR-6Per-line total = price × qty × multiplier + tax − discount; lines accumulate by (itemType, itemId, uom); a zeroed line is soft-deletedURD-POI-002..004
FR-7PO total = subtotal − discount + tax; discount/tax accept a manual override, flagged manual-vs-systemURD-PO-011
FR-8inventoryLocationId optional; defaults to the merchant's default locationURD-POI-005
FR-9A received PO can record a finance transaction with an optional payment and a default finance categoryURD-PO-012
FR-10A unique PO number is assigned per merchantURD-PO-002

Full requirement text and acceptance criteria live in the Inventory URD. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Data integrityStock increment and its movement record are written together - no stock change without a matching immutable audit entry
ImmutabilityMovement records are append-only; corrections happen via new entries, never edits
Tenancy & authzAll operations scoped per merchant (x-merchant-id); gated by inventory permissions
PrecisionMonetary/quantity math uses float(value, 4)
ConsistencyAggregate create/edit and receive are transactional; partial failures don't leave half-written POs
i18nUser-facing labels/statuses are bilingual ({ en, vi })

8. UX & Flows

Key screens (in apps/client): PO list, PO create, PO edit, the status-flow control, and a cancel-order confirmation.

9. Data & Domain

EntityRole
PurchaseOrderThe order document - vendor, status, totals, location, optional financeTransaction relation
PurchaseOrderItemA line - item ref (itemType, itemId, uom), qty, price, multiplier, tax, discount, line total
PO configPer-merchant PO settings, seeded at startup
Movement recordImmutable stock-movement (tracking) entry written on receipt

Conceptual only - full schema and invariants in the inventory domain model.

10. Dependencies & Assumptions

Depends on

  • Vendors (URD-VEN) - a PO requires an existing vendor.
  • Stock levels & movement audit (URD-STK · URD-TRK) - receipt builds on stock and tracking primitives.
  • Inventory locations (URD-LOC) - stock lands at a location; a merchant default must exist.
  • Finance (@nx/finance) - for the optional transaction/payment posting.

Assumptions

  • The merchant has at least one vendor and a default inventory location.
  • A finance category exists to classify the purchase posting.

11. Risks & Open Questions

Risk / questionMitigation / status
Receipt and stock write could diverge on partial failureWritten transactionally; stock has no entry without a movement record
No multi-currency supportOut of scope; document as a constraint for cross-border vendors
No approval gate before processingAccepted for SME scale; revisit if enterprise approval is required
Reverting a received PO vs. already-posted financeOpen: define the reversal/compensation path for finance entries

12. Release Plan & Launch Criteria

AspectPlan
PhaseP1 (foundation) - see URD feature catalog
RolloutAll merchants; no feature flag
MigrationNone (new entities; PO config seeded at startup)
Launch criteriaCreate→receive→stock+movement verified end-to-end; finance posting verified; totals match expected math
MonitoringPO volume per merchant, receive error rate, stock-vs-movement consistency checks

13. FAQ

Can a PO be changed after it leaves DRAFT? No - line items are editable only in DRAFT. Use PROCESSING → DRAFT revert to make changes, or cancel.

OVERRIDE vs ACCUMULATIVE receipt - what's the difference? OVERRIDE replaces the received quantity on the line; ACCUMULATIVE adds to it (for deliveries that arrive in multiple drops).

Does receiving a PO pay the vendor? Not automatically - a received PO can record a finance transaction with an optional payment. Recording a cost and paying it are separate.

What if no location is given? The PO defaults to the merchant's default inventory location.

Can I import opening stock through a PO? No - opening balances are handled by the separate Opening Balance Import feature; there is no ghost-vendor workaround.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.