Order Shipping & Fulfillment

The Orders module manages the full shipping lifecycle from shipment creation through delivery, including partial fulfillment, real-time tracking sync, and NDR (Non-Delivery Report) workflows.

Shipment Model

Each shipment is linked to an order and optionally to specific order items (for partial fulfillment):

Field Type Description
id UUID Primary key
order_id UUID Parent order
carrier_module string Shipping provider module alias (e.g., shipping-delhivery)
awb_number string Air Waybill / tracking number
status string pending, picked_up, in_transit, out_for_delivery, delivered, rto_initiated, rto_received, cancelled
tracking_url string Carrier tracking page URL
label_url string Shipping label PDF URL
pickup_token string Pickup schedule token from carrier
estimated_delivery_at datetime Estimated delivery date
order_item_ids jsonb Items in this shipment (for partial fulfillment)
dimensions jsonb Package dimensions {length, width, height, weight}
ndr_count integer Number of NDR attempts
shipped_at datetime When carrier picked up
delivered_at datetime When delivered

Creating a Shipment

POST /api/v1/orders/{order}/ship

{
  "carrier_module": "shipping-delhivery",
  "awb_number": "DLV123456789",
  "tracking_url": "https://track.delhivery.com/DLV123456789",
  "estimated_delivery_at": "2026-03-15T00:00:00Z",
  "dimensions": {
    "length": 30,
    "width": 20,
    "height": 10,
    "weight": 500
  }
}

Creating a shipment:

  1. Creates the Shipment record
  2. Updates the order status to shipped
  3. Publishes order.shipment_created event
  4. Publishes order.fulfilled event

Partial Fulfillment

Orders can be shipped in multiple packages. Specify order_item_ids to indicate which items are in each shipment:

POST /api/v1/orders/{order}/ship

{
  "carrier_module": "shipping-delhivery",
  "awb_number": "DLV123456789",
  "order_item_ids": ["item-uuid-1", "item-uuid-2"]
}
POST /api/v1/orders/{order}/ship

{
  "carrier_module": "shipping-delhivery",
  "awb_number": "DLV123456790",
  "order_item_ids": ["item-uuid-3"]
}

The order relationship is HasMany — an order can have multiple shipments.

Listing Shipments

GET /api/v1/orders/{order}/shipments

Returns all shipments for an order with their tracking events.

Tracking Events

Each shipment records tracking events as ShipmentEvent records:

Field Type Description
id UUID Primary key
shipment_id UUID Parent shipment
status string Event status
location string Location description
description string Event description
raw_data jsonb Raw carrier webhook data
occurred_at datetime When the event happened

Tracking Sync

A scheduled SyncTrackingStatusJob runs every 30 minutes to poll in-transit shipments from carrier APIs. Carriers that support webhooks push updates via ShipmentWebhookController.

NDR (Non-Delivery Report) Handling

When a delivery attempt fails, carriers report an NDR:

Field Type Description
id UUID Primary key
shipment_id UUID Parent shipment
attempt_number int Which delivery attempt
reason string Failure reason
action string Requested action (reattempt, return_to_origin)
resolved_at datetime When resolved

NDR workflows:

  1. Carrier reports failed delivery attempt
  2. NdrCase record created, ndr_count incremented on shipment
  3. System can auto-reattempt or escalate based on configured rules
  4. If max attempts exceeded, triggers RTO (Return to Origin)

Order Status Transitions

The shipping lifecycle drives order status:

confirmed → processing → ready_to_ship → shipped → out_for_delivery → delivered
                                       → rto_initiated → rto_received

Can Ship Check

The can_ship accessor on the Order model returns true for:

  • confirmed
  • processing
  • ready_to_ship

Returns false for all other statuses (pending, shipped, delivered, cancelled, etc.).

Carrier Integration via Bus

Shipping operations call carrier modules through the Module API Bus:

// Rate comparison
$rates = $bus->call('shipping-delhivery', 'shipping.getRates', [
    'pickup_pincode' => '400001',
    'delivery_pincode' => '560001',
    'weight' => 500,
    'dimensions' => ['length' => 30, 'width' => 20, 'height' => 10],
]);

// Create shipment with carrier
$result = $bus->call('shipping-delhivery', 'shipping.createShipment', [
    'order_number' => $order->order_number,
    'pickup_address' => [...],
    'delivery_address' => [...],
    'items' => [...],
    'weight' => 500,
    'cod_amount' => $order->is_cod ? $order->total : 0,
]);

Bulk Shipping

Create shipments for multiple orders at once:

POST /api/v1/orders/bulk/ship

{
  "shipments": [
    {
      "order_id": "uuid1",
      "carrier_module": "shipping-delhivery",
      "awb_number": "DLV123456",
      "tracking_url": "https://track.delhivery.com/DLV123456"
    },
    {
      "order_id": "uuid2",
      "carrier_module": "shipping-delhivery",
      "awb_number": "DLV123457",
      "tracking_url": "https://track.delhivery.com/DLV123457"
    }
  ]
}

Events

Event When
order.shipment_created Shipment created with tracking info
order.fulfilled Order shipped or out for delivery
order.delivered Order delivered to customer
order.status_changed Any status transition (includes tracking updates)