Running multiple warehouses adds complexity that WooCommerce wasn’t designed to handle natively. WooCommerce tracks a single stock quantity per product. Your ERP tracks stock across 3, 5, or 20 locations. Bridging this gap requires careful architecture decisions about how inventory is aggregated, how orders are routed, and how customers see availability.

The Multi-Warehouse Challenge

Your ERP shows:

Product: SKU-1234 "Blue Widget"

├── Warehouse A (New York): 50 units

├── Warehouse B (Los Angeles): 30 units

├── Warehouse C (Chicago): 0 units

└── Total Available: 80 units

What should WooCommerce show? Options:

  • Total available (80) — simplest, but might promise stock that’s committed elsewhere
  • Nearest warehouse — requires knowing customer location before they add to cart
  • Fulfillment warehouse only — only show stock from the warehouse designated for online orders
  • Available minus buffer — total minus a safety margin (e.g., 72 = 80 – 10%)

Architecture

ERP (multi-warehouse stock)

→ Aggregation Logic (middleware)

→ WooCommerce (single stock number per product)

WooCommerce (order placed)

→ Routing Logic (middleware)

→ ERP (assign to optimal warehouse)

The middleware handles the mismatch between ERP’s multi-location model and WooCommerce’s single-location model.

Inventory Aggregation Strategies

Strategy 1: Sum All Warehouses

function aggregateStock(erpItem) {

const totalStock = erpItem.warehouses.reduce(

(sum, wh) => sum + wh.available, 0

);

return {

sku: erpItem.sku,

stock_quantity: totalStock,

stock_status: totalStock > 0 ? 'instock' : 'outofstock'

};

}

Pros: Maximizes sellable inventory
Cons: May oversell if warehouse-specific commits aren’t tracked

Strategy 2: Primary Warehouse + Overflow

Designate one warehouse as the primary fulfillment center for online orders. Show its stock, but allow overflow from secondary warehouses:

function aggregateStock(erpItem, primaryWarehouseId) {

const primary = erpItem.warehouses.find(w => w.id === primaryWarehouseId);

const overflow = erpItem.warehouses

.filter(w => w.id !== primaryWarehouseId)

.reduce((sum, w) => sum + w.available, 0);

// Show primary stock + 50% of overflow (buffer for retail/wholesale)

const displayStock = (primary?.available || 0) + Math.floor(overflow * 0.5);

return {

sku: erpItem.sku,

stock_quantity: displayStock,

stock_status: displayStock > 0 ? 'instock' : 'outofstock'

};

}

Strategy 3: Regional Display (Advanced)

Show different stock levels based on the customer’s detected region:

async function getRegionalStock(erpItem, customerRegion) {

const regionWarehouseMap = {

'east': ['WH-NYC', 'WH-ATL'],

'west': ['WH-LAX', 'WH-SEA'],

'central': ['WH-CHI', 'WH-DAL']

};

const relevantWarehouses = regionWarehouseMap[customerRegion] || Object.values(regionWarehouseMap).flat();

const regionalStock = erpItem.warehouses

.filter(w => relevantWarehouses.includes(w.id))

.reduce((sum, w) => sum + w.available, 0);

return { stock_quantity: regionalStock };

}

This requires dynamic stock display on the frontend — typically via an AJAX call after detecting the customer’s location.

Order Routing

When an order comes in, your middleware must decide which warehouse fulfills it:

function routeOrder(order, warehouseStock) {

const shippingState = order.shipping.state;

const items = order.line_items;

// 1. Find warehouses that can fulfill the entire order

const candidates = warehouseStock.filter(wh =>

items.every(item =>

wh.inventory[item.sku] >= item.quantity

)

);

if (candidates.length === 0) {

// No single warehouse can fulfill — need split shipment

return splitShipment(order, warehouseStock);

}

// 2. Score candidates

const scored = candidates.map(wh => ({

warehouse: wh,

score: calculateScore(wh, shippingState, items)

}));

// 3. Select best warehouse

return scored.sort((a, b) => b.score - a.score)[0].warehouse;

}

function calculateScore(warehouse, customerState, items) {

let score = 0;

// Proximity (shipping zone distance)

const distance = getDistance(warehouse.state, customerState);

score += (1000 - distance) * 2; // Closer = higher score

// Stock depth (prefer warehouses with more stock)

const minStockRatio = Math.min(

...items.map(item => warehouse.inventory[item.sku] / item.quantity)

);

score += minStockRatio * 100;

// Workload (prefer less busy warehouses)

score -= warehouse.pendingOrders * 5;

return score;

}

Split Shipments

When no single warehouse can fulfill the entire order:

function splitShipment(order, warehouses) {

const shipments = [];

const remaining = [...order.line_items];

// Greedy algorithm: assign items to nearest warehouse with stock

while (remaining.length > 0) {

const item = remaining[0];

const warehouse = warehouses

.filter(wh => wh.inventory[item.sku] >= item.quantity)

.sort((a, b) =>

getDistance(a.state, order.shipping.state) -

getDistance(b.state, order.shipping.state)

)[0];

if (!warehouse) {

throw new Error(Cannot fulfill ${item.sku}: insufficient stock across all warehouses);

}

// Find or create shipment for this warehouse

let shipment = shipments.find(s => s.warehouseId === warehouse.id);

if (!shipment) {

shipment = { warehouseId: warehouse.id, items: [] };

shipments.push(shipment);

}

shipment.items.push(item);

warehouse.inventory[item.sku] -= item.quantity;

remaining.shift();

}

return shipments;

}

Customer communication for split shipments:

  • Notify customer that their order will arrive in multiple packages
  • Provide separate tracking numbers per shipment
  • Don’t charge extra shipping (absorb the cost)

WooCommerce Implementation

Plugin Approach

Use a multi-warehouse plugin (like ATUM or WooCommerce Multi-Warehouse) that adds per-warehouse stock tracking to WooCommerce. Your ERP sync updates each warehouse’s stock level.

Custom Meta Approach

Store warehouse-level data in product meta:

// Store per-warehouse stock in product meta

update_post_meta($product_id, '_warehouse_stock', json_encode([

'WH-NYC' => 50,

'WH-LAX' => 30,

'WH-CHI' => 0

]));

// Display stock is the aggregated number

update_post_meta($product_id, '_stock', 80);

update_post_meta($product_id, '_stock_status', 'instock');

Monitoring Multi-Warehouse Operations

Metric What to Track
Stock accuracy ERP stock vs WooCommerce displayed stock
Routing efficiency % of orders fulfilled from nearest warehouse
Split shipment rate % of orders requiring split shipments (target: < 5%)
Fulfillment time by warehouse Identify slow warehouses
Stock-out events When aggregated stock hits zero

Conclusion

Multi-warehouse WooCommerce is fundamentally an ERP problem solved through middleware. WooCommerce displays a simplified view; your ERP manages the complexity. The key decisions — aggregation strategy, routing logic, and split shipment handling — depend on your specific logistics setup. Start with the simplest aggregation (sum or primary warehouse), implement basic routing, and add complexity only when the data shows you need it. Over-engineering warehouse logic before you have the order volume to justify it wastes development time.

Leave a Reply

Your email address will not be published. Required fields are marked *

Close Search Window