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.
| 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.
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
detailsarray in aPUTis 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 |