Skip to content

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

mermaid
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:

bash
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:

ParameterTypeRequiredDescription
supplier_idinteger✅ YesSupplier to order from
warehouse_idinteger✅ YesDestination warehouse
expected_delivery_datedateNoExpected arrival date
payment_termsstringNoPayment terms (Net 30, COD, etc.)
shipping_methodstringNoShipping method
notesstringNoInternal notes
itemsarray✅ YesLine items (product, quantity, price)

Response:

json
{
  "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

php
[
    '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:

bash
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:

bash
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
  • PurchaseOrderApproved event fired

Response:

json
{
  "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:

bash
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:

bash
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
  • PurchaseOrderSent event 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:

bash
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:

bash
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:

  1. Validate received quantities ≤ ordered quantities
  2. Update PO item received quantities
  3. Increase inventory stock in destination warehouse
  4. Create stock movement records
  5. Update PO status to Received
  6. Fire PurchaseOrderReceived event

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:

json
{
  "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:

bash
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:

json
{
  "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:

bash
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:

bash
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)
  • PurchaseOrderCancelled event fired

Response:

json
{
  "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:

bash
curl -X GET "https://api.crm.test/api/v1/operations/purchase-orders/overdue" \
  -H "Authorization: Bearer {token}"

Response:

json
{
  "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:

bash
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:

bash
curl -X GET "https://api.crm.test/api/v1/operations/purchase-orders/performance-metrics?supplier_id=2" \
  -H "Authorization: Bearer {token}"

Response:

json
{
  "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:

bash
curl -X GET "https://api.crm.test/api/v1/operations/purchase-orders/statistics" \
  -H "Authorization: Bearer {token}"

Response:

json
{
  "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:

bash
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:

  1. Scan inventory for products at/below reorder point
  2. Calculate reorder quantity per product
  3. Group products by preferred supplier
  4. Create draft PO per supplier
  5. Return created POs for review

Response:

json
{
  "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

MethodEndpointDescription
GET/purchase-ordersList purchase orders
POST/purchase-ordersCreate 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

MethodEndpointDescription
POST/purchase-orders/{id}/approveApprove PO
POST/purchase-orders/{id}/rejectReject PO
POST/purchase-orders/{id}/sendSend to supplier
POST/purchase-orders/{id}/acknowledgeSupplier acknowledgment
POST/purchase-orders/{id}/receiveReceive goods
POST/purchase-orders/{id}/cancelCancel PO

Reports

MethodEndpointDescription
GET/purchase-orders/overdueOverdue orders
GET/purchase-orders/requires-attentionAction needed
GET/purchase-orders/performance-metricsSupplier performance
GET/purchase-orders/statisticsDashboard stats
POST/purchase-orders/generate-reordersAuto-generate from low stock
POST/purchase-orders/exportExport to CSV/Excel

See: API Reference for complete documentation.

Next Steps

  1. Create First PO - Follow Purchase Order Tutorial
  2. Setup Auto-Reorders - Configure reorder points in inventory
  3. Review Reports - Monitor supplier performance
  4. Integrate Finance - Link with accounts payable system

Documentation for SynthesQ CRM/ERP Platform