Support

Need help?

Most answers are already written down. For everything else, Oneop does not publish a response time — there is no SLA instrument behind one — so the fastest thing you can do is send a complete first message.

Search the help centreEmail support@oneop.ioOpen your workspace

Worth including

  • The email you log in with
  • The URL of the screen it happened on
  • What you expected, and what happened instead

Goes somewhere else

Inventory

Inventory reservations: on-hand stock is not available stock

By The Oneop team7 min read

TL;DR

An inventory reservation is a durable record that a quantity of an item is committed to a specific order before it physically ships. Reserved units stay on the shelf and still count as on-hand, but they are subtracted from available so nothing else can claim them.

A warehouse count tells you what is on the shelf. It does not tell you what you can sell, and the gap between those two numbers is made of reservations. A reservation is the record that says a specific quantity is already spoken for by a specific order, and almost every stock problem that is not a counting problem is a reservation lifecycle problem.

The concept of promising from available rather than on-hand is covered in available-to-promise and why overselling happens. This post is about the reservation itself: when it is created, what releases it, what happens when nothing does, and why two systems each holding their own reservations will oversell no matter how fast they talk to each other.

What a reservation is, precisely

An inventory reservation is a durable row, not a flag and not a calculation. It names an item, a quantity, a location and the order that holds it, and it persists across restarts, deploys and the sudden unavailability of whoever created it.

Three properties follow from that definition, and each one is a design constraint:

  • Reserved units are still on-hand. Nothing has physically moved. A cycle count will find them on the shelf, and it should.
  • Available is derived, never stored. Available equals on-hand minus reserved. Storing available as its own field creates a third number that can disagree with the other two.
  • A reservation has an owner. If you cannot name the order a reservation belongs to, you cannot release it when that order dies.

The third property is the one most often skipped, and it is the one that produces the failure below.

Create and release: the four transitions

TransitionTriggerEffect on on-handEffect on reserved
CreateOrder confirmed, or a scarce item added to a cartNo changeIncreases
ConsumeShipment picked and dispatchedDecreasesDecreases
Release, cancellationOrder cancelled, voided, or payment failedNo changeDecreases
Release, expiryTime-limited hold reaches its deadlineNo changeDecreases

The middle row is the only one where on-hand moves. That is the whole reason reservations exist: they let availability respond to a commitment immediately, hours or days before the physical movement that would otherwise be the first signal.

The two release rows are the ones systems get wrong, because neither is triggered by a person doing something. A cancellation release fires on an event the customer causes; an expiry release fires on nothing at all happening, which requires a scheduled sweep that must itself be durable.

The failure case: stock you own and cannot sell

Overselling gets all the attention. The mirror failure is quieter and just as expensive.

A retailer reserves stock at add-to-cart to stop scarce items being lost between browse and checkout. The reservation is created correctly. Checkout completion converts it to an order. Cancellation releases it. Expiry, however, is implemented as a nightly cleanup job that was disabled during a deployment eight days ago and never re-enabled, because nothing about a disabled cleanup job is visible to anyone.

Abandoned carts accumulate. After eight days, 63 units across 40 SKUs sit reserved against sessions that ended days ago. On-hand is correct, the shelves are full, and the storefront is showing “out of stock” on items a customer is standing in front of in the shop. Purchasing sees availability at zero and raises a replenishment order for stock already in the building. The discrepancy is found during a stock count, when the counted quantity matches on-hand exactly and nobody can explain why the site says zero.

Notice what did not happen: no unit was lost, no count was wrong, no order was mishandled. A release path stopped running and the system kept behaving as designed, with a growing set of holds that nothing would ever remove.

The design lesson is that every reservation needs a release path that does not depend on a person or an unmonitored job, and that expiry should be evaluated when availability is read, not only when a sweep runs. A hold whose deadline has passed is not a hold, whether or not the cleanup has got to it yet.

Concurrency: why check-then-reserve is not enough

The other half of reservation correctness is concurrency, and it has a precise failure mode. Two orders for the last three units arrive milliseconds apart. Both read available as 3. Both pass. Both write a reservation. On-hand is 3, reserved is 6, and available is negative.

The fix is that reading availability and writing the reservation must not be separable. The read has to take a lock the second transaction blocks on, so that the second order reads the numbers as they are after the first commits rather than as they were before it started. Retrying, validating twice, or checking again after writing all reduce the frequency and none of them close the window.

This is also why the check cannot live one layer up. A user interface that greys out the unavailable option is showing a number that was true when the page rendered. The refusal has to happen at the write, where the current state is.

Two systems, two copies, one shelf

Now put the same item in two systems: a storefront with its own stock cache and an operations system with the authoritative numbers.

Each system checks availability against its own copy. The storefront confirms an order for 3 and reserves 3 locally. Inside the same sync window, a phone order is taken in the operations system, which also sees 3 and reserves 3. Both checks passed against a copy that was accurate at the moment it was written and stale by the time it was read.

There is no sync frequency that fixes this. A five-minute sync makes the collision rare; a thirty-second sync makes it rarer and much harder to reproduce; a real-time push makes it rare enough to be blamed on the customer. The property that would fix it, a single serialisation point where both reservations contend for the same row, is precisely the property two copies do not have. This is the inventory instance of the general problem described in connecting CRM, invoicing and accounting: duplicated state cannot be made consistent by communication alone.

How Oneop handles reservations

In Oneop’s inventory module, available is on-hand minus reserved, computed under a row lock. Five code paths change stock, and every one of them takes a row-level lock before it reads the number it is about to change. A reservation that would exceed on-hand minus reserved is rejected at the write with an insufficient-stock error rather than being greyed out in the interface.

Two details matter more than the headline. First, the reservation accepts the caller’s transaction, so a sales order’s reservation commits with the order itself rather than as a separate step that can succeed while the order fails. Second, issuing reserved stock at shipment releases the hold in the same operation, clamped so that reserved cannot go negative, which prevents a double-release from corrupting the number in the other direction.

Every stock change also writes a movement row carrying the quantity and value before and after, across four movement types: receipt, issue, transfer and adjustment. That makes a reservation history readable as a sequence of events rather than reconstructed from a current balance, which is the difference between investigating a discrepancy and guessing at one.

Stock, warehouses and fulfilment are available on every plan including the free one, with plan limits on products, warehouses and carrier connections rather than on the invariant. For what happens after the reservation becomes a shipment, and how one order turns into several invoices, see partial shipments and partial invoices.

Frequently asked questions

When should an inventory reservation be created?

At the moment a commitment is made that the business intends to honour, most commonly order confirmation. Reserving earlier, such as at add-to-cart, is defensible for scarce or high-demand items but requires an expiry, because a browsing session is not a commitment. Reserving later, such as at picking, means the order was confirmed against a number that did not account for it.

What releases an inventory reservation?

Four events release a reservation: shipment, which converts the hold into a physical issue; cancellation of the order; expiry of a time-limited hold such as a cart reservation; and manual release by an operator. Every one of those paths must exist in the system. A reservation with no release path becomes a phantom hold on stock that is physically present and unsellable.

What is a phantom reservation?

A phantom reservation is a hold that outlives the commitment it was created for, usually because an order was cancelled, abandoned or lost and the release step never ran. The units remain on the shelf and remain counted as on-hand, but available stays reduced, so the business refuses orders it could fulfil. Phantom reservations fail silently, which is why they are typically discovered during a stock count.

Why do two systems overselling the same stock not get fixed by syncing more often?

Because each system checks availability against its own copy before the other system's reservation has arrived. Two orders taken in the same sync window both pass their local check, and the conflict only becomes visible when the copies are merged, after both customers have been confirmed. Increasing sync frequency shrinks the window without removing it, so the failure becomes rarer and harder to reproduce.

Do reservations need to be per warehouse?

Yes, wherever stock is held in more than one location. A reservation is a claim on physical units, and units in one warehouse cannot satisfy an order dispatching from another. Holding reservations against a total across locations produces orders that look fulfillable and are not, and it makes transfers between locations impossible to plan because the committed quantity per site is unknown.

Written by

The Oneop team

Writing from the team that builds Oneop — an AI customer platform with web chat and email on top of a real CRM, help desk, ledger and warehouse.

Related reading

Start on the free plan and see the real thing

No credit card. Free plan, 2 seats.

Free forever · 2 seats · No credit card