# 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, receiving site, ordered lines, agreed unit prices, payment-term snapshot, and the fulfillment progress that later receipts and supplier bills consume.

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 PurchaseChangeOrder. The `currentRevisionNumber` field (default 1) tracks how many change orders have been applied. Each PurchaseOrderLine carries a `revisionNumber` (the PO revision in which this line became active), a `lineStatus` (ACTIVE or SUPERSEDED), a `supersededByLineId` (pointer to the replacement line), and a `rootLineId` (pointer to the original v1 line in the lineage chain, null for v1 lines). When a change order is applied, affected lines are superseded and new ACTIVE lines are created, preserving the full audit trail. Receipt and billing status derivation uses lineage-aware quantities that span all lines sharing the same `rootLineId`, so existing receipts and bills referencing superseded lines continue to count toward progress.

## Domain Model Definitions

### Model type

Stateful

#### State Transitions

The purchase order carries three independent status axes:

| Field | Values | Governed by |
|---|---|---|
| `orderStatus` | `DRAFT`, `SUBMITTED`, `ORDERED`, `CANCELLED`, `CLOSED` | Document-control commands |
| `receiptStatus` | `NOT_RECEIVED`, `PARTIALLY_RECEIVED`, `RECEIVED` | postGoodsReceipt |
| `billingStatus` | `NOT_BILLED`, `PARTIALLY_BILLED`, `BILLED` | matchPurchaseBill |

`receiptStatus` and `billingStatus` are initialized to `NOT_RECEIVED` and `NOT_BILLED` respectively when `orderStatus` transitions to `ORDERED`, and are only meaningful while `orderStatus` is `ORDERED` or `CLOSED`. Both statuses are recalculated using lineage-aware quantities when a change order is applied via applyPurchaseChangeOrder.

**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
```

### 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
- [applyPurchaseChangeOrder](../commands/ApplyPurchaseChangeOrder.md) - Supersede affected lines, create new active lines, increment `currentRevisionNumber`, and recalculate `receiptStatus` and `billingStatus` via lineage

### Query Definitions

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

### Models

- PurchaseOrder
- PurchaseOrderLine

### 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, has ordered quantity greater than zero, and has a non-negative agreed unit price
- Lines that require physical receipt must carry a receiving site before the order can leave draft
- 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
- A purchase order with an open change order (DRAFT, SUBMITTED, or APPROVED) cannot be closed or cancelled
- Cancellation is not allowed if any line (including SUPERSEDED) 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 positive ordered quantity, and a non-negative unit price
- Once the parent order is approved, line-level commercial fields are immutable except through a PurchaseChangeOrder
- Each line carries a `lineStatus` (ACTIVE or SUPERSEDED, default ACTIVE); only ACTIVE lines define the current commercial commitment
- Each line carries a `revisionNumber` indicating the PO revision in which it became active (default 1)
- A SUPERSEDED line's `supersededByLineId` points to its replacement line (null for removed lines)
- A line's `rootLineId` traces its lineage to the original v1 line; null for v1 lines themselves

### Relationships

- **Belongs To BusinessPartner**: Each order references one supplier [BusinessPartner](../../../business-partner/docs/models/BusinessPartner.md)
- **Belongs To Site**: Each order belongs to one receiving [Site](../../../organization/docs/models/Site.md) at document level, with line-level overrides allowed as an extension
- **Has Many PurchaseOrderLines**: Each order contains one or more commercial commitment lines
- **Snapshots PurchasePaymentTerm**: The order stores one immutable [PurchasePaymentTerm](./PurchasePaymentTerm.md) 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 References PurchasePriceRule**: `matchedPriceRuleId` is optional and records which rule defaulted the commercial price
- **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
- **Referenced By PurchaseChangeOrder**: [PurchaseChangeOrder](./PurchaseChangeOrder.md) targets one purchase order to propose post-approval amendments to its lines
