Warehouse routing and the warehouse lock
Every product line on a sales order ships from a warehouse, and a single order can draw its lines from more than one. This page explains how a line is routed to a warehouse, how an order fans out into a separate shipment per warehouse, and the safety rule — the warehouse lock — that freezes a line's warehouse once real inventory has started to move.
Words like allocation, backorder, fulfillment order, shipment, and dropship are defined the first time they appear below and collected in the glossary.
Every line carries a warehouse
On a sales order, the warehouse lives at the line level, not the order level. Each product line records which warehouse it ships from, plus a routing method that says how that warehouse was chosen. Two lines on the same order can point at two different warehouses, and that's exactly what lets one order ship in parts from different locations.
The routing method a line carries determines whether it draws on local stock at all:
| Routing method | What it means | Warehouse required? |
|---|---|---|
| Warehouse | The line ships from a specific local warehouse. Its warehouse is set, and stock is allocated and fulfilled there. | Yes — a warehouse must be chosen. |
| Dropship | The line ships from a supplier straight to the customer. It bypasses warehouse allocation and local fulfillment entirely. | No — the supplier holds the stock. See Create a dropship request. |
| Unassigned | The line hasn't been routed to a warehouse yet. It can't be allocated or shipped until a warehouse is assigned. | Not yet. |
For a line routed to a Warehouse, a warehouse is mandatory — the order can't be validated or approved with that line left without one. When SKU.io needs to pick a warehouse for you (during approval or in the Allocate dialog), it uses a routing suggestion based on where stock is available; you can override it. See Approve, reserve, and allocate stock.
An order can split across warehouses
Because the warehouse sits on the line, a single order naturally splits when its lines live in different places. When you approve and start fulfilling, SKU.io builds one fulfillment order per warehouse — the per-warehouse plan for what ships from where — and each fulfillment order ships as its own shipment. One sales order therefore becomes many fulfillment orders and many shipments, one set per warehouse involved.
You can reshape this split by hand: move a line to another warehouse, or slice one line's quantity across several warehouses so part ships from each. A line split across warehouses isn't duplicated on the sales channel — the customer still sees one ordered line, while SKU.io tracks the pieces internally. These actions live on the Fulfillment tab; see Split, move, and merge fulfillments.
At any moment, the set of warehouses a line is actively set to ship from is derived from its live (non-cancelled, non-terminal) fulfillment orders — so cancelling or completing a fulfillment order updates where the line is considered shippable from.
The warehouse lock: Why a warehouse freezes once you ship
Routing is fully editable — until it isn't. The moment a line begins to fulfill, its warehouse becomes load-bearing: it's the location whose stock was drawn down, the location the inventory ledger and the COGS basis were written against, and the location a provider was told to ship from. Silently repointing it afterward would leave the books describing one warehouse while reality happened at another.
To prevent that drift, SKU.io locks the warehouse on two records once fulfillment starts against them:
| Record | Locks once… | Effect |
|---|---|---|
| A shipment (the executed fulfillment record) | it has recorded any inventory movement | Its warehouse can no longer be changed. |
| An order line | it has a fulfillment line on a non-cancelled shipment — or a submitted / in-flight fulfillment order already covers it | Its warehouse can no longer be changed. |
The line lock deliberately arms before anything physically ships: once a fulfillment order has been submitted to a carrier or 3PL, the line is locked even though no shipment record exists yet, because a provider has already been told where to ship from.
Fulfillment lines that hang off a cancelled shipment, and cancelled fulfillment orders, don't lock anything. Those will never ship, so the line stays free to be re-routed — for example when a channel cancels a shipment and the order is re-planned to a different location.
What you see when a warehouse is locked
Because the lock protects a line that's already shipping, you meet it when you try to edit the wrong thing:
- Changing the warehouse on a line that has already started shipping is refused. SKU.io blocks the save and the error identifies the specific record and the reason — that the line already has a shipment (or the shipment already has inventory movements), so its warehouse is locked.
- Editing a line that's fulfilled at a warehouse and quantity is blocked the same way from the order-edit flow, which is the first line of defense; the lock itself is the backstop underneath it. See View and edit a sales order.
The remedy is always the same: to re-route a line that has shipped, void its shipment first. Voiding reverses the inventory movement and releases the lock, after which the warehouse can be reassigned. See Void, restore, or reset a shipment.
The lock exists so that a shipped line's warehouse can never quietly change. If you genuinely need a different warehouse, you are changing the plan: void, re-route, and re-ship. There is no in-place override in the normal UI.
Clearing a line's warehouse cancels its allocations
There is one special transition worth understanding: setting a line's warehouse back to none (for example, when a channel such as Shopify unassigns a line asynchronously). A line with no warehouse is routed out of local warehousing and must hold no reservation — any allocation left behind would be a ghost commitment against a warehouse the line no longer points at, inflating committed stock and keeping a phantom backorder alive.
So whenever a line's warehouse is cleared, SKU.io automatically cancels that line's active allocations, exactly once, at the moment the warehouse is nulled. You don't trigger this by hand — it's a write-boundary guarantee that keeps a cleared line from stranding a reservation. See Inventory allocation and backorders.
Under the hood: The repair-only bypass and its safety net
The lock is intentionally strict, so there is exactly one sanctioned way past it — a bounded repair mode reserved for SKU.io's own inventory-health tooling. It exists only to reconcile pre-existing drift (for example, realigning a record to the warehouse where its inventory movement actually landed) and runs synchronously within a single operation; it's never something a product feature or a background job reaches for. Adding any new caller requires explicit engineering review.
Two things administrators and support staff should keep in mind:
- Raw database writes bypass the lock. The lock rides on the application's save events, so a direct SQL update or a bulk query-builder write can move a warehouse without tripping it. Such writes are reviewed by hand and aren't part of any normal workflow.
- A health sweep is the backstop — but correcting drift is a deliberate step, not silent self-healing. If drift ever does slip through — a stranded allocation on a cleared line, or a warehouse that diverged from where movements landed — an hourly
sku:inventory:health:notifysweep runs in identify-only mode: it detects the drift and alerts the team on Slack, but it doesn't repair. Remediation is then applied withsku:inventory:health --fix. A separate automated sweep (every few hours) self-heals fulfillment-order drift, but the stranded-allocation and diverged-warehouse repairs named here are run on demand rather than on a schedule. The invariant holds because escapees are caught and corrected — not because those two classes quietly repair themselves each hour.
This is the "Stage 0" form of a larger change: over time, the warehouse is moving onto the fulfillment order as the single source of truth for where work ships from, which is why the lock reasons about both the line and its fulfillment orders today. See Two-tier fulfillment: intent vs execution.
Worked example: Two warehouses, one order
Follow SO-DOCS-0001 for Blue Bottle Retail with two product lines:
| Moment | Stainless Water Bottle 750ml | Insulated Tumbler 500ml |
|---|---|---|
| You create and route the lines | Routed to Main Warehouse (editable) | Routed to Overflow Warehouse (editable) |
| You approve the order | Allocated at Main Warehouse; still re-routable | Allocated at Overflow Warehouse; still re-routable |
| You dispatch the Overflow line to a carrier | Still editable — no work started | Locked — a submitted fulfillment order now covers it |
| You ship the Main Warehouse line | Locked — its shipment recorded inventory movements | Locked |
| You void the Overflow shipment | Locked | Unlocked — void reversed the movement; re-route freely |
Notice that each line locks independently, at its own moment: the Overflow line locked when its fulfillment order was submitted, before anything shipped, while the Main Warehouse line locked only when its shipment actually moved stock. Voiding is what returns a line to an editable, re-routable state.
Next steps
- Two-tier fulfillment: intent vs execution — the fulfillment order vs shipment split behind warehouse routing
- Split, move, and merge fulfillments — reshape which warehouse a line ships from
- Void, restore, or reset a shipment — how to release the lock and re-route
- Approve, reserve, and allocate stock — the routing suggestion and the Allocate dialog
- Inventory allocation and backorders — what happens to a line's reservation when its warehouse changes
- Create a dropship request — lines that route to a supplier instead of a warehouse
- Sales orders overview