Order Inventory Integration

The Orders module integrates with inventory systems through an event-driven pattern. Stock is reserved when orders are created and released when orders are cancelled — without requiring tight coupling to any specific inventory module.

Architecture

Orders Module                    Inventory / WMS Module
    │                                    │
    ├── order.created ──────────────────►│ Reserve stock
    │                                    │
    ├── order.cancelled ────────────────►│ Release reservation
    │                                    │
    ├── order.shipment_created ─────────►│ Decrement stock
    │                                    │
    └── order.inventory_reserved ◄──────│ Confirm reservation

How It Works

On Order Created

When an order is created via the ingest pipeline, the OnOrderCreatedReserveStock listener fires:

// Listener self-subscribes to Orders.order.created
class OnOrderCreatedReserveStock
{
    public function handle(array $payload): void
    {
        $order = Order::with('items')->find($payload['order_id']);

        $this->reservationService->reserveForOrder($order);
    }
}

The InventoryReservationService publishes an order.inventory_reserved event with item details:

$this->eventBus->publish('Orders', 'order.inventory_reserved', [
    'order_id' => $order->id,
    'items' => $order->items->map(fn($item) => [
        'product_id' => $item->product_id,
        'variant_id' => $item->variant_id,
        'sku' => $item->sku,
        'quantity' => $item->quantity,
    ])->toArray(),
]);

Any inventory module (Products, WMS, or a custom module) can subscribe to this event and handle the actual stock decrement.

On Order Cancelled

The OnOrderCancelledReleaseStock listener fires when an order is cancelled:

// Listener self-subscribes to Orders.order.cancelled
class OnOrderCancelledReleaseStock
{
    public function handle(array $payload): void
    {
        $order = Order::with('items')->find($payload['order_id']);

        $this->reservationService->releaseForOrder($order);
    }
}

Publishes order.inventory_released so inventory modules can restore the reserved stock.

On Shipment Created

When items are actually shipped, the reservation converts to a permanent decrement:

$this->reservationService->decrementForShipment($order, $shipment);

Graceful Degradation

The inventory integration is designed to work even if no inventory module is installed:

  • Events are published regardless — if no module subscribes, they are simply ignored
  • No exceptions are thrown if inventory operations fail
  • Orders continue to process normally without inventory tracking

This means:

  • A new tenant with only the Orders module works out of the box
  • Adding an inventory module later automatically picks up the events
  • Removing an inventory module doesn't break order processing

Module Configuration

In modules/Orders/module.json, the self-subscriptions are declared:

{
  "api": {
    "events": {
      "subscribes": [
        {
          "source": "Orders",
          "event": "order.created",
          "handler": "OnOrderCreatedReserveStock@handle"
        },
        {
          "source": "Orders",
          "event": "order.cancelled",
          "handler": "OnOrderCancelledReleaseStock@handle"
        }
      ]
    }
  }
}

Subscribing from an Inventory Module

To handle stock reservation from a Products or WMS module:

// modules/Products/module.json
{
  "api": {
    "events": {
      "subscribes": [
        {
          "source": "Orders",
          "event": "order.inventory_reserved",
          "handler": "StockReservationHandler@reserve"
        },
        {
          "source": "Orders",
          "event": "order.inventory_released",
          "handler": "StockReservationHandler@release"
        }
      ]
    }
  }
}
// modules/Products/backend/App/Listeners/StockReservationHandler.php
class StockReservationHandler
{
    public function reserve(array $payload): void
    {
        foreach ($payload['items'] as $item) {
            if ($item['product_id']) {
                ProductVariant::where('id', $item['variant_id'])
                    ->decrement('stock_quantity', $item['quantity']);
            }
        }
    }

    public function release(array $payload): void
    {
        foreach ($payload['items'] as $item) {
            if ($item['product_id']) {
                ProductVariant::where('id', $item['variant_id'])
                    ->increment('stock_quantity', $item['quantity']);
            }
        }
    }
}

Events Reference

Event Direction Payload
order.inventory_reserved Published order_id, items[{product_id, variant_id, sku, quantity}]
order.inventory_released Published order_id, items[{product_id, variant_id, sku, quantity}]
order.created Subscribed Triggers reservation
order.cancelled Subscribed Triggers release