# Purchase Order Lifecycle

## Overview

Purchase Order Lifecycle governs how a supplier commitment is created, reviewed, placed, fulfilled, and closed. A `PurchaseOrder` records the commercial agreement with a supplier for one or more items, including ordered quantity, expected delivery, unit price, currency, and receiving site. The order is the primary document that downstream receipts and supplier invoices match against.

The lifecycle separates status into three independent axes. `orderStatus` governs the document-control lifecycle: a purchase order begins in draft, may be submitted for approval, can be explicitly rejected back to draft with a reason, becomes ordered when released to the supplier, and may eventually be cancelled or closed. `receiptStatus` independently tracks how much of the order has been physically received (not received, partially received, received). `billingStatus` independently tracks how much has been invoiced by the supplier (not billed, partially billed, billed). This separation ensures receipt and billing can progress in any order without overwriting each other, giving procurement teams a single source of truth for what is still open with each supplier.

Once a purchase order reaches `ORDERED` status, its commercial terms are immutable through normal update commands. The `amendOrderedPurchaseOrder` command allows atomic amendments — modifying header fields, adding lines, modifying quantities or prices, or removing lines. Each amendment writes one `PurchaseOrderRevision` envelope (with `revisionNumber = MAX(existing) + 1`, starting at 1) and one `PurchaseOrderFieldChange` row per modified field (header or line, discriminated by `recordType`). Recalculated receipt/billing statuses are written. REMOVE physically deletes the line from the table when no downstream references exist; the pre-deletion field values survive in `PurchaseOrderFieldChange`. Existing goods receipt and account-payable invoice references remain valid because line IDs never change.

Purchase orders also publish expected inbound supply to inventory once supplier commitment begins. The core purchase module follows a line-level fulfillment model: a purchase order line is the commercial line and the planned receipt unit. Approval creates one `InventorySupplyPlan` row for each physical-receipt line, amendments update or close the affected supply row, and cancellation or closure closes the remaining open supply. Planned split deliveries are represented by splitting purchase order lines rather than by adding schedule lines under one line. This keeps ATP and planning visibility aligned with the purchase-order lifecycle without treating expected receipts as current stock.

## Business Purpose

- Create a formal, auditable supplier commitment before goods or services are received
- Freeze negotiated commercial terms at the time an order is placed
- Capture the company and receiving-site context needed for receipt routing and supplier traceability
- Track open quantity, received quantity, billed quantity, and remaining commitment per line
- Provide the reference document for receipt matching and account-payable invoice matching (receipts and invoices can also exist without a PO reference)
- Support partial fulfillment without losing visibility into remaining supplier obligations
- Prevent inactive suppliers, inactive items, or incomplete orders from entering downstream execution

## Process Flow

```mermaid
flowchart TD
    A[Create Purchase Order] --> B["orderStatus: DRAFT"]
    B --> C[Add supplier, lines, prices, terms]
    C --> D{Submit?}
    D -->|No| C
    D -->|Yes| E["orderStatus: SUBMITTED"]
    E --> F{Approve?}
    F -->|Reject| R[REJECTED → back to DRAFT]
    R --> B
    F -->|Approve and send| G["orderStatus: ORDERED\nreceiptStatus: NOT_RECEIVED\nbillingStatus: NOT_BILLED"]
    G --> H{Receipt posted?}
    H -->|Partial| I["receiptStatus: PARTIALLY_RECEIVED"]
    H -->|Full| J["receiptStatus: RECEIVED"]
    G --> K{AP billing feedback?}
    K -->|Partial| L["billingStatus: PARTIALLY_BILLED"]
    K -->|Full| M["billingStatus: BILLED"]
    G --> N{Close remaining?}
    I --> N
    J --> N
    L --> N
    M --> N
    N -->|Close| O["orderStatus: CLOSED"]
    B --> P["orderStatus: CANCELLED"]
    E --> P
    G --> P
```

## Scenario Patterns

- **Standard stock purchase**: A buyer creates an order for replenishment items, submits it, and sends it to the supplier once approved
- **Rejected for revision**: An approver rejects a submitted order because the receiving site or supplier quote is wrong, and the order returns to draft with the rejection reason preserved for audit
- **Split delivery**: A supplier confirms two delivery dates, so the buyer represents them as two purchase order lines in the core module. The purchase order remains open after the first receipt and closes only when all ordered quantity is received or explicitly closed out. Each receipt references the purchase order through its receipt lines, not through a header-level foreign key
- **Service or non-stock procurement**: A purchase order is created for a supplier-provided service or consumable item that may still require invoice matching even if no warehouse stock is updated
- **Order cancellation before fulfillment**: An order is cancelled after submission because demand disappears and no receipt or bill has yet been matched
- **Close remaining balance**: A supplier underdelivers the final 2 units and the buyer chooses to close the remaining open quantity instead of keeping the order indefinitely open
- **Multi-line mixed fulfillment**: Some lines on the same order are fully received while others remain open, and line-level progress rolls up to `receiptStatus` and `billingStatus` independently
- **Supplier change control**: A buyer duplicates and revises a draft order rather than changing the supplier on an already ordered document, preserving traceability
- **Post-approval price amendment**: The supplier renegotiates a price increase on an ordered PO. Procurement amends the unit price on the affected line. The line is updated in-place and a revision snapshot captures the old price
- **Post-approval quantity increase**: Production needs more raw material. An amendment increases the ordered quantity. Receipt status may move from RECEIVED back to PARTIALLY_RECEIVED
- **Post-approval line removal**: A line item is cancelled before any receipts or bills. An amendment removes the line, physically deleting it after creating a revision snapshot
- **Blocked amendment removal**: A buyer tries to remove a line with existing receipts. The amendment rejects the REMOVE and instructs the buyer to MODIFY the quantity down instead

## Test Cases

- Creating a purchase order with a valid ACTIVE supplier, company, and at least one line should succeed
- A purchase order line must require an ACTIVE item, quantity greater than zero, and a non-negative unit price
- Purchase orders are always created with `orderStatus` = `DRAFT`; `receiptStatus` and `billingStatus` are not set until the order is approved
- A `DRAFT` order can be revised freely, but `ORDERED` commercial fields must not change without an explicit revision workflow
- Submitting a purchase order without any lines should fail
- Submitting a purchase order with a missing, unavailable, or cross-company supplier account should fail
- Approving or ordering a purchase order whose supplier is INACTIVE should fail
- Approving a purchase order should create inventory supply plans for physical-receipt lines
- Rejecting a submitted purchase order should record the rejection reason and return the document to `DRAFT`
- Cancelling a purchase order after a posted receipt or billed quantity exists should be prevented
- Cancelling or closing a purchase order should close open inventory supply plans linked to its lines
- Posting a partial receipt should update line received quantities and move `receiptStatus` to `PARTIALLY_RECEIVED` without affecting `billingStatus`
- Receiving account-payable billing feedback should update line billed quantities and move `billingStatus` independently of `receiptStatus`
- Closing a purchase order should set `orderStatus` to `CLOSED` only when all remaining open quantities are fulfilled, cancelled, or explicitly written off
- Purchase order totals and open balances must be isolated per company
- Each purchase order line should expose remaining quantity to receive and remaining quantity to bill
- External supplier order references are optional correlation metadata and are not required to be unique; the same reference may appear on multiple purchase orders
- Only authorized procurement users should be able to submit, approve, cancel, or close purchase orders
- Amending an `ORDERED` PO should update lines in-place with stable IDs and create one PurchaseOrderRevision envelope plus PurchaseOrderFieldChange rows for each modified field
- Amending header fields (including app-extension fields) should be supported by the same command and produce PurchaseOrderFieldChange rows with `recordType=HEADER`
- Approval should not create a PurchaseOrderRevision envelope; the first envelope is written on the first amendment with `revisionNumber=1`
- Subsequent amendments should write envelopes with `revisionNumber = MAX(existing) + 1`
- MODIFY amendment should support price-only changes (quantity not provided preserves current value)
- MODIFY amendment should fail if new quantity is below received or billed quantity
- REMOVE amendment should fail if the line has posted receipts, billed quantity, or draft downstream references
- ADD amendment should create a new line with item snapshot and positive quantity

## Reference Links

- [Odoo Purchase documentation](https://www.odoo.com/documentation/19.0/applications/inventory_and_mrp/purchase.html)
- [Oracle Fusion Cloud Procurement guide](https://docs.oracle.com/en/cloud/saas/procurement/index.html)
- [SAP procurement overview](https://help.sap.com/docs/SAP_S4HANA_CLOUD)
