Stock said 600. Four hundred of them expired on the shelf.
Batch numbers and expiry dates are created in the warehouse and stay there. Orders, ERP records and customer emails all run on the product code, which is why the one question that matters after a recall has no answer.
The number was right. It was also useless.
A distributor of laboratory reagents spent last autumn looking at a healthy stock line. Six hundred units of one test kit, well above the reorder point, so purchasing left it alone. In November about four hundred of those units passed their expiry date on the shelf and went into a write-off.
Six hundred was the real count. It was a single total, though, and what decided the outcome was how that total split across batches with different expiry dates. The warehouse system had the split. The ERP had a number. The reorder rule read the number.
Two months later a supplier recalled one batch of a different product, and the same company ran into the other half of the same problem. They could list every customer who had bought that product. They could not name one who had received that batch.
A product code is not a shipment
The product code says what you sell. The batch says what actually left the building: one production run with its own expiry date and its own release paperwork. Serial numbers hang off it for anything under warranty, UDIs for medical devices, best-before dates for anything consumable.
Integrations model the first layer only, and there is a fair reason for that. When the order is placed, the batch does not exist yet as far as the order is concerned. Product and quantity is all an order can carry. The batch gets decided later, on the warehouse floor, when someone pulls a specific box off a specific shelf.
In most setups the chain stops right there. The warehouse system knows. The ERP has an order line, the CRM has a customer, and every automation that builds confirmations, invoices and shipping mail is working off the order line. Query a product, get an answer in seconds. Query a batch, get a phone call to the warehouse.
Where it drops out
A GS1 barcode on the outer case holds four things: product code, batch, expiry, quantity. The integration takes the product code and the quantity, because those are the two the destination requires. The other two are optional, and optional in a first mapping means gone.
Often it is gone before that, at goods receipt. Someone scans the pallet, the warehouse system records batch and expiry, and the posting into the ERP carries a summary: product, quantity, delivery note reference. What sits in stock afterwards is a number with no origin, and every automation built on top of it inherits that gap.
Then there is the case no data model plans for. A single order line rarely comes out of a single batch. Eighty units from one, forty from another. There is one field per line, so either the last scan wins or somebody concatenates both into a text field that a person can read and no query can use.
Expiry dates fail on format. The label says 0926. The record ends up holding "0926", or "09/26", or "26-09" depending on the scanner. A rule like "do not ship with under six months remaining" cannot run against that, so nobody writes it.
Returns are the quiet one. Return handling is almost always a later and thinner process than outbound. Goods go back on the shelf, no batch gets recorded, and from then on there is stock in the building whose origin nobody can reconstruct.
Why acceptance testing never catches it
The test case is the easy case. Create an order, pick one line from one location, print the note. One batch, one expiry, everything lines up.
The cases that break need states nobody sets up on purpose: a remainder too small to cover the line, a return from an older batch, a supplier who changed case size in March. Nobody tests a recall at all, because it reads like a disaster scenario rather than a requirement.
The pipeline stays green through all of it. A missing batch is not an error, it is an empty field, and empty fields do not stop a run. The cost lands the first time somebody asks a question backwards, and that is usually months after go-live.
The recall is the rare version
Most of the damage happens without anyone using the word recall.
Contract terms are the common one. Hospital and lab buyers routinely require a minimum remaining shelf life, often two thirds of the total, written into the framework agreement. An automation that only counts stock will happily ship units that are technically fine and contractually a rejection. The credit note arrives a fortnight later.
Then there is the customer's own paperwork. A hospital has to record which batch of an implantable product went into which patient. If your delivery note carries no machine-readable batch, someone on their side retypes it by hand. That is a reason to change supplier, and it will never show up in your sales figures.
What we build
The batch belongs on the shipment line, not the order. The order stays what it is, product and quantity. The allocation happens at picking and lives on the shipment. Two levels instead of one, and the lower one is the level that counts.
Capture it where it is created, which means receiving rather than dispatch. Teams that plan to fill the batch in later at the outbound step never do, because by then the pallet is broken down and nobody has the time. Two seconds at put-away, or a weekend of reconstruction afterwards.
A shipment line has to be able to carry several batches: a list of batch, quantity and expiry. If the destination cannot hold a list, split the line at picking. A comma inside a text field fixes nothing, it moves the work to recall day.
Expiry goes into a date field, not a string. Normalise the barcode format at scan time, ISO, one field. A month-only value like 0926 becomes the last day of that month, because that is what it means. After that, a shelf-life rule is one line of logic instead of a project.
On batch-managed products, nothing ships without a batch. That is a hard check before the delivery note prints, not a warning in a log. The product master already knows which products are batch-managed. If the batch is missing, the shipment stops and somebody gets a task.
Traceability has to work as a query in both directions: from a batch to every customer who received it, and from a customer to every batch that reached them. If either direction needs a phone call, what you have is paperwork.
Ten minutes to find out
Pick one batch-managed product and one shipment from last quarter. Without calling the warehouse, answer three questions: which batch went out, what its expiry date was, and which other customers got units from the same batch.
Most companies get the first answer and stall on the third. That gap does not sit in the warehouse. It sits in the mapping between the scanner and everything downstream, and it costs a lot less to close before a recall than during one.