The bugs that cost real money in a business application are rarely the ones that crash the app. Those get fixed — someone notices immediately. The expensive bugs are the quiet ones. They let your inventory double-book a date range, let the same quotation become two orders, let your operations team see revenue numbers they're not supposed to see. Nobody notices for weeks, and by then you're reverse-engineering a mess from emails.
After building multi-branch ERPs for distribution and restaurants, I've run into most of these. Here are five that live in a lot of small-business software — and what to ask before you buy.
1. It double-books inventory when date ranges overlap
The bug: You rent or deploy equipment for events. Two customers want the same item on overlapping but non-identical date ranges — say, April 17–21 and April 21–25. Your software shows both as available because the overlap is only a single day, and that day sits on the boundary of both reservations.
Why it happens: Most calendar-based inventory systems do a simple "does this range overlap with an existing reservation?" check. When the answer is "yes, for one day," they often round it off — especially if start and end are inclusive. On that one shared day, both reservations quietly claim the item, and your software can't tell which one to honour.
How we handle it in the Onsite ERP: The availability calculation does a timeline sweep across every overlapping reservation. For each day in the requested range, it counts the peak concurrent demand. A strict rule handles same-day boundaries: when one reservation ends on the same day another begins, both are treated as using the item that day. The sweep processes start-events before end-events to make sure the peak count is never underreported.
We had a real regression that let boundary-day double-booking slip through for a week. The fix was a single ordering rule in the sweep — and we wrote it down explicitly in our system docs so a future refactor can't accidentally remove it.
What to ask a vendor: "If I reserve this item April 17–21, and another customer reserves it April 21–25, will the system show it as available on April 21?" If the answer is yes, it's double-booking. If the answer is no, ask how the system handles that — they should be able to explain.
2. The same quotation can become two orders
The bug: A salesperson converts a quotation to an order. Something lags — a network hiccup, a second click, two users looking at the same record — and the quotation gets converted twice. Now you have two orders, two inventory reservations, two invoices going out for one job.
Why it happens: A naive "convert" action reads the quotation, creates the order, and marks the quotation converted — in separate steps. Between the read and the mark, anything can interleave. If the quotation doesn't have a conversion flag, there's nothing stopping a second convert from running.
How we handle it: Three fields on every quotation: convertedToOrder (boolean), orderId (string), convertedAt (timestamp). The conversion uses a conditional database write — it only succeeds if convertedToOrder is still false. A second conversion attempt fails on the condition and returns a useful error instead of silently creating a duplicate.
We also freeze converted quotations: they can't be edited or deleted once an order exists. If the salesperson wants to walk back a conversion, they use a revert path that restores the quotation and deletes the order — and it only works before the order reaches certain downstream states, because after that there's already inventory committed and people doing work.
What to ask a vendor: "What happens if I click 'convert to order' twice quickly?" If they say "you'll get two orders," walk away. If they say "the second click is ignored or blocked," ask how — you want to hear the word "conditional" or "atomic."
3. Its inventory counts don't match what's in the warehouse
The bug: Your system says you have 20 units of something available. You walk into the warehouse and count 14. Or 23. Or 20 but three of them are broken. Your quarterly audit reveals a 15% discrepancy across the board.
Why it happens: Inventory "lives" in too many places at once and nobody is the authority. Dispatch gets logged in one system, returns in another, damage writeoffs in a third, and the master count is a running total that drifts every time one of the downstream steps gets skipped or double-counted.
How we handle it: A single physical-inventory table with four states per SKU: available, inUse, inRepair, broken. There's a hard invariant: available + inUse + inRepair + broken = quantity, always. Every move between these states is a single atomic database update — and each one is guarded by a precondition. Move to inUse only if available ≥ qty. Move from inUse only if inUse ≥ qty. The system refuses to do a move that would break the invariant, rather than doing it and leaving the numbers wrong.
Importantly, inventory only moves at two moments: when equipment physically leaves the warehouse (dispatch) and when it comes back and is inspected (quality check). Nothing else touches the counters — not order creation, not approval, not even recording a reservation. Reservations are a separate system that gates availability by date without touching physical stock.
What to ask a vendor: "When inventory counts don't match reality, what's the path back to accurate numbers?" Any answer that involves "manual adjustment by an admin" means the system is leaking stock somewhere. A good answer involves an audit trail of every movement and a way to replay it.
4. Its dashboards show different people different things — in the wrong way
The bug: Your operations manager can see revenue figures they shouldn't. Your sales team can see costs they shouldn't. Your accounting team can see who's been marked for firing. Role-based "permissions" usually mean role-based page access — they hide entire screens. But inside a screen, data flows pretty freely.
Why it happens: Filtering data by role is harder than filtering pages by role. A page-level permission check is one line of code at the top of a route. Field-level filtering is a decision that has to be made in every function that returns data.
How we handle it: Three independent layers of financial-data redaction for the operations role:
- The server actions that return financial data actively throw an error if the caller's role is operations. Can't even ask.
- Dashboard data that can go to operations (product activity, order counts) has revenue fields stripped out before leaving the server.
- Notification messages have two variants — one with amounts, one without — composed based on the recipient's role.
If a developer accidentally adds a new path that exposes revenue to operations, one of those three layers catches it. It's deliberately redundant.
What to ask a vendor: "Can you show me the operations dashboard, logged in as an operations user?" If you see any ₹ or $ figures anywhere — individual order values, dashboard totals, low-stock cost impact — the system is leaking. Fixing that after the fact is rarely a one-line change.
5. It can't tell you who approved what, when
The bug: A customer disputes an order six months after the fact. They claim they never approved the advance. You try to prove otherwise. Your software has a "status" column that says "approved" but no record of who approved, when, or what the order looked like when they did. You're reconstructing the history from Slack messages and email timestamps.
Why it happens: Most software stores current state, not history. "Approved" is a flag on the order, maybe with a timestamp of the last status change. But if the order was edited after approval, or if there were multiple approvals in sequence, none of that is visible in the current-state view.
How we handle it: Every order has a timeline array — an append-only log of every status-changing event, with the actor, timestamp, and a human-readable action. "Created by Rahul at 9:04 AM." "Admin approved by Priya at 10:12 AM." "Accounts approved by Meera at 10:47 AM." "Payment received ₹2 lakh at 3:22 PM." The timeline is how the UI renders the order's history to every role, and it's how disputes get resolved — there's no ambiguity because the record is first-class, not inferred.
We also separate approval concerns: admin approval for orders above a threshold value, accounts approval for any advance payment. Both can run concurrently, both are captured as independent events, and the order doesn't move forward until both are complete. When either approval triggers, it's written with a conditional update so a second write can't silently overwrite the first.
What to ask a vendor: "If I need to prove exactly who approved this order and when, six months from now, what do I click?" The answer should be a single screen. Not a support ticket. Not an audit log export. A visible timeline, available to everyone on the order.
The meta-question
Each of these bugs has a common ancestor: the software was built to make a demo look good, not to survive real operations. Demos don't stress overlapping date ranges. Demos don't click "convert" twice. Demos don't have an operations team that shouldn't see revenue. The software passes the demo and ships, and two years into daily use you're staring at a reconciliation spreadsheet.
When you're evaluating software, run the operational tests, not the demo. Try to break it on purpose. The systems that survive those tests are the ones worth paying for.
If you want to talk about a specific operational problem you're running into — double-booking, duplicate orders, audit gaps, dashboards leaking data — I'm always up for that conversation. I've fixed most of these in real systems, and I can tell you pretty quickly whether yours is fixable in place or whether you need something new.