
Pharmacy Batch and Expiry: The Part Generic Stock Systems Get Wrong

Generic inventory systems treat stock as fungible. A unit is a unit. That assumption works fine for selling chairs or spare parts. In pharmacy, it quietly causes write-offs, dispensing errors, and regulatory exposure — and the bugs only surface months after go-live.
This post covers exactly where generic stock logic breaks, what a purpose-built pharmacy module needs to handle, and the specific trade-offs you'll face when designing or evaluating one.
Why Batch Tracking in Pharmacy Is Structurally Different From General Inventory
Most inventory systems track quantity at the SKU or location level. Pharmacy requires quantity tracking at the batch level, because two packs of Amoxicillin 500mg from different manufacturers, with different expiry dates and different cold-chain histories, are not interchangeable from a compliance standpoint.
The WHO Model Quality Assurance System and most national drug authority frameworks (CDSCO in India, MHRA in the UK, FDA in the US) require that every dispensed unit be traceable back to a specific batch number and manufacturer lot. This is not optional record-keeping. It is the audit trail that makes a product recall executable in under 24 hours.
Generic systems can store a batch number as a text field against a product. That is not batch tracking. Real batch tracking means:
- Stock is received, held, and depleted at the batch level, not just the SKU level
- FIFO or FEFO (first-expiry-first-out) picking is enforced by the system, not left to the person at the counter
- A batch can be quarantined without removing the product from the catalogue
- Recall workflows can identify every transaction that touched a given batch
The last point is where most off-the-shelf ERP systems fall short. They can tell you how many units of a product you sold. They often cannot tell you which specific batch was in a specific sale without manual cross-referencing.
What Does FEFO Actually Require at the Data Model Level?
FIFO is simple: sell the oldest stock first. FEFO adds a constraint: sell the stock that expires soonest first, regardless of when it arrived. For a pharmacy, FEFO is almost always the correct policy because a batch received last week with a six-month expiry should be picked before a batch received three months ago with a nine-month expiry.
Implementing FEFO correctly requires the following:
- Each stock entry (a "batch lot line") carries its own expiry date, quantity, batch number, and supplier reference
- When a dispense or sale transaction is initiated, the system queries open batch lot lines ordered by expiry date ascending, then allocates quantity from the earliest-expiring batch first
- Partial allocation is supported: if a batch has 30 units and you need 50, the system takes 30 from the first batch and 20 from the next
This sounds straightforward. The complexity is in edge cases: what happens when two batches have the same expiry date? What is the tiebreaker? What happens when a batch is under quarantine? Does the system skip it silently, or does it alert the dispenser? What happens when a batch has fewer than the minimum dispensing quantity remaining?
These are not hypothetical. They come up in live systems. If the data model does not account for batch status (available, quarantined, recalled, expired, reserved), you will patch around these cases with application logic that becomes increasingly fragile.
Expiry Threshold Alerts
Expiry management is not just about refusing to dispense expired stock. It is about giving the pharmacy enough lead time to return near-expiry stock to the distributor (typically 3 months before expiry is the return window, though this varies by supplier agreement) or to discount it before it becomes a write-off.
A configurable alert threshold per product category is the minimum viable feature. A fast-moving antibiotic might warrant a 30-day alert. A slow-moving oncology drug sitting in a hospital pharmacy might need a 90-day alert. The threshold should be set at the product or category level, not system-wide.
/// Not sure where to start?
Get the architecture before you commit
Tell us what you're building and we'll map the technical approach, stack, and rough timeline. No cost, no obligation, no sales call required.
How Do Returns and Adjustments Break Batch Integrity?
Returns are where most batch implementations break down. A customer returns 10 tablets. Which batch were they from? If the system does not capture the batch number at the point of sale, there is no way to restore stock to the correct batch lot line. The units get added back to the most recent batch, or to a generic "returned stock" bucket, and the audit trail is gone.
The correct approach is to capture batch number at point of sale, and to allow returns to reference the original transaction. The return flow should credit the specific batch lot line that was debited in the original transaction. This requires the transaction ledger to carry batch-level references, not just product-level references.
Stock adjustments are a similar problem. A physical count finds 95 units where the system shows 100. The adjustment needs to specify which batch the 5-unit discrepancy comes from. If your adjustment screen only lets you adjust at the product level, you will have correct total quantities with incorrect per-batch quantities, and your FEFO logic will silently misbehave.
Can a Generic ERP Be Extended to Handle This Correctly?
Sometimes. The honest answer depends on what the ERP's data model looks like at the inventory transaction level.
| Capability | Generic ERP (e.g. Odoo, SAP Business One) | Purpose-built pharmacy module |
|---|---|---|
| Batch number storage | Text field on product or lot | First-class entity with status, expiry, supplier lot |
| FEFO enforcement | Usually a configuration option, varies by module | Core picking logic, enforced at transaction level |
| Batch-level returns | Rarely supported out of the box | Linked to originating transaction |
| Quarantine workflow | Manual workaround or third-party module | Built-in status with dispense blocking |
| Recall trace | Requires custom report | Standard audit query |
| Expiry alerts | Generic notification rules | Per-category thresholds with escalation |
Odoo's stock module (v16 and v17) does support lot/serial number tracking and expiry dates, and FEFO can be enabled per product. It covers the baseline. Where it falls short is in pharmacy-specific workflows: partial batch returns referencing original transactions, quarantine blocking at point of sale, and regulatory report formats for CDSCO or state drug controller submissions. These require custom development on top of the standard module.
SAP Business One has similar characteristics. The batch management module is capable, but the pharmacy-specific compliance layer is typically built by implementation partners rather than included in the base product.
The trade-off is straightforward: if you are a mid-size pharmacy chain that already runs on Odoo and your compliance requirements are standard, extending Odoo is likely faster and cheaper than building from scratch. If you are running a hospital pharmacy with controlled substances, cold-chain drugs, and regulatory submissions, a purpose-built system or a deep customisation project is the more defensible path.
A Short Note on Controlled Substances
Controlled substances (scheduled drugs under the NDPS Act in India, Schedule II-V in the US) add a layer beyond standard batch tracking. Every transaction, including partial quantities, must be logged with prescriber details, patient details, and in some jurisdictions, a running balance register. The register must balance to zero discrepancy at any point-in-time audit.
This is genuinely difficult to retrofit onto a generic inventory system. The transaction model needs to be designed for it from the start.
Conclusion
The gap between "we store batch numbers" and "we have batch integrity" is significant, and it only becomes visible under pressure: during a recall, during a drug authority inspection, or when a dispenser unknowingly reaches for an expired batch because the system failed to enforce FEFO.
If you are evaluating a pharmacy stock system, ask to see how the system handles a partial batch return, a quarantine event mid-transaction, and a recall trace report. Those three scenarios will tell you more than any feature checklist. If you are building one, design the batch lot line as a first-class entity from day one. Retrofitting it later is expensive and error-prone.
If you are working through this architecture and want a technical review of your data model or workflow design, reach out to the team at Sodio.
FAQ
Does FEFO dispensing require barcode scanning at point of sale? Not strictly, but it helps significantly. Without scanning, the system relies on the dispenser manually confirming the batch being dispensed. With scanning, the system can verify that the physical pack matches the batch the system allocated. In high-volume pharmacy environments, manual confirmation introduces meaningful error rates.
How should a system handle stock with multiple expiry dates within the same batch number? This should not happen under GMP rules — a single manufacturer batch number should have a single expiry date. If it does occur, it usually indicates a data entry error at goods receipt. The system should flag this as an exception and require manual resolution rather than silently accepting duplicate expiry dates for the same batch number.
What is the minimum data to capture at goods receipt for compliant batch tracking? At minimum: batch number, manufacturer name, manufacturing date, expiry date, quantity received, supplier invoice reference, and date of receipt. For cold-chain products, you should also capture the temperature log reference from the delivery. Some state drug controllers in India additionally require the drug licence number of the supplier on the inward record.
Can batch tracking work without a full WMS? Yes. For most pharmacy operations, batch tracking at the transaction level (purchase order, sales/dispense, return, adjustment) is sufficient without a full warehouse management system. WMS-level features like bin locations and directed putaway add value in large hospital pharmacy or distribution centre contexts, but they are not prerequisites for compliant batch integrity.
How do you handle a product recall when only some batches are affected? The system should allow you to enter the recalled batch numbers and generate a list of all transactions (sales, transfers, consignments) that touched those batches, along with patient or customer references where captured. Stock of the recalled batches should be automatically quarantined. The report output should be exportable in a format the drug authority accepts, which in India typically means a structured Excel or PDF with specific column requirements.
Have a project in mind? Contact Sodio Technologies to discuss your requirements and explore the right technology solution for your business.
/// Work with us
Talk to the engineers who'd build it
You'll get a technical scope, timeline and cost estimate from the people doing the work, not an account manager. In-house team, no subcontracting, since 2016.
