Skip to content

Ninety-six pieces the system refused to pick

A wholesale order needed two pieces. The pallet held ninety-six of them, stacked as four cases, and the warehouse refused the pick. The bug was one comparison written in the wrong unit.

A wholesale order came in for two pieces. The system had located the stock, named the bin, named the pallet, and put it in front of the picker. Confirm the pick and it refused:

Pick quantity exceeds available quantity on source pallet.

The pallet held ninety-six pieces.

They were stacked as four cases of twenty-four, which is how the goods had arrived and how they had been put away. The screen even said so: 96 Pcs · 4 mc. The picker was standing in front of ninety-six bottles being told there were not two.

One comparison, written in the wrong unit

The check that refused it lives in the code that deducts stock from a located pallet line, and it reads, in effect:

refuse if the line’s loose pieces are fewer than the pieces requested, or if the line’s cases are fewer than the cases requested

Read it against the actual data. The pallet line held cases = 4, pieces = 0, because nobody had opened a case yet. The request was pieces = 2, cases = 0. So: are the line’s loose pieces (zero) fewer than the two requested? Yes. Refuse.

The comparison is correct in its own terms and wrong about the world. It treats loose pieces and cased pieces as different inventory rather than the same goods in different packaging. The ninety-six on the screen was a derived number, cases times the conversion factor, and the check never looked at it.

The consumer floor already knew

Here is the part that makes it a design story instead of a typo.

The direct-to-consumer floor had solved this months earlier. A consumer order for a handful of units against a case-stacked pallet is routine, so that path had a small primitive: before deducting, work out whether the loose pieces cover the request, and if they do not, break open as many cases as needed. Four cases of twenty-four become three cases and twenty-four loose. Then deduct.

That primitive was sitting in the codebase, tested, working, and named for the floor it was written on. The wholesale pick path never called it. The guard around it read if this is an ecommerce order.

So the capability existed and the question was who was allowed to use it. That is a recurring shape: a thing gets built for the situation where it is first needed, the situation gets written into the condition, and years later the condition is the only thing standing between a second floor and code that would have worked fine.

The blast radius

This is not exotic data. Stock that enters through an opening inventory load, the import every warehouse runs on day one with an existing catalogue, is entered as whole cases, because that is how it is stacked on the pallet.

On the rehearsal tenant that meant 4,562 pallets in that state. Any wholesale order for loose pieces against any of them would have failed, with an error message telling the operator that stock they were looking at did not exist. It surfaced during a routine test of a different feature, which is the only reason it was found before a real warehouse met it.

The fix, and the two things it must not do

The primitive is now channel-neutral and both wholesale confirm paths call it, under one condition: break when pieces are requested. The plan itself returns nothing when the loose pieces already cover the pick, so a pure case pick never reaches it, and a mixed order of two cases plus five pieces breaks exactly the one case it needs and no more.

Two constraints were worth being stubborn about.

It writes no stock movement. The pallet holds ninety-six pieces before the break and ninety-six after; only the arrangement changed. Nothing entered or left the building. A ledger that records the same goods moving because their packaging changed is a ledger that fills with events meaning nothing, and the first thing to die in that noise is the ability to spot a real one. A test pins the invariant: ninety-six before, ninety-six after the break, ninety-four after the two-piece pick.

It must not be able to invent stock. The second test replays the ledger against the stored numbers after a piece pick from a case-only pallet and asserts zero discrepancies. We had just spent a week cataloguing ninety products whose numbers the ledger could not explain. A sprint that rewrites pallet arithmetic had to prove it was not adding a ninety-first.

What it looked like on the floor

Same order, after the change. Two pieces from a pallet holding three cases and no loose pieces. The picker scans the bin, scans the product, confirms.

The pallet reads 70 Pcs · 2 mc + 22 loose. One case opened, two pieces gone, everything accounted for, and no movement row invented for the opening of a box.

The ninety-six were always there. The system just had to be taught to see them.

Questions

Why store cases and pieces separately at all?
Because a warehouse counts both, and they are not interchangeable on the floor. A pallet of four sealed cases is a different physical object from ninety-six loose bottles, even though the totals match. The mistake is not storing both; it is comparing one against the other.
Does breaking a case write a stock movement?
No, and it should not. The pallet holds the same number of pieces before and after. Four cases of twenty-four, or three cases and twenty-four loose, is ninety-six either way. Nothing entered or left the building, so there is nothing for the ledger to record. Only the shape of what is on the pallet changed.
Where did the case-only pallets come from?
An opening inventory load. Stock that arrives when you first set up a system is typically entered as whole cases, because that is how it is stacked. Any warehouse importing an existing catalogue starts in exactly that state, which is what made this worth fixing before a real one did.

Keep reading

See how DockingWare handles this →