Accounting truth versus warehouse reality
In an enterprise ERP, inventory is an asset on the balance sheet. Until a product physically crosses the threshold of the warehouse and a bill of lading is signed, the business still legally owns the goods. As a result, standard ERP stock tables—such as StockPositions.CurrentStock in Exact Online—keep the quantity intact throughout the entire pick, pack, and stage cycle.
Ecommerce platforms operate on an entirely conflicting premise. A storefront does not care about legal title transfer; it cares about pickable availability. If an item cannot be picked from a shelf right now, offering it for immediate sale causes split shipments, backorder apologies, and customer service escalation.
Merchants assume they can solve this by mapping the ERP's 'allocated' or 'committed' field into Shopify. But in the Shopify Admin API, committed inventory is a derived, read-only metric calculated strictly from open orders created within that specific Shopify store. You cannot call inventorySetQuantities and pass an arbitrary committed payload for an order that originated in wholesale, phone sales, or an external B2B channel. If an external wholesale order is being picked on the floor, the only lever the integration has is to modify Shopify's available stock directly.
The double-deduction trap
When developers first confront this lag, the common instinct is to make the ERP deduct stock early. When the picking slip prints, the middleware script deducts the quantities from the ERP's stock position or immediately posts an inventory adjustment.
This is an architectural trap that corrupts financial reporting. When warehouse staff finish packing the pallets at the end of the shift, they scan the shipping label and post the standard ERP Goods Delivery. The ERP's native accounting engine fires, deducting the shipped items from stock. Because the middleware already deducted those units when the picking slip printed, the ERP deducts the stock a second time.
Within forty-eight hours, the warehouse ledger drifts away from reality. Physical stock counts fail, financial audits flag mysterious negative inventory balances, and warehouse managers lose trust in the software.
To solve the picking lag without corrupting accounting, the system must never alter the ERP's physical shelf stock prematurely. Instead, operational availability must be derived in a dedicated middleware layer that leaves ERP accounting untouched.
The operational reservation layer
On Weldaad, an international wholesale and retail home goods merchant integrated with Exact Online, the warehouse team needed to eliminate overselling without introducing accounting discrepancies.
The solution was an operational reservation engine that sits between Exact Online and Shopify. The engine monitors order workflow states across the business. When an order is approved for warehouse fulfillment, it receives a specific operational selection code: MONDIAL.
The moment an order carries the MONDIAL flag, the engine evaluates every line item on the order and creates an ephemeral picking reservation grouped by SKU. Operational availability is then calculated using a strict formula: Web Available Stock = max(0, Exact Physical Shelf Stock - Active Picking Reservations - Safety Buffer).
This derived number is pushed directly to Shopify's available inventory level. The raw ERP shelf stock remains untouched, maintaining absolute accounting integrity. Shopify customers only see what is genuinely sitting uncommitted on the warehouse racks.
Cross-order SKU aggregation and state release
The challenge compounds when multiple wholesale and retail orders move through the picking floor simultaneously. Fast-moving SKUs are rarely picked one order at a time; five different picking slips might each require two units of the same ceramic bowl.
The reservation engine must aggregate picking reservations across all concurrent orders by SKU. Furthermore, it must manage the atomic lifecycle transition: when the warehouse finishes packing order WLD-1001 and marks it shipped, the ERP posts the official Goods Delivery, decrementing its physical shelf stock from 15 to 11.
At that exact moment, the reservation engine must release the 4-unit picking reservation for WLD-1001. Before shipment, 15 physical minus 4 reserved equals 11 available. After shipment, 11 physical minus 0 reserved equals 11 available. Because the reservation release matches the ERP's physical deduction, Shopify's available stock remains stable at 11 units throughout the entire transition. There is no sudden stock spike when the order ships, and no double-deduction drop.
export type OrderWorkflowState = "draft" | "picking" | "shipped" | "cancelled";
export interface WarehouseOrder {
orderId: string;
orderNumber: string;
state: OrderWorkflowState;
selectionCode?: string; // "MONDIAL" indicates active picking release
lines: Array<{ sku: string; quantity: number }>;
updatedAt: string;
}
export interface ErpStockRecord {
sku: string;
physicalShelfStock: number; // Raw ERP StockPositions.CurrentStock
safetyBuffer?: number;
}
export interface ShopifyInventoryLevel {
sku: string;
inventoryItemId: string;
locationId: string;
available: number;
}
export interface PickingReconciliationPlan {
reconciledSkus: Array<{
sku: string;
physicalShelfStock: number;
activePickingReserved: number;
targetAvailable: number;
currentShopifyAvailable: number;
needsShopifyUpdate: boolean;
}>;
shopifySetOperations: Array<{
inventoryItemId: string;
locationId: string;
available: number;
}>;
}
/**
* Reconciles ERP physical inventory against active warehouse picking orders,
* computing true web-available stock while strictly guarding against double deductions.
*/
export function reconcilePickingReservations(
orders: WarehouseOrder[],
erpStock: Map<string, ErpStockRecord>,
shopifyStock: Map<string, ShopifyInventoryLevel>
): PickingReconciliationPlan {
const activeReservations = new Map<string, number>();
// 1. Aggregate in-flight reservations across all active picking orders
for (const order of orders) {
if (order.state === "picking") {
for (const line of order.lines) {
const current = activeReservations.get(line.sku) || 0;
activeReservations.set(line.sku, current + line.quantity);
}
}
}
const reconciledSkus = [];
const shopifySetOperations = [];
// 2. Derive web availability per SKU and detect delta against Shopify
for (const [sku, erpRecord] of erpStock.entries()) {
const reservedQty = activeReservations.get(sku) || 0;
const buffer = erpRecord.safetyBuffer || 0;
const rawAvailable = erpRecord.physicalShelfStock - reservedQty - buffer;
const targetAvailable = Math.max(0, rawAvailable);
const shopifyRecord = shopifyStock.get(sku);
const currentShopifyAvailable = shopifyRecord ? shopifyRecord.available : 0;
const needsUpdate = targetAvailable !== currentShopifyAvailable;
reconciledSkus.push({
sku,
physicalShelfStock: erpRecord.physicalShelfStock,
activePickingReserved: reservedQty,
targetAvailable,
currentShopifyAvailable,
needsShopifyUpdate: needsUpdate,
});
if (needsUpdate && shopifyRecord) {
shopifySetOperations.push({
inventoryItemId: shopifyRecord.inventoryItemId,
locationId: shopifyRecord.locationId,
available: targetAvailable,
});
}
}
return { reconciledSkus, shopifySetOperations };
}What this architecture does not handle
While an operational reservation buffer eliminates the gap between picking slips and ERP despatch, it has defined boundaries:
Unregistered floor shrinkage: If a warehouse picker drops and smashes a fragile vase, the reservation engine assumes the item is still available until staff manually log a damaged stock adjustment in the ERP.
Offline barcode scanner latency: If warehouse handheld terminals operate in offline batch mode (synchronising via cradle docking rather than continuous Wi-Fi), picking state updates lag until terminals reconnect.
Multi-warehouse transfer transit: When moving stock between a central distribution hub and a regional retail showroom, items in transit require dedicated transit location handling rather than picking reservations.
When this needs an engineer
If your business fulfills retail orders directly from a single Shopify-managed warehouse using native Shopify Fulfillment workflows, you do not need custom reservation middleware. Shopify automatically calculates committed stock for orders placed through the online store.
It requires dedicated engineering when commerce spans multiple fulfillment surfaces: when an ERP is the financial source of truth and only records stock decrements upon formal Goods Delivery shipment; when wholesale B2B orders, phone orders, or EDI shipments are picked from the same physical bins as D2C online store stock; and when picking and packing cycles take multiple hours or days, creating wide operational windows where unreserved stock is vulnerable to overselling.
In our work on ERP integrations and operational warehouse systems, we treat inventory not as a static integer in a database, but as a live pipeline reflecting the physical reality of the warehouse floor.
Send us the store and the symptom.
