One Step Back, One Step Forward
European food law asks a business to name who supplied a batch and who received it. Our schema answers both, and has since long before anyone asked — with two caveats that decide whether the answer arrives in an hour or not at all: the tracking is off by default, and deleting a batch leaves its movements pointing at nothing.
A recall is a question with a clock attached. Somebody names a batch and asks two things: where did this come from, and where has it gone. Every hour the second answer takes is an hour of product still moving. The interesting part, for anyone choosing a system, is that both questions are answered by records created months earlier by people who were not thinking about recalls at all.
This is the one post in this corpus where our own answer is mostly yes, and we have written it carefully for that reason. A vendor describing a strength is the least trustworthy thing a vendor writes. The two paragraphs that cost us are near the end and they are the reason to keep reading.
The obligation, in the regulation's own words
European food law states the principle first: "The traceability of food, feed, food-producing animals, and any other substance intended to be, or expected to be, incorporated into a food or feed shall be established at all stages of production, processing and distribution."
Then it splits it in two. Operators must be able to identify who supplied them, and they must be able to identify the businesses they supplied — and in both cases they must "have in place systems and procedures which allow for this information to be made available to the competent authorities on demand." The Commission's shorthand for the pair is one step back, one step forward.
One step, not the whole chain
The requirement is narrower than people expect and that narrowness is the design insight. You are not asked to trace a product from field to fork. You are asked to know your immediate supplier and your immediate customer, because if every operator in the chain can do that, the chain reconstructs itself one link at a time. It is a distributed system with no central register — which is precisely why the obligation lands on your own records rather than on somebody else's database.
Two limits on what we are saying. Whether you are a food or feed business operator, and what your particular sector adds on top, is not a question this post answers — some categories carry their own implementing rules with additional particulars. And the obligation is for systems and procedures, which is a larger thing than software: a schema can hold the links, and it cannot make somebody pick up the phone.
What the question actually asks of a schema
Strip the law out and it is a data-modelling problem with three parts, and most systems fail the second or the third rather than the first.
What a stock system usually has
- A quantity per item per location, moved up and down by transactions.
- A purchase history that says how much of an item was bought and from whom.
- A sales history that says how much went out and to whom.
- A cost layer, often FIFO, that looks like batching and is an accounting device rather than a physical one.
What the question needs instead
- A batch as a thing, with its own identity, distinct from the quantity of the item.
- That batch linked to the specific supplier and the specific purchase document that brought it in.
- Every outbound movement of that batch linked to the specific document that took it out.
- The whole of it queryable in both directions from the batch, not reconstructable by inference from dates.
The third line is where cost-layer FIFO stops being enough. A FIFO layer tells you what a unit cost. It does not tell you which physical batch left the building on which delivery note, and a recall does not care what anything cost.
Our own schema, stated exactly
A batch in our product is its own record, not a label on a quantity. It carries a batch number and a lot number as separate fields — they are not always the same thing, and a manufacturer's lot is frequently not the reference you received it under. Against that batch sit the dates that matter: when it was received, when it was manufactured, and when it expires.
For the step back, the batch carries the supplier and the purchase order directly. Not derived, not inferred from the nearest receipt by date — stored on the batch as identifiers, so the answer to "where did this come from" is a field lookup rather than a reconstruction.
For the step forward there is a separate allocation ledger, and its shape is the part worth explaining. Each row records a batch, a quantity, a direction, a movement type, and then a polymorphic reference to the document that caused it — a type and an identifier for the source, and a second pair for the specific line on that source. It is indexed from the batch and from the source, so the traversal runs in both directions at speed.
| The question | How it is answered | What kind of answer that is |
|---|---|---|
| Who supplied this batch? | Supplier and purchase order stored on the batch | A field |
| When did it arrive, and when was it made? | Received and manufactured dates on the batch | A field |
| Where has it gone? | Allocation rows filtered by batch, each naming its source document and line | An indexed query |
| What else came in on the same purchase order? | Batches filtered by purchase order | An indexed query |
| Is this batch already flagged? | A recall status and a quality status on the batch itself | A field |
There is also a quality status that begins life as unreviewed rather than as approved, which is a small decision with a large consequence: a batch that nobody has looked at is distinguishable from one that somebody has passed. Defaulting that to approved would have been the friendlier choice and the wrong one.
Alongside the batch model there are serial numbers as their own records, an expiry-driven picking policy so the earliest-expiring stock is the stock that leaves first, and per-item switches for which of these regimes apply.
The two things that cost us
Everything above is real and none of it is the reason to be careful. These two are.
It is off by default, and it is set per item
Lot tracking, serial tracking and expiry tracking are three separate switches on each item, and all three arrive off. The item's tracking mode defaults to plain quantity. Nothing about that is unreasonable — most stock in most businesses does not need a batch identity, and forcing it on everything would make every receipt slower for no gain.
The consequence is what matters. Traceability in our product is not a property of the organization, it is a property of each item, decided one at a time by whoever set that item up. So the honest description is not "we support traceability". It is: we will answer the recall question for exactly the items somebody remembered to switch on, and there is no report that tells you which those are, no warning when a category is half-covered, and no moment at which the system says the set looks incomplete.
For a business where this obligation bites, that turns a schema question into an implementation question. The schema is ready. The configuration is a decision somebody has to make deliberately, item by item, and get right without help.
A deleted batch leaves its movements pointing at nothing
The allocation ledger's link to the batch is nullable, and the foreign key is set to null the batch reference when the batch is deleted rather than to prevent the deletion.
The effect is worth stating slowly, because it is not data loss in the usual sense. The movement rows survive. The quantities survive. The links to the source documents survive. What disappears is the one field that says which batch those movements were about — so the forward history remains, complete and detailed, and is no longer attached to the thing anybody would search by. A count of rows is unchanged. The chain is severed.
A nullable batch on an allocation is legitimate on its own terms: allocations exist for items with no batch tracking, and those rows must be allowed to hold nothing. What makes this the sharper case is that the same nullable column carries two meanings — this item never had a batch, and this item had one that is gone — and no query can tell them apart.
How to test any of this, ours included
Take a batch you received three months ago. Show me its supplier and the purchase document, on the batch record.
What you will hear
Both, immediately, without opening a purchase report.
How to read it
If the answer is found by matching dates or quantities, it is a reconstruction. Reconstructions are fine until two deliveries of the same item arrive in one week.
Now show me every despatch that batch went out on, and the customer on each.
What you will hear
A list, from the batch, in one step.
How to read it
This is the step that is usually missing. Ask for it from the batch — not from a date range, which is the answer people give when the link does not exist.
Which of my items have batch tracking switched on right now?
What you will hear
A list, and a straight answer about whether the setting is per item or global.
How to read it
A per-item setting with no coverage report is a system whose traceability is as complete as somebody's memory. Ours is exactly this.
Delete a test batch that has movements against it. What happens to the movements?
What you will hear
Either the deletion is refused, or the movements keep their identity.
How to read it
Ask this of every vendor and do it in a test environment. It is the fastest way to find out whether traceability is a schema or a screen.
Does the batch have a status that distinguishes unreviewed from approved?
What you will hear
Yes, with unreviewed as the starting state.
How to read it
A default of approved means every batch is passed before anyone looks at it, and the field is decorative.
What AWRA OpsHub does today
- Batches as first-class records with batch number and lot number held separately, plus received, manufactured and expiry dates.
- Supplier and purchase order stored on the batch — the step back, as a field rather than an inference.
- An allocation ledger recording each batch movement against the source document and the specific line that caused it, indexed in both directions — the step forward.
- Quality status defaulting to unreviewed, and a recall status, both on the batch.
- Serial numbers as their own records, and an expiry-driven picking policy.
What it does not do
- Any organization-wide traceability setting. It is three booleans per item, all defaulting to off, with no coverage report.
- Protection of a batch that has movements against it — deletion nulls the reference on the surviving allocation rows.
- Any distinction, in a query, between an allocation that never had a batch and one whose batch was deleted.
- Any sector-specific particulars beyond the general model — food of animal origin and similar categories carry their own requirements we do not implement.
- Notification to an authority, a customer or anyone else. The records answer the question; sending the answer is yours.
Not ours, by choice
- Nothing here is legal or food-safety advice, and this post does not say whether the regulation applies to you or what your sector adds to it.
- The obligation is for systems and procedures. We are an input to the first and none of the second.
- We have no reference customer in Europe.
A coverage report naming the items where tracking is off, so the gap is visible before a recall rather than during one. Refusing the deletion of a batch that has movements against it, or preserving its identity on those movements when it goes. An organization-level default so new items inherit the regime rather than starting bare. None of the three is a schema change, which is unusual for this corpus and is rather the point — the model is already right, and what is missing sits on top of it. Sector-specific particulars beyond the general model are a real build for a named sector and we would scope and price that separately, on the usual terms: a written specification, a timeline and a price agreed before anything starts.
The two admissions above are the reason this post is publishable at all. A vendor describing a strength is the least trustworthy thing a vendor writes, and we would rather hand you the two questions that would have caught us.
Our take
The step back and the step forward are both stored, in fields and in an indexed ledger, and that is more than a stock system usually has. Whether they will answer on the day depends on a decision made months earlier by whoever created the item, and on nobody having deleted a batch in the meantime. If this obligation is live for you, the questions to ask are not about capability — they are about defaults and about deletion, and they are the two we have just answered against ourselves.
If a recall question would land on your stock records
Send us the shape of what you handle and we will tell you which parts of the above are configuration and which are engineering. If the honest answer is that the coverage report has to exist first, we will say so.
See plans & pricing