# PurchaseOrder

## Description

PurchaseOrder is the commercial procurement document that turns supplier pricing into a concrete supplier commitment. It owns the supplier reference, optional default receiving site, ordered lines, agreed unit prices, and the fulfillment progress that later receipts and supplier invoices consume. The header-level receiving site, when present, serves as the default delivery destination; individual lines may override it with a line-level receiving site. Service-only orders may omit the receiving site entirely.

The model separates status into three independent axes so procurement can answer each question without ambiguity: `orderStatus` tracks the document-control lifecycle (draft through ordered, cancelled, or closed), `receiptStatus` tracks how much has been physically received, and `billingStatus` tracks how much has been invoiced by the supplier. This separation allows receipt and billing to progress independently without one overwriting the other.

The purchase order supports post-approval amendments through `amendOrderedPurchaseOrder`. Amendments may target both line fields (quantity, price, line add / remove) and header fields (any non-protected header field, including app-extension fields). The current revision count, when needed, is derived from the maximum `revisionNumber` of attached `PurchaseOrderRevision` rows; it is not denormalized onto the order itself.

Revision history is stored as a two-layer structure: a `PurchaseOrderRevision` envelope captures one record per amendment (revision number, reason, timestamps), and a child `PurchaseOrderFieldChange` table captures one record per changed field (recordType `HEADER` or `LINE`, recordId, fieldName, changeKind, oldValue, newValue). Header field changes and line field changes share the same `PurchaseOrderFieldChange` table, discriminated by `recordType`. The first envelope is created by the first amendment (revisionNumber = 1); approval itself does not create one. Because line IDs never change across amendments, inventory receipt evidence and account-payable invoice lines reference the same stable ID, and receipt/billing status derivation requires no lineage resolution.

## Domain Model Definitions

### Model type

Stateful

#### State Transitions

The purchase order carries three independent status fields: `orderStatus` tracks the document-control lifecycle, `receiptStatus` tracks fulfillment progress derived from inventory receipt evidence, and `billingStatus` tracks invoice progress derived from the billed-quantity projection on purchase order lines. `receiptStatus` and `billingStatus` are derived fields initialized when `orderStatus` reaches `ORDERED`.

**orderStatus Transitions**

```mermaid
stateDiagram-v2
    [*] --> Draft: createPurchaseOrder
    Draft --> Draft: updatePurchaseOrder
    Draft --> Submitted: submitPurchaseOrder
    Submitted --> Draft: rejectPurchaseOrder
    Submitted --> Ordered: approvePurchaseOrder
    Draft --> Cancelled: cancelPurchaseOrder
    Submitted --> Cancelled: cancelPurchaseOrder
    Ordered --> Cancelled: cancelPurchaseOrder
    Ordered --> Closed: closePurchaseOrder
```

**receiptStatus Transitions**

```mermaid
stateDiagram-v2
    [*] --> NotReceived: approvePurchaseOrder
    NotReceived --> PartiallyReceived: inventory receipt posted
    NotReceived --> Received: inventory receipt posted
    PartiallyReceived --> PartiallyReceived: inventory receipt posted
    PartiallyReceived --> Received: inventory receipt posted
```

**billingStatus Transitions**

```mermaid
stateDiagram-v2
    [*] --> NotBilled: approvePurchaseOrder
    NotBilled --> PartiallyBilled: billing feedback
    NotBilled --> Billed: billing feedback
    PartiallyBilled --> PartiallyBilled: billing feedback
    PartiallyBilled --> Billed: billing feedback
    PartiallyBilled --> NotBilled: billing feedback reversal
    Billed --> PartiallyBilled: billing feedback reversal
    Billed --> NotBilled: billing feedback reversal
```

`receiptStatus` and `billingStatus` are derived — they are recalculated by fulfillment and billing commands, not driven by explicit user transitions. Only `orderStatus` is the user-driven lifecycle; its transitions are:

| Operation | From | To | Command |
|-----------|------|----|---------|
| submit | DRAFT | SUBMITTED | [submitPurchaseOrder](../command/SubmitPurchaseOrder.md) |
| reject | SUBMITTED | DRAFT | [rejectPurchaseOrder](../command/RejectPurchaseOrder.md) |
| approve | SUBMITTED | ORDERED | [approvePurchaseOrder](../command/ApprovePurchaseOrder.md) |
| cancel | DRAFT, SUBMITTED, ORDERED | CANCELLED | [cancelPurchaseOrder](../command/CancelPurchaseOrder.md) |
| close | ORDERED | CLOSED | [closePurchaseOrder](../command/ClosePurchaseOrder.md) |

### Command Definitions

- [createPurchaseOrder](../command/CreatePurchaseOrder.md) - Create a new draft purchase order with one or more lines
- [updatePurchaseOrder](../command/UpdatePurchaseOrder.md) - Revise mutable draft commercial fields and line content
- [submitPurchaseOrder](../command/SubmitPurchaseOrder.md) - Validate draft completeness and move the order into submitted review
- [rejectPurchaseOrder](../command/RejectPurchaseOrder.md) - Return a submitted order to draft with a rejection reason
- [approvePurchaseOrder](../command/ApprovePurchaseOrder.md) - Freeze commercial terms and release a submitted order to the supplier
- [cancelPurchaseOrder](../command/CancelPurchaseOrder.md) - Cancel an order before downstream fulfillment or billing makes it irreversible
- [closePurchaseOrder](../command/ClosePurchaseOrder.md) - Close remaining open quantity after fulfillment or explicit write-off
- [recalculatePurchaseOrderBillingStatus](../command/RecalculatePurchaseOrderBillingStatus.md) - Update billed progress and `billingStatus` from external billing feedback
- [amendOrderedPurchaseOrder](../command/AmendOrderedPurchaseOrder.md) - Amend an ordered PO by adding, modifying, or removing lines with revision snapshots

### Query Definitions

- [getPurchaseOrder](../query/GetPurchaseOrder.md) - Retrieve a purchase order with its lines, snapshots, and fulfillment totals

### Models

- PurchaseOrder
- PurchaseOrderLine
- PurchaseOrderRevision
- PurchaseOrderFieldChange

### Invariants

- A purchase order always belongs to exactly one company and one supplier
- Submitted, ordered, and closed orders must contain at least one line
- Every line references an active item, a unit of measure, has ordered quantity greater than zero, and has a non-negative agreed unit price
- The header-level receiving site is optional; when present it serves as the default delivery destination for lines without their own receiving site
- Physical-receipt lines must resolve a receiving site from either the line or the header; if neither is set, the order cannot be created
- Commercial fields that define the supplier commitment are mutable only while the order is in `DRAFT`
- The ordered unit price snapshot is immutable after the order enters `ORDERED`
- Cumulative received quantity cannot be negative and may exceed ordered quantity to preserve physical receipt history
- Cumulative billed quantity cannot be negative and cannot exceed ordered quantity unless account-payable feedback reflects a released invoice hold
- Cancellation is not allowed once a posted receipt or a billed quantity already exists
- External supplier order reference is optional correlation metadata and is not required to be unique; the same value may appear on multiple purchase orders
- Cancellation is not allowed if any line has posted receipts or billed quantity
- Cancellation is not allowed if any active line has posted receipts or billed quantity
- Every purchase order line belongs to exactly one purchase order and inherits its company scope
- Each line must reference one ACTIVE item, a unit of measure, a positive ordered quantity, and a non-negative unit price
- Each line carries nullable `billedQuantity` as a purchase-owned projection of current invoiced quantity; null is treated as zero
- Once the parent order is approved, line-level commercial fields are immutable except through amendOrderedPurchaseOrder
- Each line carries a `lineStatus` (ACTIVE or CANCELLED, default ACTIVE); only ACTIVE lines define the current commercial commitment
- A line's primary key (UUID) is stable across revisions — it is never replaced by a new record when an amendment modifies it
- Each amendment writes exactly one `PurchaseOrderRevision` envelope row whose `revisionNumber` is `MAX(existing) + 1`, starting at 1 for the first amendment; approval itself does not create an envelope
- `PurchaseOrderFieldChange` rows are immutable per-field deltas; for `MODIFIED` both `oldValue` and `newValue` are populated, for `ADDED` `oldValue` is null, for `REMOVED` `newValue` is null
- `PurchaseOrderFieldChange.recordType` discriminates header vs line changes; `recordId` points to the affected `PurchaseOrder.id` or `PurchaseOrderLine.id`
- Values in `oldValue` / `newValue` are stored as strings; callers serialize per the field's underlying type (ISO date for dates, decimal string for decimals, uuid string for foreign keys)

### Relationships

- **Belongs To BusinessPartner**: Each order references one supplier [BusinessPartner](../../../business-partner/docs/model/BusinessPartner.md)
- **Belongs To Site**: Each order references a default receiving [Site](../../../organization/docs/model/Site.md) at the header level
- **PurchaseOrderLine May Override Site**: Each line may optionally reference a different receiving [Site](../../../organization/docs/model/Site.md) to override the header default
- **Has Many PurchaseOrderLines**: Each order contains one or more commercial commitment lines
- **Snapshots PurchasePriceRule**: Each line may preserve the matched [PurchasePriceRule](./PurchasePriceRule.md) reference and final ordered price
- **Referenced By Inventory Receipt Evidence**: Inventory-owned `GOODS_RECEIPT` execution lines optionally consume open ordered quantity through their source line ID.
- **Referenced By Account-Payable Invoice Lines (cross-module)**: Account-payable invoice lines optionally reference a `PurchaseOrderLine` to consume open billed quantity; invoices can also exist without a PO reference for spot purchases or expense-type invoices
- **PurchaseOrderLine References Item (cross-module)**: `itemId` must point to an ACTIVE item from item-management
- **PurchaseOrderLine Referenced By InventoryExecutionLine**: Inventory receipt execution lines optionally consume open receivable quantity from one order line; PO-unlinked receipt lines do not affect order line quantities
- **PurchaseOrderLine Billing Projection**: Account-payable billing feedback updates each line's `billedQuantity`; PO-unlinked AP invoice lines do not affect order line quantities
- **Amended By amendOrderedPurchaseOrder**: Post-approval amendments modify header fields, add, modify, or remove lines with deltas recorded in PurchaseOrderRevision (envelope) and PurchaseOrderFieldChange (per-field)
- **Has Many PurchaseOrderRevisions**: Each post-approval amendment creates one `PurchaseOrderRevision` envelope; an unamended order has zero envelopes
- **Referenced By PurchaseRequisitionLineAllocation**: When a purchase order is created from a requisition, [PurchaseRequisitionLineAllocation](./PurchaseRequisitionLineAllocation.md) records link each purchase order line back to its source requisition line
