← All postsEngineering

How to stop overselling when your sales team works offline

iotoms team · September 9, 2026 · 5 min read

A snack distributor ran four vans plus a walk-in counter at the depot, all drawing from one batch of a seasonal item their vendor only shipped twice a year — thirty cases, no more coming until spring. The morning it arrived, every rep loaded a share onto their van's stock count, the counter kept the rest, and the day went the way a good sales day is supposed to go: reps sold, the counter sold, everyone was offline half the time because that was normal for this depot's coverage area.

By early afternoon, the office had thirty-four cases sold against thirty on hand. Four cases didn't exist. Two customers had been promised delivery of a product that was already gone, one of them a store that had specifically asked for it because a competitor's brand had gone out of stock. Someone had to call and explain that the order they'd confirmed that morning wasn't actually coming.

Why this isn't the duplicate-order problem

It's tempting to file this under the same bucket as duplicate syncs — a network problem, a retry problem. It isn't. Nobody's phone retried anything. Four different reps, on four different vans, each looked at their own device, saw stock available, and sold it. Every sale was real, entered once, synced once. The problem was that "stock available" was a number each phone had last pulled down hours earlier, and none of them knew what the other three had done with theirs since.

This is the actual tradeoff of offline-first: a rep who can't sell without a live connection isn't offline-first, she's just broken until the signal comes back. So the app has to let her sell against a number that's already stale the moment she sees it. The question isn't how to make the number perfectly current — you can't, not without giving up offline capability entirely — it's what happens when four stale numbers get reconciled against each other later and don't add up.

Where it actually breaks: last-write-wins on a quantity

Most sync systems, built for records like a customer's address or a rep's notes, use a simple rule for conflicts: whichever change reaches the server last wins, and the earlier one is discarded. That's fine for an address. It's quietly wrong for a stock count, because a stock count isn't a fact about the world that one device can just overwrite — it's an accumulation of everyone's activity.

If van A syncs "12 cases remaining" and van B syncs "9 cases remaining" ninety seconds later, last-write-wins keeps 9 and throws away van A's update entirely. It's not that 9 is wrong on its own — it's that the system now has no idea whether van A's three cases sold are reflected in that 9 or not. Depending on sync order, the same real-world sales produce a different final number every time, and there's no way to tell after the fact which version is missing whose sales.

The fix: sync the change, not the number

The fix is to stop syncing the quantity and start syncing the transaction that changed it. Instead of a device saying "stock is now 9," it says "I sold 3 units, at this time, against this batch." That's a delta, and deltas from different devices don't conflict the way snapshots do — they just add up. Van A's "-3" and van B's "-6" and the counter's "-4" all merge cleanly regardless of what order they arrive in, because addition doesn't care about order. The server applies every delta it receives and lands on the true total sold, every time, instead of guessing which snapshot to keep.

That solves the bookkeeping half of the problem — the running total stays correct no matter how sync order shuffles. It doesn't, on its own, stop four people from selling against a number that was already too optimistic by the time the fourth one looked at it. That half has to be handled differently, because you can't lock inventory across four vans that aren't all online at once — pessimistic locking assumes you can tell everyone else to wait, and offline devices can't be told anything until they resync.

So the system has to allow the oversell to happen, catch it the moment enough deltas land to reveal it, and hand it to a person instead of hiding it. The moment the running total goes negative — more sold than was ever on hand — that's flagged immediately, tied to the specific orders that pushed it past zero, sorted by when each sale actually happened. The earliest sale keeps its stock. The ones after it get flagged for a human decision: call the customer, substitute a comparable item, or push the order to the next batch. What the system must never do is silently let the last invoice printed declare itself the truth and let the earlier three vanish from the count.

What to check if you're evaluating this in a field sales or POS system

  • Ask what happens when two offline devices sell the last unit of the same SKU before either syncs — does the system detect it, or does one sale just quietly disappear from the count?
  • Ask whether stock sync sends deltas (units sold) or snapshots (total remaining). Snapshots lose information on every conflict; deltas don't.
  • Check whether an oversold item surfaces as a task for someone to resolve, with the actual orders attached, or whether it just shows up as a stock discrepancy days later with no trace of which sales caused it.
  • Test it directly: put two devices offline, sell against the same low-stock item on both, bring them back online together, and see what the resulting count and any alerts actually look like.
  • For high-turnover or limited-allocation items, ask if there's a way to flag them for tighter reconciliation — checking in more often — rather than treating every SKU the same way.

iotoms syncs stock as transactions rather than totals, so an oversell shows up as a specific set of orders competing for the same units — timestamped, attributed to the van or counter that made the sale — instead of a stock count that's simply wrong with no explanation of why.

See your own routes running on iotoms

Book a demo