Skip to content

Batch and expiry tracking

A batched product is grouped into lots — units within a lot are interchangeable, but lots themselves matter. Each batch has a lot number, an optional expiry date, a per-unit cost, and its own remaining quantity. Fexl Lite sells the oldest batch first at the till (FEFO — first-expiry-first-out, layered on top of FIFO costing) and surfaces expiring stock before it becomes unsellable. This is the right mode for medicine, food, cosmetics, lab consumables, dyed fabrics, and anything where a supplier can call you to recall a specific lot.

Updated 5 May 2026·For v2.2.0·5 min read

Setting up

1

Create the product with Tracking = Batched

Open Products → New Product. Set Tracking to Batched. Two extra fields appear: Identifier label (e.g. “Lot #”, “Batch”) and a Track expiry toggle. Leave expiry on for anything with a shelf life; turn it off for products where lot is meaningful but expiry is not (dye batches, paint runs).

2

Restock with a lot number

The Restock dialog requires a lot/batch number for batched products. If Track expiry is on, the expiry date is also required; otherwise it’s optional. Each restock creates one inventory_batches row alongside the FIFO ingress.

3

Stock by category, not by hand

For pharmacies and food retailers receiving 50+ lots a week, the import lots button on the batch tracking page accepts a CSV — one row per (lot, expiry, qty, cost). Faster than the restock dialog for bulk receipts.

inventory · restock · batch lot + expiry fields

What changes at the till

When a cashier adds a batched product to the cart, a batch picker opens listing each available lot with its expiry date and remaining quantity. The picker sorts oldest expiry first so the cashier sells expiring stock before fresh stock by default. The chosen lot’s quantity decrements; the receipt prints lot number and expiry under the line.

If a particular lot is being recalled (supplier defect, contamination), set its status to quarantine from the batch tracking page and it disappears from the picker — sales can’t accidentally draw from it.

Costing — per-batch, not per-FIFO-walk

For batched products the FIFO resolver short-circuits: lineCOGS reads the specific batch’s cost for the units drawn (priority 2 in the chain — see FIFO costing). The general FIFO walk over inventory_transactions.qty_remaining doesn’t apply to batched lines, because the batch IS the layer.

This matters when you’ve got two lots of the same product at different costs sitting on the shelf at once: the COGS posted to the books reflects the specific lot the cashier sold from, not a blended average across the two.

The batch tracking page

Open Inventory → View Tracking on a batched product. Every batch lists with:

  • Batch number — the lot identifier you entered at restock.
  • Statusactive, expired, depleted, or low_stock.
  • Supplier — who delivered this lot.
  • Received / expiry dates — for the alert window.
  • Cost per unit — what posts to COGS when this lot sells.
  • Initial / current qty / sold qty — the running counts.

Filter by status to find every active lot, every expired one, or every one running low. Expired batches are highlighted red across the row.

Expiry alerts

The expiry warning window is configured in Settings → Inventory → Expiry warning days (default: 30). Any batch whose expiry date falls within that window from today raises an expiring soon banner on the inventory page, fires an in-app inventory alert, and routes to Telegram or WhatsApp if those notification channels are wired up.

Distinct from Quantity and Serialized

  • Vs Quantity — a Quantity product doesn’t separate by lot. If you put two deliveries on the same shelf, COGS will smooth across them via WAC (Bug #46 lesson — Quantity-tracked products use WAC for the surfaced “current cost”; FIFO is per-restock-layer underneath but doesn’t surface as a per-lot picker). For FIFO semantics at the UI level — “which lot did this come from?” — use Batch tracking, not Quantity.
  • Vs Serialized — Serialized is one row per unit with a unique identifier. Batched is one row per lot, with units inside the lot being interchangeable. If you can’t tell the units apart but the lots matter, batch is the right shape; if every unit has its own number, serialize.

Bulk write-off

For an expired lot you’ve physically discarded, use Remove Stock → reason: expired on the batch tracking page to write off the remaining quantity. The egress posts to disposal expense (5040) and zeroes the lot’s current_qty so the picker stops showing it. Keeps the audit trail intact instead of silently deleting.