Receiving Goods from Purchase Orders
Overview
The receiving process is a critical step in the purchase order lifecycle where incoming goods are verified, quality-checked, and added to inventory. This guide covers the complete receiving workflow, from full receipts to partial deliveries with quality control.
Key Concepts
Receipt Types
Full Receipt
- All ordered items received in a single shipment
- PO status changes from
SENT→RECEIVED - Complete inventory update in one transaction
- Simplest and most common scenario
Partial Receipt
- Only some items received from the PO
- PO status changes from
SENT→PARTIALLY_RECEIVED - Can receive multiple partial shipments over time
- Tracks pending quantities for outstanding items
- Final partial receipt moves status to
RECEIVED
Quality Control
The receiving process includes optional quality checks:
- Pass: Items accepted and added to available inventory
- Fail: Issues documented, supplier notified, possible return/replacement
- Quality check results affect supplier performance ratings
- Failed quality checks require notes explaining the issue
Business Rules
Receiving Constraints
Status Requirements: Can only receive POs in these statuses:
SENT- Standard receiving pathACKNOWLEDGED- Supplier confirmed, awaiting deliveryPARTIALLY_RECEIVED- Additional shipment for partial PO
Quantity Validation:
- Cannot receive more than ordered quantity
- Cannot receive negative quantities
- Received quantity must be at least 1
- System prevents over-receiving to maintain order accuracy
Quality Check Rules:
- If quality check fails, quality notes are required
- Failed quality checks recorded per line item
- Overall PO quality status reflects worst line item result
- Quality failures stored in internal notes for audit trail
Inventory Updates:
- Inventory updated automatically upon receipt
- Stock movements created for audit trail
- Warehouse specified in line item receives the stock
- Reserved stock adjusted if needed
API Endpoint
Receive Purchase Order
Endpoint: POST /api/v1/operations/purchase-orders/{purchaseOrder}/receive
Authentication: Required (Bearer token)
Route Name: purchase-orders.receive
Request Structure
Full Receipt (All Items)
To receive all items at their full ordered quantities, send an empty request:
{
"notes": "Delivery received in good condition",
"quality_check_passed": true
}Parameters:
notes(optional, string, max 2000): General receiving notesquality_check_passed(optional, boolean, default: true): Overall quality statusquality_notes(optional, string, max 2000): Required if quality_check_passed = falsereceived_date(optional, date, Y-m-d): Date received (defaults to today)
Partial Receipt (Specific Items)
To receive specific items with custom quantities:
{
"received_items": [
{
"product_id": 101,
"quantity_received": 50,
"notes": "First shipment - 50 units received",
"quality_check_passed": true
},
{
"product_id": 102,
"quantity_received": 25,
"quality_check_passed": false,
"quality_notes": "5 units damaged during shipping - supplier notified"
}
],
"notes": "Partial delivery - remaining items expected next week",
"quality_check_passed": false,
"quality_notes": "Some damaged items in shipment - see line item notes"
}received_items Array Parameters:
product_id(required, integer): Product being receivedquantity_received(required, integer, 1-1000000): Quantity receivedwarehouse_id(optional, integer): Warehouse if PO has multiple warehousesnotes(optional, string, max 500): Line item specific notesquality_check_passed(optional, boolean): Quality check for this itemquality_notes(optional, string, max 1000): Required if quality check failed
Overall Parameters:
notes(optional, string, max 2000): General receipt notesquality_check_passed(optional, boolean): Overall quality statusquality_notes(optional, string, max 2000): Overall quality notesreceived_date(optional, date): Receipt date (Y-m-d format)
Response Structure
Success Response (200)
{
"message": "Purchase order marked as received successfully",
"data": {
"id": 45,
"order_number": "PO-202512-00045",
"status": "received",
"supplier": {
"id": 12,
"name": "Acme Supplies Inc",
"email": "orders@acmesupplies.com"
},
"total_amount": 2450.00,
"currency": "USD",
"order_date": "2025-12-01",
"expected_delivery_date": "2025-12-15",
"actual_delivery_date": "2025-12-17",
"internal_notes": "Receipt notes: Delivery received in good condition",
"line_items": [
{
"id": 156,
"product_id": 101,
"product_sku": "WIDGET-001",
"product_name": "Premium Widget",
"quantity_ordered": 100,
"quantity_received": 100,
"unit_price": 15.50,
"line_total": 1550.00,
"receiving_notes": "All units received in good condition",
"received_at": "2025-12-17T14:30:00Z"
},
{
"id": 157,
"product_id": 102,
"product_sku": "GADGET-002",
"product_name": "Smart Gadget",
"quantity_ordered": 50,
"quantity_received": 50,
"unit_price": 18.00,
"line_total": 900.00,
"receiving_notes": null,
"received_at": "2025-12-17T14:30:00Z"
}
]
}
}Error Response - Invalid Status (422)
{
"error": "Failed to receive purchase order",
"message": "Cannot receive purchase order in status: draft. Order must be SENT, ACKNOWLEDGED, or PARTIALLY_RECEIVED."
}Error Response - Product Not Found (422)
{
"error": "Product not found in purchase order",
"message": "Product ID 999 is not in this purchase order"
}Error Response - Over-Receiving (500)
{
"error": "Failed to receive purchase order",
"message": "Cannot receive 150 units. Only 100 units pending (ordered: 100, received: 0)"
}Workflow Examples
Example 1: Full Receipt - Simple Delivery
Scenario: All items arrive in one shipment, no issues
Request:
curl -X POST https://api.example.com/api/v1/operations/purchase-orders/45/receive \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"notes": "Delivery received on time, all items in good condition",
"quality_check_passed": true
}'What Happens:
- System validates PO status (must be SENT/ACKNOWLEDGED)
- All line items received at full ordered quantities
- Quality check recorded as passed
- Inventory updated for all products
- Stock movements created for audit trail
- PO status changes to
RECEIVED - Actual delivery date set to today
- Supplier performance metrics updated (on-time delivery)
Example 2: Partial Receipt - Split Shipment
Scenario: Supplier ships in two batches, first batch arrives
First Shipment Request:
{
"received_items": [
{
"product_id": 101,
"quantity_received": 50,
"notes": "First batch - 50 of 100 units"
}
],
"notes": "First shipment received - expecting second shipment Dec 20",
"quality_check_passed": true
}What Happens:
- Product 101: 50 units received (50 pending)
- Product 102: 0 units received (50 still pending)
- PO status changes to
PARTIALLY_RECEIVED - Inventory updated for 50 units of Product 101
- Stock movement created for partial receipt
- Remaining quantities tracked for next shipment
Second Shipment Request (3 days later):
{
"received_items": [
{
"product_id": 101,
"quantity_received": 50,
"notes": "Second batch - completing order"
},
{
"product_id": 102,
"quantity_received": 50,
"notes": "All units of gadget received"
}
],
"notes": "Final shipment - order complete",
"quality_check_passed": true
}What Happens:
- Product 101: 50 more units received (total 100, fully received)
- Product 102: 50 units received (total 50, fully received)
- All items now fully received
- PO status changes to
RECEIVED - Inventory updated for remaining quantities
- Actual delivery date set to final shipment date
Example 3: Quality Issues - Damaged Goods
Scenario: Some items damaged during shipping
Request:
{
"received_items": [
{
"product_id": 101,
"quantity_received": 95,
"quality_check_passed": false,
"quality_notes": "5 units damaged - boxes crushed. Photos taken and emailed to supplier."
},
{
"product_id": 102,
"quantity_received": 50,
"quality_check_passed": true,
"notes": "All units in good condition"
}
],
"notes": "Partial damage on Product 101 - supplier contacted for replacement",
"quality_check_passed": false,
"quality_notes": "Overall shipment has quality issues - see line item details"
}What Happens:
- Product 101: Only 95 units accepted (5 damaged)
- Quality failure recorded with detailed notes
- 5 units remain pending (awaiting replacement from supplier)
- PO status changes to
PARTIALLY_RECEIVED - Internal notes updated with quality issues
- Supplier performance rating affected
- Quality notes preserved for supplier communication
Follow-up for Replacement:
{
"received_items": [
{
"product_id": 101,
"quantity_received": 5,
"notes": "Replacement units for damaged items received"
}
],
"notes": "Replacement shipment received - order now complete"
}Example 4: Multi-Warehouse Receipt
Scenario: PO has items going to different warehouses
Request:
{
"received_items": [
{
"product_id": 101,
"warehouse_id": 1,
"quantity_received": 50,
"notes": "Received at Main Warehouse"
},
{
"product_id": 101,
"warehouse_id": 2,
"quantity_received": 50,
"notes": "Received at Regional Warehouse"
}
],
"notes": "Split shipment to two warehouse locations"
}What Happens:
- 50 units added to inventory at Warehouse 1
- 50 units added to inventory at Warehouse 2
- Separate stock movements created for each warehouse
- Each warehouse's inventory updated independently
Validation Rules
Field Validation
received_items.*.product_id
- Required when received_items specified
- Must be integer
- Must exist in the purchase order line items
received_items.*.quantity_received
- Required when received_items specified
- Must be integer between 1 and 1,000,000
- Cannot exceed pending quantity for that line item
received_items.*.notes
- Optional
- String, max 500 characters
received_items.*.quality_check_passed
- Optional boolean
- Defaults to true
received_items.*.quality_notes
- Optional string, max 1000 characters
- Required if quality_check_passed = false
notes
- Optional string, max 2000 characters
- Appended to PO internal_notes field
quality_check_passed
- Optional boolean
- Defaults to true
- Overall quality status for entire receipt
quality_notes
- Optional string, max 2000 characters
- Required if quality_check_passed = false AND no line item has quality_notes
received_date
- Optional date in Y-m-d format
- Defaults to current date
- Cannot be in the future
Business Logic Validation
PO Status Check
Valid statuses: SENT, ACKNOWLEDGED, PARTIALLY_RECEIVED
Invalid: DRAFT, PENDING, APPROVED, RECEIVED, COMPLETED, CANCELLEDQuantity Validation
For each line item:
pending_quantity = quantity_ordered - quantity_received
if quantity_received > pending_quantity:
reject with errorQuality Check Validation
if quality_check_passed == false:
if quality_notes is empty AND no line items have quality_notes:
reject with errorBackend Processing Flow
When you submit a receive request, the system performs these steps:
1. Request Validation
- Validates PO exists and user has permission
- Checks PO status allows receiving
- Validates all input fields per rules above
2. Line Item Mapping
If received_items is empty:
- System automatically receives all items at full pending quantities
- For each line item, calculates:
quantityToReceive = quantity_ordered - quantity_received - Only processes items with pending quantities > 0
If received_items is specified:
- Maps product_id (and optionally warehouse_id) to specific line items
- Validates each product exists in the PO
- Uses specified quantities for each item
3. Domain Service Validation
The system validates:
- Quantities don't exceed pending amounts
- All specified line items exist in PO
- Receipt quantities are within acceptable ranges
- Quality check requirements are met
4. Receipt Processing
For each received item:
- Updates
quantity_receivedby adding the new quantity - Records
received_attimestamp - Stores
receiving_notesfrom the request - Saves quality check results if provided
5. Status Determination
The system calculates totals and determines new status:
If all items fully received:
- Status changes to
RECEIVED - Sets
actual_delivery_dateto received_date (or today)
If some items received:
- Status changes to
PARTIALLY_RECEIVED - Tracks pending quantities for outstanding items
If no items received:
- Status remains
SENT(unchanged)
6. Inventory Updates
For each received item:
- Retrieves inventory record for the product and warehouse
- Adjusts stock quantity by the received amount
- Creates stock movement record with:
- Movement type: "purchase"
- Reason: "PO #{order_number} received"
- Reference to the purchase order
- Updates inventory totals and last movement timestamp
7. Event Dispatching
The system dispatches a PurchaseOrderReceived event containing:
- Purchase order ID and order number
- Received items and quantities
- User who processed the receipt
- Whether it's a partial receipt
This event triggers:
- Email notifications to procurement team
- Supplier performance metric updates
- Inventory alerts if reorder points reached
- Financial accrual updates (if Finance module integrated)
Integration Points
Inventory Module
Automatic Stock Updates
- Each received item increases inventory.quantity_on_hand
- Stock movements created for audit trail
- Movement type: "purchase"
- Reference links to PO and line item
Low Stock Alerts
- After receiving, system checks if other products below reorder point
- May trigger auto-reorder for different products
Supplier Module
Performance Metrics
- On-time delivery rate calculated
- Quality acceptance rate updated
- Lead time variance recorded
- Supplier rating adjusted based on quality checks
Finance Module (Future)
- Receipt triggers invoice matching workflow
- Accrual accounting for received goods
- Three-way match: PO + Receipt + Invoice
Best Practices
Receiving Workflow
- Receive Promptly: Process receipts same-day to maintain accurate inventory
- Inspect Thoroughly: Quality check before accepting
- Document Issues: Take photos of damaged goods, detailed notes
- Partial Receipts OK: Don't wait for complete shipment
- Update Immediately: Real-time updates prevent stock discrepancies
Quality Control
- Always Inspect: Even from trusted suppliers
- Require Notes: Failed quality checks must explain issue
- Track Patterns: Repeated issues indicate supplier problems
- Communicate Fast: Notify supplier immediately of issues
- Photo Evidence: Pictures worth 1000 words in disputes
Data Entry
- Verify Product IDs: Ensure correct product being received
- Double-Check Quantities: Count carefully, errors compound
- Include Context: Notes help future troubleshooting
- Use Warehouse IDs: Multi-warehouse operations need clarity
- Date Accuracy: Correct dates for performance metrics
Handling Discrepancies
Over-Shipment (more than ordered):
- System prevents over-receiving
- Accept ordered quantity only
- Coordinate with supplier for return/adjustment
Under-Shipment (less than ordered):
- Receive actual quantity
- Document shortage in notes
- Contact supplier for ETA on remainder
- Use partial receipt workflow
Wrong Items:
- Don't receive incorrect items
- Document issue with photos
- Contact supplier immediately
- Await correct items or cancellation
Damaged Items:
- Receive good units only
- Quality check failure with notes and photos
- Document damage extent
- Request replacement shipment
Troubleshooting
Common Errors
"Cannot receive purchase order in status: draft"
- Cause: Trying to receive a PO that hasn't been sent
- Solution: PO must be approved and sent first
- Workflow: Draft → Approve → Send → Receive
"Product ID 999 is not in this purchase order"
- Cause: product_id doesn't match any line item
- Solution: Check PO details for correct product IDs
- Tip: Use GET /purchase-orders/{id} to see line items
"Cannot receive 150 units. Only 100 units pending"
- Cause: Trying to receive more than ordered
- Solution: Check quantity_received vs quantity_ordered
- Fix: Adjust quantity_received to match available amount
"Quality notes are required when quality check fails"
- Cause: quality_check_passed = false but no notes provided
- Solution: Add quality_notes explaining the failure
- Rule: Failed quality checks must be documented
Validation Tips
Check PO Status First
GET /api/v1/operations/purchase-orders/45Verify status is SENT, ACKNOWLEDGED, or PARTIALLY_RECEIVED
Review Line Items
{
"line_items": [
{
"id": 156,
"product_id": 101,
"quantity_ordered": 100,
"quantity_received": 0, // Shows pending quantity
"warehouse_id": 1
}
]
}Calculate Pending Quantities
pending = quantity_ordered - quantity_receivedCan only receive up to pending amount
Related Guides
- Purchase Order Workflow - Complete PO lifecycle
- Auto-Reordering - Automated restock generation
- Inventory Management - Stock level tracking
- Stock Movements - Audit trail for inventory changes
Summary
The receiving process is the critical final step in the purchase order workflow. By following these guidelines, you can ensure accurate inventory, maintain quality standards, and build strong supplier relationships. Remember:
- Receive promptly and inspect thoroughly
- Document everything, especially issues
- Use partial receipts for split shipments
- Quality checks protect your operations
- System prevents over-receiving for safety