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 |