# Purchase Change Order

## Overview

Purchase Change Order governs how an approved purchase order is amended after it has been released to the supplier. Once a `PurchaseOrder` reaches `ORDERED` status, its commercial terms are immutable through normal update commands. A `PurchaseChangeOrder` is a separate document that proposes changes to an existing ordered purchase order — adding lines, modifying quantities or prices, or removing lines — subject to approval before the changes take effect.

A change order introduces two new types: `PurchaseChangeOrder` (the change request document with its own approval lifecycle) and `PurchaseChangeOrderLine` (each proposed ADD, MODIFY, or REMOVE action). The `PurchaseChangeOrder` carries a `purchaseOrderId`, `fromRevisionNumber`, `reason`, and lifecycle status (`DRAFT` → `SUBMITTED` → `APPROVED` → `APPLIED`, with `REJECTED` and `CANCELLED` branches). Each `PurchaseChangeOrderLine` specifies an `action` (ADD, MODIFY, or REMOVE), the `originalPurchaseOrderLineId` for MODIFY and REMOVE, and the proposed new `quantity`, `unitPrice`, and `itemId` (for ADD only).

When a change order is approved and applied, the affected purchase order lines are superseded and replaced by new lines carrying the revised terms. The `PurchaseOrderLine` model gains four new fields: `revisionNumber` (the PO revision in which this line became active, default 1), `lineStatus` (ACTIVE or SUPERSEDED, default ACTIVE), `supersededByLineId` (pointer to the replacement line), and `rootLineId` (pointer to the original v1 line in this lineage chain, null for v1 lines). The `PurchaseOrder` model gains `currentRevisionNumber` (default 1, incremented on each change order application). The original lines and their downstream references (matched bills, posted receipts) remain intact as historical records. The `rootLineId` connects the full lineage from original to current revision, enabling receipt and billing progress to be computed across the entire history of a line rather than only against the current active version.

The application algorithm executes atomically when a change order moves from APPROVED to APPLIED. For MODIFY actions: the original ACTIVE line is set to SUPERSEDED, a new ACTIVE line is created with updated quantity and unitPrice inheriting the same `purchaseOrderId`, `itemId`, `unitId`, `receivingSiteId`, `requiresPhysicalReceipt`, and item snapshots, and the `rootLineId` is set to the original line's root (or the original line itself if it was a v1 line). For REMOVE actions: the line is set to SUPERSEDED with no replacement (no `supersededByLineId`). For ADD actions: a new ACTIVE line is created with `rootLineId` set to null (it is its own root). After processing all lines, the PO's `currentRevisionNumber` is incremented and receipt/billing statuses are recalculated.

The change order enforces constraints to protect data integrity. MODIFY does not allow item or unit-of-measure changes (item changes require REMOVE + ADD). Quantity cannot be reduced below the lineage's received or billed quantity. REMOVE is blocked if the line has any posted receipt or matched bill quantity. Only one open change order (DRAFT, SUBMITTED, or APPROVED) is allowed per PO at a time. Change orders can only target ORDERED POs. A PO with an open change order cannot be closed or cancelled. The change order's `fromRevisionNumber` must match the PO's `currentRevisionNumber` to prevent stale applications.

Status recalculation uses lineage carry-forward rather than filtering to ACTIVE lines only. For a given ACTIVE line, the effective received quantity is the sum of all posted `GoodsReceiptLine.quantity` where `purchaseOrderLineId` matches any line in the same lineage (any line sharing the same `rootLineId`, or the root line itself). The effective billed quantity follows the same pattern with MATCHED `PurchaseBillLine` records. The existing `deriveReceiptStatus` and `deriveBillingStatus` functions continue to work with the same `LineProgress[]` interface, but each entry now represents an ACTIVE line with quantities computed from the full lineage. For example, if v1 was 100 units with 100 received and v2 changes to 120, the receipt status becomes PARTIALLY_RECEIVED (100/120). Existing bills and receipts are never invalidated — the gap between old and new target is filled by new bills and receipts referencing the new active line.

Existing commands are affected as follows. `matchPurchaseBill` compares against the ACTIVE line's quantity and unitPrice, with 3-way match using lineage-aware received quantity. `postGoodsReceipt` validates over-receipt against the ACTIVE line's target using lineage-aware received quantity. `closePurchaseOrder` uses lineage-aware quantities and blocks if an open change order exists. `cancelPurchaseOrder` blocks if an open change order exists or if any line (including SUPERSEDED) has posted receipts or matched bills.

This feature does not retroactively re-evaluate matched bills or posted receipts. Its responsibility is to revise the forward-looking target quantities and prices, preserve the audit trail of what changed and why, and ensure downstream status derivation remains correct across revisions.

## Business Purpose

- Allow procurement to amend approved purchase orders when supplier terms change, quantities need adjustment, or lines must be added or removed
- Require explicit approval before changes to an approved order take effect, preventing unauthorized modifications to supplier commitments
- Preserve a complete audit trail of what was changed, when, by whom, and why
- Maintain the integrity of existing matched bills and posted receipts by never mutating historical line references
- Enable correct billing and receipt status calculation across line revisions through lineage tracking
- Support the standard commercial workflow where a single PO number is maintained throughout its life, even as terms evolve
- Prevent invalid amendments such as reducing quantity below what has already been received or billed

## Process Flow

```mermaid
flowchart TD
    A[Identify need to amend ORDERED PO] --> B[Create PurchaseChangeOrder]
    B --> C["changeOrder status: DRAFT"]
    C --> D[Add change order lines — ADD, MODIFY, or REMOVE]
    D --> E{Submit?}
    E -->|No| D
    E -->|Yes| F["changeOrder status: SUBMITTED"]
    F --> G{Approve?}
    G -->|Reject| H["changeOrder status: REJECTED → back to DRAFT"]
    H --> C
    G -->|Approve| I["changeOrder status: APPROVED"]
    I --> J[Apply change order]
    J --> K[Supersede affected PO lines]
    K --> L[Create new ACTIVE PO lines with updated terms]
    L --> M[Increment PO currentRevisionNumber]
    M --> N[Recalculate receiptStatus and billingStatus via lineage]
    N --> O["changeOrder status: APPLIED"]
    C --> P["changeOrder status: CANCELLED"]
```

## Scenario Patterns

- **Price renegotiation**: The supplier requests a price increase after the PO was ordered. Procurement creates a change order modifying the unit price on the affected line. After approval, the new price applies to future bills while existing matched bills remain at the original price
- **Quantity increase**: Production needs more raw material than originally ordered. A change order increases the ordered quantity. Existing receipts and bills count toward the new higher target via lineage carry-forward
- **Quantity decrease after partial receipt**: 100 units were ordered, 80 received, and the remaining 20 are no longer needed. A change order modifies the quantity to 80. Receipt status becomes `RECEIVED` (80/80) because the lineage-aware received quantity meets the new target
- **Line removal before fulfillment**: A line item is cancelled before any receipts or bills. A change order removes the line, superseding it with no replacement
- **Line addition**: An additional item is negotiated into the existing order. A change order adds a new line with its own target quantity, price, and a fresh lineage root
- **Item substitution**: The supplier cannot provide the original item and offers a substitute. Procurement creates a change order with `REMOVE` on the original line and `ADD` for the substitute item, because item changes within a single lineage are not permitted
- **Rejected change order**: An approver rejects a change order because the price increase is too high. The change order returns to draft for revision, and the PO continues operating on its current terms
- **Multiple sequential revisions**: PO starts at revision 1, a change order brings it to revision 2, and a later change order brings it to revision 3. Each revision preserves its line history and all downstream references remain valid
- **Blocked removal due to existing receipt**: A buyer tries to remove a line that has already been partially received. The change order validation rejects the REMOVE action and instructs the buyer to use MODIFY to reduce quantity to the received amount instead

## Test Cases

- Creating a change order against an `ORDERED` PO should succeed
- Creating a change order against a `DRAFT`, `SUBMITTED`, `CANCELLED`, or `CLOSED` PO should fail
- Creating a second change order while one is in `DRAFT`, `SUBMITTED`, or `APPROVED` should fail
- Submitting a change order with no change order lines should fail
- A `DRAFT` change order can be cancelled
- A `REJECTED` change order can be returned to `DRAFT` for revision
- Applying a change order should increment the PO's `currentRevisionNumber`
- Applying a change order whose `fromRevisionNumber` differs from PO's `currentRevisionNumber` should fail
- Modifying a line to reduce quantity below received quantity should fail
- Modifying a line to reduce quantity below billed quantity (MATCHED bills) should fail
- Modifying a line should not allow changing the item
- Modifying a line should not allow changing the unit of measure
- Modifying a line should create a new ACTIVE line and supersede the original
- Removing a line with posted receipt quantity should fail
- Removing a line with matched bill quantity should fail
- Removing a line with no receipts or bills should supersede it with no replacement
- Adding a line with an inactive item should fail
- Adding a line with quantity ≤ 0 should fail
- Adding a line should create a new ACTIVE line with its own rootLineId
- After a MODIFY from 100 to 120 with 100 received, receipt status should be `PARTIALLY_RECEIVED`
- After a MODIFY from 100 to 80 with 80 received, receipt status should be `RECEIVED`
- After a MODIFY from 100 to 120 with 100 billed, billing status should be `PARTIALLY_BILLED`
- Receipts referencing superseded lines should still count toward the lineage total
- Bills referencing superseded lines should still count toward the lineage total
- New receipts and bills should reference the current ACTIVE line, not the superseded line
- A PO with mixed ACTIVE and SUPERSEDED lines should only count ACTIVE lines for target quantities
- `matchPurchaseBill` should validate against the ACTIVE line's quantity and price after a change order
- `matchPurchaseBill` 3-way match should use lineage-aware received quantity
- `postGoodsReceipt` should validate over-receipt against the ACTIVE line's quantity using lineage-aware received quantity
- `closePurchaseOrder` should use lineage-aware quantities for close validation
- `closePurchaseOrder` should fail if an open change order exists
- `cancelPurchaseOrder` should fail if an open change order exists
- `cancelPurchaseOrder` should fail if any line (including SUPERSEDED) has posted receipts or matched bills

## Reference Links

- [Oracle Fusion Cloud Change Orders](https://docs.oracle.com/en/cloud/saas/procurement/24d/fappo/change-orders.html)
- [Oracle Change Order Life Cycle](https://docs.oracle.com/en/cloud/saas/procurement/24d/fappo/how-change-orders-are-processed.html)
- [SAP Purchase Order Version Management](https://help.sap.com/docs/SAP_S4HANA_CLOUD)
- [Dynamics 365 Review and accept changes to confirmed purchase orders](https://learn.microsoft.com/en-us/dynamics365/supply-chain/procurement/purchase-order-changes-after-confirmation)
