Loading...
Loading...
Design an order management system that routes orders to the right warehouse, handles split shipments, and manages backorders gracefully
npx skill4agent add finsilabs/awesome-ecommerce-skills order-management-system| Scenario | Recommended Approach | Why |
|---|---|---|
| Single warehouse, Shopify | Shopify + ShipStation | ShipStation handles order management, label creation, and tracking natively |
| Multi-location, Shopify | Shopify Locations + ShipStation or Shopify Fulfillment Network | Shopify supports up to 10 locations; ShipStation routes to the right location based on rules |
| 3PL integration | ShipBob, Whiplash, or Flexport + your platform's app | Each 3PL has native apps for Shopify, WooCommerce, and BigCommerce |
| Complex routing + backorders | Skubana (Extensiv), Linnworks, or ShipHero | These purpose-built OMS tools handle multi-warehouse routing, backorder queues, and split shipments |
| Custom / Headless | Build an OMS state machine + integrate Shippo/EasyPost for labels | Full control over routing rules, state transitions, and audit trail |
// Order status state machine with full audit trail
type OrderStatus =
| 'pending'
| 'payment_processing'
| 'paid'
| 'awaiting_fulfillment'
| 'partially_fulfilled'
| 'fulfilled'
| 'delivered'
| 'cancelled'
| 'refunded';
const VALID_TRANSITIONS: Partial<Record<OrderStatus, OrderStatus[]>> = {
pending: ['payment_processing', 'cancelled'],
payment_processing: ['paid', 'cancelled'],
paid: ['awaiting_fulfillment', 'cancelled'],
awaiting_fulfillment: ['partially_fulfilled', 'fulfilled', 'cancelled'],
partially_fulfilled: ['fulfilled'],
fulfilled: ['delivered', 'refunded'],
delivered: ['refunded'],
};
async function transitionOrder(params: {
orderId: string;
newStatus: OrderStatus;
actorId: string;
note?: string;
}): Promise<void> {
const order = await db.orders.findById(params.orderId);
const allowed = VALID_TRANSITIONS[order.status] ?? [];
if (!allowed.includes(params.newStatus)) {
throw new Error(`Invalid transition: ${order.status} → ${params.newStatus}`);
}
await db.transaction(async tx => {
await tx.orders.update(params.orderId, { status: params.newStatus, updated_at: new Date() });
// Every transition is recorded — this IS the audit trail
await tx.orderEvents.insert({
order_id: params.orderId,
from_status: order.status,
to_status: params.newStatus,
actor_id: params.actorId,
note: params.note ?? null,
occurred_at: new Date(),
});
});
}
// Route an order to the right fulfillment source
async function routeOrder(orderId: string): Promise<void> {
const order = await db.orders.findById(orderId);
const lines = await db.orderLines.findByOrderId(orderId);
for (const line of lines) {
// Check own warehouse first
const warehouseStock = await db.inventory.findAvailable(line.sku, line.quantity);
if (warehouseStock) {
await db.fulfillmentLines.insert({
order_id: orderId,
order_line_id: line.id,
source: 'warehouse',
source_id: warehouseStock.location_id,
status: 'pending',
});
continue;
}
// Fall back to dropship supplier
const supplier = await db.supplierProducts.findBestSupplier(line.product_id, line.quantity);
if (supplier) {
await db.fulfillmentLines.insert({
order_id: orderId,
order_line_id: line.id,
source: 'dropship',
source_id: supplier.supplier_id,
status: 'pending',
});
continue;
}
// No source available — create a backorder
await db.backorders.insert({
order_id: orderId,
order_line_id: line.id,
product_id: line.product_id,
quantity: line.quantity,
status: 'pending',
});
// Notify customer about the backorder
}
}| Problem | Solution |
|---|---|
| Order splits into multiple shipments unexpectedly | Pre-warn customers at checkout if an order will ship from multiple locations; show estimated delivery per shipment separately |
| Backorder never fulfilled after stock arrives | Set up an automatic trigger: when inventory is replenished above the backorder quantity, trigger fulfillment for the oldest pending backorder (FIFO) |
| Partial cancellation leaves the order in a broken state | Implement partial cancellation — cancel only lines that haven't been picked; issue a refund for cancelled lines; update the order total |
| Shopify shows "partially fulfilled" but customer thinks full shipment is coming | Send a clear email explaining each shipment as it ships, with the items in that specific shipment and the remaining items to follow |