The order shipped. The invoice never came.
In almost every ERP, billing waits on one status: shipped. When a fulfillment quietly slides back to packed, nothing breaks. The box is on a truck, the customer gets it, and the invoice simply never exists.
Billing waits on one word
Every ERP has some version of the same chain. An order is picked, then packed, then shipped. The shipped status is the trigger: it is what tells billing there is something to invoice, what tells the customer service screen the order is done, and what tells the warehouse to stop looking at it.
Now picture that status moving backward. The fulfillment was shipped this morning — label printed, tracking number issued, carrier picked it up. This afternoon it reads packed again. Nothing errors. Nothing alerts. The billing run looks for shipped fulfillments, doesn’t find this one, and moves on. Tomorrow it does the same. The order isn’t late, it isn’t on hold and it isn’t on anybody’s exception report. It has simply fallen out of every queue that would notice it.
The most expensive errors in an ERP are the ones that look like nothing happened.
What it does to the business
- Revenue that never posts. The goods are delivered and the sale never hits the income statement for the period. Month-end looks soft for no visible reason.
- Cash that never arrives. No invoice means no receivable, which means nothing to collect. The customer isn’t late; they were never billed.
- A borrowing base that’s smaller than it should be. Lenders lend against eligible receivables. An invoice that doesn’t exist can’t be borrowed against.
- An awkward customer conversation. When somebody finally finds it weeks later, the invoice lands long after the delivery. Some customers pay without a thought. Others ask why, and some dispute it.
- Inventory that looks wrong. Depending on the system, a reverted fulfillment can leave stock committed or pull it back into the picture, and people start counting shelves that are right.
How it happens
Not usually through anyone doing something wrong on purpose. The common causes are mundane, and that is why they keep happening:
- A stale screen. Someone opened the fulfillment before it shipped, got pulled away, and came back to a page that still showed packing buttons. One click on a button that should no longer exist, and the system honors it.
- Shared logins at stations. Pack stations and shipping benches often share a user, so the audit trail says “station 6” rather than a person, and nobody remembers which tab they had open.
- Integrations doing what they’re told. A shipping system that voids a label is right to un-ship a fulfillment. The same mechanism, triggered by mistake, looks identical.
- Buttons that should not be offered. Some screens still show “mark packed” on a record that is already shipped. The system offers a regression with no confirmation, and nothing on the server checks it.
Training gets you most of the way. Not all of it.
The first answer is always training: tell the warehouse not to do that. It is the right first answer, and it works — mostly. People learn the screens, the obvious mistakes stop, and the error rate drops.
But training has a floor. A trained person on a busy afternoon, at a shared station, with four tabs open, will eventually click a button that the system should never have shown them. You cannot train away a race condition. You can only make it harder to lose.
The more useful question is not “who did this?” but “why could it be done?” When the honest answer is that the system allowed a move that is almost never correct, the fix belongs in the system.
What a safeguard looks like
We built a small guard that sits on the fulfillment record. Its rules are simple enough to explain to the warehouse in one sentence: once something is shipped, the screen won’t let you un-ship it.
- Block the move in the UI. A person trying to move a shipped fulfillment back to packed or picked gets a plain message: this already shipped, possibly after you opened the page; refresh to see where it is.
- Allow it, loudly, everywhere else. Integrations and scheduled processes keep their ability to void and un-ship — that’s sometimes correct — but every one sends a notice to the people who own shipping and billing, so it’s never silent.
- A deliberate override. When a reversal really is needed, a manager can tick a single-use override. It works once, then resets itself, so nobody leaves the door propped open.
- An audit line every time. Every block, override and allowed revert is logged with who, when, which screen path and which status, so the next question takes minutes instead of a week of forensics.
What changed
The silent losses stopped being silent. The warehouse gets a clear message instead of an accidental reversal, the people who own billing hear about every legitimate revert the same day, and the month-end hunt for “why is this order delivered but not invoiced” mostly went away.
None of that required a new system. It needed someone to look at where order-to-cash quietly leaks, decide which leaks a person can reasonably be trusted to avoid, and close the rest in the software.
The build wasn’t as simple as it sounds. The first version missed a whole category of reverts, and the second missed another. That story, and what it teaches about trusting the record you are handed, is in the companion piece.
Related: three ways the first guard missed, and one order, six authorizations.