# PurchaseOrder

## Description

PurchaseOrder is the commercial procurement document that turns supplier pricing and payment terms into a concrete supplier commitment. It owns the supplier reference, optional default receiving site, ordered lines, agreed unit prices, payment-term snapshot, and the fulfillment progress that later receipts and supplier bills 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`. The `revisionNumber` field (default 1, set on approval) tracks how many amendments have been applied. When an amendment is applied, affected lines are updated in-place — the line's primary key (UUID) remains stable across revisions. A snapshot of each affected line's pre-change state is captured as a `PurchaseOrderLineRevision` record before the update, preserving the full audit trail. Because line IDs never change, goods receipt lines and purchase bill lines always 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 recalculated by postGoodsReceipt, and `billingStatus` tracks invoice progress recalculated by matchPurchaseBill. `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: postGoodsReceipt
    NotReceived --> Received: postGoodsReceipt
    PartiallyReceived --> PartiallyReceived: postGoodsReceipt
    PartiallyReceived --> Received: postGoodsReceipt
```

**billingStatus Transitions**

```mermaid
stateDiagram-v2
    [*] --> NotBilled: approvePurchaseOrder
    NotBilled --> PartiallyBilled: matchPurchaseBill
    NotBilled --> Billed: matchPurchaseBill
    PartiallyBilled --> PartiallyBilled: matchPurchaseBill
    PartiallyBilled --> Billed: matchPurchaseBill
```

`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](../commands/SubmitPurchaseOrder.md) |
| reject | SUBMITTED | DRAFT | [rejectPurchaseOrder](../commands/RejectPurchaseOrder.md) |
| approve | SUBMITTED | ORDERED | [approvePurchaseOrder](../commands/ApprovePurchaseOrder.md) |
| cancel | DRAFT, SUBMITTED, ORDERED | CANCELLED | [cancelPurchaseOrder](../commands/CancelPurchaseOrder.md) |
| close | ORDERED | CLOSED | [closePurchaseOrder](../commands/ClosePurchaseOrder.md) |

### Command Definitions

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

### Query Definitions

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

### Models

- PurchaseOrder
- PurchaseOrderLine
- PurchaseOrderLineRevision

### 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 payment term snapshot and ordered unit price snapshot are immutable after the order enters `ORDERED`
- Cumulative received quantity and cumulative billed quantity cannot be negative and cannot exceed business tolerance rules for the line
- Cancellation is not allowed once a posted receipt or a matched supplier bill already exists
- External supplier order reference is unique within one company when provided
- Cancellation is not allowed if any line has posted receipts or matched bills
- Cancellation is not allowed if any active line has posted receipts or matched bills
- 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
- 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
- `PurchaseOrderLineRevision` records are immutable snapshots of a line's state before an amendment was applied; they capture the revision number, previous quantity, previous unit price, and the amendment reason

### Relationships

- **Belongs To BusinessPartner**: Each order references one supplier [BusinessPartner](../../../business-partner/docs/models/BusinessPartner.md)
- **Belongs To Site**: Each order references a default receiving [Site](../../../organization/docs/models/Site.md) at the header level
- **PurchaseOrderLine May Override Site**: Each line may optionally reference a different receiving [Site](../../../organization/docs/models/Site.md) to override the header default
- **Has Many PurchaseOrderLines**: Each order contains one or more commercial commitment lines
- **Snapshots PurchasePaymentTerm**: The order requires one [PurchasePaymentTerm](./PurchasePaymentTerm.md) reference and stores an immutable snapshot for downstream bill calculation
- **Snapshots PurchasePriceRule**: Each line may preserve the matched [PurchasePriceRule](./PurchasePriceRule.md) reference and final ordered price
- **Referenced By GoodsReceipt (via lines)**: [GoodsReceipt](./GoodsReceipt.md) lines optionally consume open ordered quantity; the receipt header does not carry a direct purchase-order foreign key, so the relationship is expressed through GoodsReceiptLine → PurchaseOrderLine (nullable). Receipts can exist without a PO reference for blind receipts, non-PO purchases, or consignment scenarios
- **Referenced By PurchaseBill (via lines)**: [PurchaseBill](./PurchaseBill.md) lines optionally consume open billed quantity; the bill header does not carry a direct purchase-order foreign key, so the relationship is expressed through PurchaseBillLine → PurchaseOrderLine (nullable). Bills can 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 GoodsReceiptLine**: Goods receipt lines optionally consume open receivable quantity from one order line; PO-unlinked receipt lines do not affect order line quantities
- **PurchaseOrderLine Referenced By PurchaseBillLine**: Purchase bill lines optionally consume open billable quantity from one order line; PO-unlinked bill lines do not affect order line quantities
- **Amended By amendOrderedPurchaseOrder**: Post-approval amendments add, modify, or remove lines with revision snapshots recorded in PurchaseOrderLineRevision
- **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
