# PurchaseOrder

## Description

PurchaseOrder is the commercial procurement document that turns supplier pricing into a concrete supplier commitment. It owns the supplier reference, transaction currency, 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 the received-quantity projection on purchase order lines, 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: receipt feedback
    NotReceived --> Received: receipt feedback
    PartiallyReceived --> PartiallyReceived: receipt feedback
    PartiallyReceived --> Received: receipt feedback
```

**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
- [recalculatePurchaseOrderReceiptStatus](../command/RecalculatePurchaseOrderReceiptStatus.md) - Update received progress and `receiptStatus` from external receipt 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
- The header carries the transaction currency, which must be the owning company's base currency because the kit does not handle FX
- 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
- Each line carries nullable `receivedQuantity` as a purchase-owned projection of current received 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 SupplierAccount**: Each order references one supplier [SupplierAccount](../../../business-partner/docs/model/SupplierAccount.md)
- **Belongs To Currency (cross-module)**: Each order references its transaction [Currency](../../../primitives/docs/model/Currency.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
- **Referenced By Inbound Receipt Evidence**: Inbound-shipment-owned receipt 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 InboundShipmentLine**: Inbound shipment lines optionally consume open receivable quantity from one order line; PO-unlinked receipt lines do not affect order line quantities
- **PurchaseOrderLine Receipt Projection**: Inbound-shipment receipt feedback updates each line's `receivedQuantity`; 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
