Skip to content

Purchase Order Lifecycle

This guide traces a purchase order through its full lifecycle - from creation to delivery confirmation - showing which API calls to make at each stage and how to interpret status transitions.


Status Overview

A purchase order moves through the following statuses. Both the header (PO List Document Status) and each line item (PO Details Document Status) carry their own status, which can differ.

Suggested ──► Open ──► Partially Received ──► Received
                 └──► Cancelled
Status Meaning
Suggested System-generated order recommendation - not yet confirmed
Open Confirmed and awaiting shipment
Partially Received Some line items received, others still in transit
Received All quantities confirmed delivered
Cancelled Order cancelled - no further changes

Stage 1 - Create the PO

Create the PO with status Open and one or more line items. All line items must reference the same warehouse.

CSRF=$(grep csrf_access_token cookies.txt | awk '{print $NF}')

curl -b cookies.txt -X POST \
  "https://acme.knosc.com/api/purchase-order" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "PO List Number": "PO-2024-500",
    "PO List Document Status": "Open",
    "PO List Order Date": "2024-06-01",
    "Supplier Number": "SUP-007",
    "Purchase Order Id": null,
    "details": [
      {
        "id": null,
        "PO Details Id": null,
        "Item Number": "ITEM-A",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 500,
        "PO Details Price": 12.50,
        "PO Details Document Status": "Open",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-08",
        "PO Details ETA Date": "2024-06-15"
      },
      {
        "id": null,
        "PO Details Id": null,
        "Item Number": "ITEM-B",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 200,
        "PO Details Price": 8.00,
        "PO Details Document Status": "Open",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-08",
        "PO Details ETA Date": "2024-06-15"
      }
    ]
  }'

After creation, fetch the PO to get the system-assigned line item IDs - you'll need them for subsequent updates.

curl -b cookies.txt "https://acme.knosc.com/api/purchase-order?filter[PO+List+Number]=PO-2024-500"

Stage 2 - Supplier Confirms Ship Date

When the supplier confirms a revised ship date, update the affected line items using PUT. Include all existing line items in the details array - any item omitted will be deleted.

curl -b cookies.txt -X PUT \
  "https://acme.knosc.com/api/purchase-order/310" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "PO List Number": "PO-2024-500",
    "PO List Document Status": "Open",
    "PO List Order Date": "2024-06-01",
    "Supplier Number": "SUP-007",
    "Purchase Order Id": 310,
    "details": [
      {
        "id": 1001,
        "PO Details Id": 1001,
        "Item Number": "ITEM-A",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 500,
        "PO Details Price": 12.50,
        "PO Details Document Status": "Open",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-17"
      },
      {
        "id": 1002,
        "PO Details Id": 1002,
        "Item Number": "ITEM-B",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 200,
        "PO Details Price": 8.00,
        "PO Details Document Status": "Open",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-17"
      }
    ]
  }'

Important: The details array in a PUT is a full replacement. Always read the current PO first and include all existing line items, modifying only the fields you want to change.


Stage 3 - Item Ships (Add Tracking Number)

When goods leave the supplier, record the Bill of Lading / tracking reference on each line item:

curl -b cookies.txt -X PUT \
  "https://acme.knosc.com/api/purchase-order/310" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "PO List Number": "PO-2024-500",
    "PO List Document Status": "Open",
    "PO List Order Date": "2024-06-01",
    "Supplier Number": "SUP-007",
    "Purchase Order Id": 310,
    "details": [
      {
        "id": 1001,
        "PO Details Id": 1001,
        "Item Number": "ITEM-A",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 500,
        "PO Details Document Status": "Open",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-17",
        "PO Details Actual Ship Date": "2024-06-10",
        "PO Details Tracking": "BOL-20240610-001"
      },
      {
        "id": 1002,
        "PO Details Id": 1002,
        "Item Number": "ITEM-B",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 200,
        "PO Details Document Status": "Open",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-17",
        "PO Details Actual Ship Date": "2024-06-10",
        "PO Details Tracking": "BOL-20240610-001"
      }
    ]
  }'

Stage 4 - Partial Receipt

If only ITEM-A arrives first, mark that line as Received and set PO Details Actual Delivery Date. Update the PO header status to Partially Received.

curl -b cookies.txt -X PUT \
  "https://acme.knosc.com/api/purchase-order/310" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "PO List Number": "PO-2024-500",
    "PO List Document Status": "Partially Received",
    "PO List Order Date": "2024-06-01",
    "Supplier Number": "SUP-007",
    "Purchase Order Id": 310,
    "details": [
      {
        "id": 1001,
        "PO Details Id": 1001,
        "Item Number": "ITEM-A",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 500,
        "PO Details Document Status": "Received",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-17",
        "PO Details Actual Ship Date": "2024-06-10",
        "PO Details Actual Delivery Date": "2024-06-17",
        "PO Details Tracking": "BOL-20240610-001"
      },
      {
        "id": 1002,
        "PO Details Id": 1002,
        "Item Number": "ITEM-B",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 200,
        "PO Details Document Status": "Open",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-17",
        "PO Details Actual Ship Date": "2024-06-10",
        "PO Details Tracking": "BOL-20240610-001"
      }
    ]
  }'

Stage 5 - Full Receipt

When the remaining line arrives, mark it Received and set the header to Received:

curl -b cookies.txt -X PUT \
  "https://acme.knosc.com/api/purchase-order/310" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "PO List Number": "PO-2024-500",
    "PO List Document Status": "Received",
    "PO List Order Date": "2024-06-01",
    "Supplier Number": "SUP-007",
    "Purchase Order Id": 310,
    "details": [
      {
        "id": 1001,
        "PO Details Id": 1001,
        "Item Number": "ITEM-A",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 500,
        "PO Details Document Status": "Received",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-17",
        "PO Details Actual Ship Date": "2024-06-10",
        "PO Details Actual Delivery Date": "2024-06-17",
        "PO Details Tracking": "BOL-20240610-001"
      },
      {
        "id": 1002,
        "PO Details Id": 1002,
        "Item Number": "ITEM-B",
        "Warehouse Number": "WH-01",
        "Ship From Number": "SF-01",
        "PO Details Quantity": 200,
        "PO Details Document Status": "Received",
        "PO Details Order Date": "2024-06-01",
        "PO Details Ship Date": "2024-06-10",
        "PO Details ETA Date": "2024-06-22",
        "PO Details Actual Ship Date": "2024-06-10",
        "PO Details Actual Delivery Date": "2024-06-22",
        "PO Details Tracking": "BOL-20240610-002"
      }
    ]
  }'

Cancelling a PO

To cancel an open PO, set both the header and all line items to Cancelled:

curl -b cookies.txt -X PUT \
  "https://acme.knosc.com/api/purchase-order/310" \
  -H "Content-Type: application/json" \
  -H "X-XSRF-TOKEN: $CSRF" \
  -d '{
    "PO List Number": "PO-2024-500",
    "PO List Document Status": "Cancelled",
    "Supplier Number": "SUP-007",
    "Purchase Order Id": 310,
    "details": [
      { "id": 1001, "PO Details Id": 1001, "Item Number": "ITEM-A", "Warehouse Number": "WH-01", "Ship From Number": "SF-01", "PO Details Quantity": 500, "PO Details Document Status": "Cancelled" },
      { "id": 1002, "PO Details Id": 1002, "Item Number": "ITEM-B", "Warehouse Number": "WH-01", "Ship From Number": "SF-01", "PO Details Quantity": 200, "PO Details Document Status": "Cancelled" }
    ]
  }'

Python - Lifecycle Helper

def get_po_by_number(client, po_number: str) -> dict:
    """Fetch a PO by document number. Returns the first match."""
    result = client.get(f"/purchase-order?filter[PO List Number]={po_number}")
    rows = result["data"]["rows"]
    if not rows:
        raise ValueError(f"PO not found: {po_number}")
    # Fetch full detail
    po_id = rows[0]["id"]
    return client.get(f"/purchase-order/{po_id}")["data"]


def update_po_status(client, po_id: int, current_po: dict, new_header_status: str, line_statuses: dict[int, str]):
    """
    Update PO header and selected line item statuses.
    current_po: the full PO object from GET /api/purchase-order/{id}
    line_statuses: {detail_id: new_status} - only listed lines are changed; others keep current status
    """
    details = []
    for line in current_po["details"]:
        detail = dict(line)
        if line["id"] in line_statuses:
            detail["PO Details Document Status"] = line_statuses[line["id"]]
        details.append(detail)

    payload = {**current_po, "PO List Document Status": new_header_status, "details": details}
    return client.put(f"/purchase-order/{po_id}", payload)


# Usage:
po = get_po_by_number(client, "PO-2024-500")
po_id = po["id"]

# Mark ITEM-A line (id=1001) as received
update_po_status(client, po_id, po, "Partially Received", {1001: "Received"})

Key Rules Summary

Rule Detail
details is always a full replacement on PUT Omitting a line item deletes it
All line items must share the same Warehouse Number Split orders across warehouses into separate POs
Order Date ≤ Ship Date ≤ ETA Date Date ordering is validated server-side
Received header status Set only when all line items are Received
Queued writes return notification_type: Warning Do not retry - the write is already queued