Purchase Order Workflow Guide
Overview
The Purchase Order (PO) system manages the complete procurement lifecycle from requisition to receipt. It supports multi-item orders, partial receiving, approval workflows, supplier communication, and automatic inventory updates. Built with CQRS patterns and domain events for integration with inventory and financial systems.
Table of Contents
- Purchase Order Lifecycle
- Creating Purchase Orders
- Approval Workflow
- Sending to Suppliers
- Receiving Goods
- Partial Receiving
- Cancelling Orders
- Purchase Order Reports
- Auto-Generated Reorders
- API Endpoints
Purchase Order Lifecycle
stateDiagram-v2
[*] --> Draft
Draft --> PendingApproval
Draft --> Approved: Direct approval
Draft --> Cancelled
PendingApproval --> Approved
PendingApproval --> Rejected
PendingApproval --> Cancelled
Approved --> Sent
Approved --> Cancelled
Sent --> Acknowledged
Sent --> Cancelled
Acknowledged --> PartiallyReceived
Acknowledged --> Received: Full receipt
Acknowledged --> Cancelled
PartiallyReceived --> Received
PartiallyReceived --> Cancelled
Received --> Invoiced
Received --> Completed
Invoiced --> Completed
Completed --> [*]
Cancelled --> [*]
Rejected --> [*]Status Descriptions
Draft
Initial status for new purchase orders.
- Order being created
- Can be edited freely
- Not yet submitted for approval
- No supplier notification
Actions Available: Edit, Submit for Approval, Delete, Approve (if authorized)
Pending Approval
Awaiting management approval before sending to supplier.
- Submitted for review
- Cannot be edited during approval
- Assigned to approver
- Requires authorized approval
Actions Available: Approve, Reject, Cancel
Approved
Approved and ready to send to supplier.
- Management approval obtained
- Ready for transmission
- Can be sent to supplier
- Still editable (before sending)
Actions Available: Send to Supplier, Cancel, Edit (limited)
Sent
Transmitted to supplier, awaiting acknowledgment.
- Email/EDI sent to supplier
- Supplier notification timestamp recorded
- Awaiting supplier confirmation
- Cannot be edited
Actions Available: Acknowledge (supplier), Cancel
Acknowledged
Supplier confirmed receipt and acceptance.
- Supplier accepted the order
- Expected delivery date confirmed
- Ready for receiving workflow
- Can receive items
Actions Available: Receive Items, Cancel
Partially Received
Some items received, awaiting remaining goods.
- At least one receipt recorded
- Not all items received yet
- Can receive additional items
- Inventory updated for received items
Actions Available: Receive More Items, Complete (close PO), Cancel remaining
Received
All items received, order fulfillment complete.
- All ordered items received
- Inventory fully updated
- Ready for invoicing
- Can be closed
Actions Available: Record Invoice, Complete
Invoiced
Supplier invoice recorded, ready to close.
- Invoice number and amount recorded
- Linked to accounts payable
- Ready for final closure
- Awaiting payment completion
Actions Available: Complete
Completed
Order closed and archived.
- All items received
- Invoice recorded
- Final status reached
- Cannot be reopened
No Actions Available (terminal state)
Cancelled
Order cancelled before completion.
- Cancelled by user or system
- Reason recorded
- Inventory reservations released
- Supplier notified (if sent)
No Actions Available (terminal state)
Rejected
Approval denied, returned to draft or cancelled.
- Management rejected approval
- Reason recorded
- Can be revised and resubmitted
- Or permanently cancelled
Actions Available: None (must create new PO)
Creating Purchase Orders
Manual Creation
Create a purchase order with line items:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"supplier_id": 2,
"warehouse_id": 1,
"expected_delivery_date": "2025-02-15",
"payment_terms": "Net 30",
"notes": "Rush order for restocking",
"items": [
{
"product_id": 42,
"quantity": 50,
"unit_price": 750.00
},
{
"product_id": 43,
"quantity": 100,
"unit_price": 25.00
}
]
}'Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
supplier_id | integer | ✅ Yes | Supplier to order from |
warehouse_id | integer | ✅ Yes | Destination warehouse |
expected_delivery_date | date | No | Expected arrival date |
payment_terms | string | No | Payment terms (Net 30, COD, etc.) |
shipping_method | string | No | Shipping method |
notes | string | No | Internal notes |
items | array | ✅ Yes | Line items (product, quantity, price) |
Response:
{
"message": "Purchase order created successfully",
"data": {
"id": 123,
"po_number": "PO-20250115-123",
"status": "draft",
"supplier": {
"id": 2,
"name": "Tech Supplies Inc"
},
"warehouse": {
"id": 1,
"name": "Main Distribution Center"
},
"expected_delivery_date": "2025-02-15",
"items": [
{
"id": 456,
"product_id": 42,
"product_name": "Laptop - Dell XPS 13",
"quantity_ordered": 50,
"unit_price": 750.00,
"subtotal": 37500.00
},
{
"id": 457,
"product_id": 43,
"product_name": "Mouse - Wireless",
"quantity_ordered": 100,
"unit_price": 25.00,
"subtotal": 2500.00
}
],
"subtotal": 40000.00,
"tax_amount": 3200.00,
"shipping_cost": 150.00,
"total_amount": 43350.00,
"created_at": "2025-01-15T10:00:00Z"
}
}Purchase Order Structure
[
'id' => 123,
'po_number' => 'PO-20250115-123', // Auto-generated
'status' => 'draft',
'supplier_id' => 2,
'warehouse_id' => 1,
// Dates
'order_date' => '2025-01-15',
'expected_delivery_date' => '2025-02-15',
'delivery_date' => null, // Actual delivery
// Financial
'subtotal' => 40000.00,
'tax_amount' => 3200.00,
'shipping_cost' => 150.00,
'discount_amount' => 0.00,
'total_amount' => 43350.00,
'currency' => 'USD',
// Supplier Details
'payment_terms' => 'Net 30',
'shipping_method' => 'Ground',
'tracking_number' => null,
// Notes
'notes' => 'Rush order for restocking',
'cancellation_reason' => null,
// Workflow
'approved_by' => null,
'approved_at' => null,
'sent_at' => null,
'acknowledged_at' => null,
'received_at' => null,
// Line Items
'items' => [...],
]Approval Workflow
Submit for Approval
Submit draft PO for management review:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/submit-for-approval \
-H "Authorization: Bearer {token}" \
-d '{
"approver_id": 5,
"notes": "Urgent restocking needed"
}'Effect:
- Status: Draft → Pending Approval
- Approver assigned
- Notification sent to approver
- PO locked from editing
Approve Purchase Order
Approve a pending purchase order:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/approve \
-H "Authorization: Bearer {token}" \
-d '{
"notes": "Approved - pricing verified"
}'Effect:
- Status: Pending Approval → Approved
- Approval timestamp recorded
- Approver ID recorded
- Ready to send to supplier
PurchaseOrderApprovedevent fired
Response:
{
"message": "Purchase order approved successfully",
"data": {
"id": 123,
"po_number": "PO-20250115-123",
"status": "approved",
"approved_by": {
"id": 5,
"name": "Manager Name"
},
"approved_at": "2025-01-15T11:00:00Z"
}
}Reject Purchase Order
Reject a pending approval:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/reject \
-H "Authorization: Bearer {token}" \
-d '{
"reason": "Pricing too high - negotiate with supplier"
}'Effect:
- Status: Pending Approval → Rejected
- Rejection reason recorded
- Creator notified
- Cannot proceed with order
Sending to Suppliers
Send to Supplier
Transmit approved PO to supplier:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/send \
-H "Authorization: Bearer {token}" \
-d '{
"send_email": true,
"email_template": "standard_po",
"cc_emails": ["purchasing@company.com"]
}'Effect:
- Status: Approved → Sent
- Email sent to supplier with PO PDF
- Sent timestamp recorded
- PO locked from editing
PurchaseOrderSentevent fired
Email Contents:
- PO number and details
- Line items with quantities and prices
- Expected delivery date
- Payment terms
- Shipping instructions
- Contact information
Acknowledge Receipt (Supplier)
Supplier confirms order acceptance:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/acknowledge \
-H "Authorization: Bearer {token}" \
-d '{
"confirmed_delivery_date": "2025-02-20",
"supplier_reference": "SUP-ORD-456",
"notes": "Order confirmed - delivery scheduled"
}'Effect:
- Status: Sent → Acknowledged
- Supplier reference recorded
- Confirmed delivery date updated
- Ready for receiving workflow
Receiving Goods
Full Receipt
Receive all items in a single delivery:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/receive \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"received_date": "2025-02-20",
"received_by": "Warehouse Staff",
"items": [
{
"purchase_order_item_id": 456,
"quantity_received": 50,
"warehouse_id": 1,
"bin_location": "A-12-3"
},
{
"purchase_order_item_id": 457,
"quantity_received": 100,
"warehouse_id": 1,
"bin_location": "A-12-4"
}
],
"notes": "All items received in good condition"
}'Process:
- Validate received quantities ≤ ordered quantities
- Update PO item received quantities
- Increase inventory stock in destination warehouse
- Create stock movement records
- Update PO status to Received
- Fire
PurchaseOrderReceivedevent
Inventory Updates:
Product: Laptop - Dell XPS 13
Warehouse: Main DC
Before:
- Stock: 50 units
Receive: +50 units
After:
- Stock: 100 units
- Stock Movement: "Purchase Order #PO-123 received"Response:
{
"message": "Purchase order received successfully",
"data": {
"id": 123,
"po_number": "PO-20250115-123",
"status": "received",
"items": [
{
"product_id": 42,
"product_name": "Laptop - Dell XPS 13",
"quantity_ordered": 50,
"quantity_received": 50,
"quantity_remaining": 0,
"received_percentage": 100.0
},
{
"product_id": 43,
"product_name": "Mouse - Wireless",
"quantity_ordered": 100,
"quantity_received": 100,
"quantity_remaining": 0,
"received_percentage": 100.0
}
],
"total_received_percentage": 100.0,
"inventory_updated": true
}
}Partial Receiving
Receive Partial Shipment
Receive only some items or quantities:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/receive \
-H "Authorization: Bearer {token}" \
-d '{
"received_date": "2025-02-15",
"items": [
{
"purchase_order_item_id": 456,
"quantity_received": 30
}
],
"notes": "First shipment - 30 laptops. Remaining 20 expected Feb 25"
}'Effect:
- Status: Acknowledged → Partially Received
- Item 1: 30/50 received (60%)
- Item 2: 0/100 received (0%)
- Overall: 30/150 items (20%)
- Inventory updated for received items only
- Awaiting additional receipts
Response:
{
"message": "Partial receipt recorded successfully",
"data": {
"id": 123,
"po_number": "PO-20250115-123",
"status": "partially_received",
"items": [
{
"product_id": 42,
"quantity_ordered": 50,
"quantity_received": 30,
"quantity_remaining": 20,
"received_percentage": 60.0
},
{
"product_id": 43,
"quantity_ordered": 100,
"quantity_received": 0,
"quantity_remaining": 100,
"received_percentage": 0.0
}
],
"total_received_percentage": 20.0,
"awaiting_items": 120
}
}Second Partial Receipt
Receive remaining items:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/receive \
-H "Authorization: Bearer {token}" \
-d '{
"received_date": "2025-02-25",
"items": [
{
"purchase_order_item_id": 456,
"quantity_received": 20
},
{
"purchase_order_item_id": 457,
"quantity_received": 100
}
]
}'Effect:
- Item 1: 50/50 received (100%)
- Item 2: 100/100 received (100%)
- Overall: 150/150 items (100%)
- Status: Partially Received → Received
- Order fulfillment complete
Cancelling Orders
Cancel Purchase Order
Cancel an active purchase order:
curl -X POST https://api.crm.test/api/v1/operations/purchase-orders/123/cancel \
-H "Authorization: Bearer {token}" \
-d '{
"reason": "Supplier cannot fulfill - lead time too long",
"notify_supplier": true
}'Can Cancel When:
- Draft
- Pending Approval
- Approved (before sending)
- Sent (with supplier notification)
- Acknowledged (with supplier notification)
- Partially Received (cancels remaining items)
Cannot Cancel When:
- Received (fully received)
- Invoiced
- Completed
Effect:
- Status → Cancelled
- Cancellation reason recorded
- Cancelled timestamp and user recorded
- Inventory reservations released
- Supplier notified (if sent)
PurchaseOrderCancelledevent fired
Response:
{
"message": "Purchase order cancelled successfully",
"data": {
"id": 123,
"po_number": "PO-20250115-123",
"status": "cancelled",
"cancelled_by": {
"id": 3,
"name": "Purchasing Manager"
},
"cancelled_at": "2025-01-16T09:00:00Z",
"cancellation_reason": "Supplier cannot fulfill - lead time too long",
"supplier_notified": true
}
}Purchase Order Reports
Overdue Orders
Get purchase orders past expected delivery:
curl -X GET "https://api.crm.test/api/v1/operations/purchase-orders/overdue" \
-H "Authorization: Bearer {token}"Response:
{
"data": [
{
"id": 123,
"po_number": "PO-20250115-123",
"supplier": "Tech Supplies Inc",
"expected_delivery_date": "2025-01-20",
"days_overdue": 5,
"status": "sent",
"total_amount": 43350.00,
"urgency": "high"
}
],
"meta": {
"total_overdue_orders": 8,
"total_overdue_value": 285000.00
}
}Orders Requiring Attention
Get POs needing action:
curl -X GET "https://api.crm.test/api/v1/operations/purchase-orders/requires-attention" \
-H "Authorization: Bearer {token}"Requires Attention When:
- Pending Approval (> 24 hours)
- Overdue delivery
- Partially received (> 7 days)
- Acknowledged but not received (> expected date)
Supplier Performance
Analyze supplier delivery and quality metrics:
curl -X GET "https://api.crm.test/api/v1/operations/purchase-orders/performance-metrics?supplier_id=2" \
-H "Authorization: Bearer {token}"Response:
{
"data": {
"supplier_id": 2,
"supplier_name": "Tech Supplies Inc",
"metrics": {
"total_orders": 45,
"on_time_delivery_rate": 91.1,
"average_delay_days": 2.3,
"cancellation_rate": 4.4,
"average_order_value": 38500.00,
"total_value_ytd": 1732500.00,
"quality_issues": 2,
"quality_score": 95.6
},
"recent_performance": [
{
"month": "2025-01",
"orders": 8,
"on_time": 7,
"on_time_rate": 87.5
}
]
}
}Purchase Order Statistics
Dashboard statistics:
curl -X GET "https://api.crm.test/api/v1/operations/purchase-orders/statistics" \
-H "Authorization: Bearer {token}"Response:
{
"data": {
"total_active_orders": 23,
"pending_approval_count": 5,
"overdue_count": 8,
"total_pending_value": 567000.00,
"avg_order_value": 42500.00,
"by_status": {
"draft": 3,
"pending_approval": 5,
"approved": 2,
"sent": 8,
"partially_received": 5
},
"top_suppliers": [
{
"supplier_id": 2,
"supplier_name": "Tech Supplies Inc",
"order_count": 45,
"total_value": 1732500.00
}
]
}
}Auto-Generated Reorders
Generate from Low Stock
Automatically create purchase orders for low stock items:
curl -X POST "https://api.crm.test/api/v1/operations/purchase-orders/generate-reorders" \
-H "Authorization: Bearer {token}" \
-d '{
"warehouse_id": 1,
"auto_approve": false,
"group_by_supplier": true
}'Process:
- Scan inventory for products at/below reorder point
- Calculate reorder quantity per product
- Group products by preferred supplier
- Create draft PO per supplier
- Return created POs for review
Response:
{
"message": "Generated 3 purchase order(s) successfully",
"data": {
"generated_count": 3,
"total_items": 12,
"total_estimated_value": 85000.00,
"purchase_orders": [
{
"id": 124,
"po_number": "PO-20250115-124",
"supplier_id": 2,
"supplier_name": "Tech Supplies Inc",
"item_count": 5,
"total_amount": 45000.00,
"status": "draft"
},
{
"id": 125,
"po_number": "PO-20250115-125",
"supplier_id": 3,
"supplier_name": "Office Supplies Co",
"item_count": 4,
"total_amount": 25000.00,
"status": "draft"
},
{
"id": 126,
"po_number": "PO-20250115-126",
"supplier_id": 4,
"supplier_name": "Hardware Direct",
"item_count": 3,
"total_amount": 15000.00,
"status": "draft"
}
]
}
}API Endpoints
Purchase Order CRUD
| Method | Endpoint | Description |
|---|---|---|
| GET | /purchase-orders | List purchase orders |
| POST | /purchase-orders | Create new PO |
| GET | /purchase-orders/{id} | Get PO details |
| PUT | /purchase-orders/{id} | Update PO |
| DELETE | /purchase-orders/{id} | Delete PO (draft only) |
Workflow Actions
| Method | Endpoint | Description |
|---|---|---|
| POST | /purchase-orders/{id}/approve | Approve PO |
| POST | /purchase-orders/{id}/reject | Reject PO |
| POST | /purchase-orders/{id}/send | Send to supplier |
| POST | /purchase-orders/{id}/acknowledge | Supplier acknowledgment |
| POST | /purchase-orders/{id}/receive | Receive goods |
| POST | /purchase-orders/{id}/cancel | Cancel PO |
Reports
| Method | Endpoint | Description |
|---|---|---|
| GET | /purchase-orders/overdue | Overdue orders |
| GET | /purchase-orders/requires-attention | Action needed |
| GET | /purchase-orders/performance-metrics | Supplier performance |
| GET | /purchase-orders/statistics | Dashboard stats |
| POST | /purchase-orders/generate-reorders | Auto-generate from low stock |
| POST | /purchase-orders/export | Export to CSV/Excel |
See: API Reference for complete documentation.
Related Guides
- Multi-Warehouse Inventory - Inventory updates from receipts
- Product Management - Product catalog
- Stock Movement Tracking - Receipt audit trail
- Domain Events - PO workflow events
Next Steps
- Create First PO - Follow Purchase Order Tutorial
- Setup Auto-Reorders - Configure reorder points in inventory
- Review Reports - Monitor supplier performance
- Integrate Finance - Link with accounts payable system