Batch & Expiry

Recording batch numbers and expiry dates, which batch sells first, the expired-stock warning at the counter, items that must have an expiry date, the expiry alert window, and pharmacy details.

16 min read · Updated 28 Oct 2026

Batch and expiry: sell it before it expires · 0:39 · Watch on YouTube
On this page

If you sell anything perishable, you want to know what is about to go off before your customers do. Kassly lets you tag incoming stock with a batch number and an expiry date. It then keeps track of how much of each batch is left, sells the usable stock first, and tells you what is close to its date or already past it.

What Needs an add-on?
Recording a batch number and expiry when stock arrives No. Free on every plan.
Using up batches as stock leaves, usable stock first No. It happens for every batch you record.
Require expiry date on stock-in on an item No
The expired-stock warning on the POS No
The Expiry alerts window in Inventory Settings, and the daily expiry notice No
Pharmacy Details on a product No
The Batches card on a product, and the Expiry Alerts page on the web, phone and desktop Yes — Inventory Pro
Breaking a box into pieces with a Decomposition Yes — Recipe Costing

Seeing batch balances and the Expiry Alerts page needs the Inventory Pro add-on (₱500 per branch each month), or any bundle that includes it. Recording batches, and keeping their balances up to date, does not. If your add-on lapses and you renew later, the balances are still correct.

If you never enter a batch number or expiry date, nothing on this page affects you. Your sales work exactly as before.

Recording a batch

Batch details are captured when stock comes in.

Where Batch fields
Stock Adjustment → Stock In (add inventory), at a branch or a warehouse Batch Number, Manufacture Date, Expiry Date
Bulk Stock-In, both the table and the Excel template Batch number and expiry per row
Receiving a purchase order on the web Batch # and Expiry Date per line
Completing a commissary production run Expiry and manufacture date of the output
The Kassly app on a phone or tablet: Products → Stock → the item → Stock In, and receiving a purchase order Lot / batch no. and Expiry date. Needs app version 1.33.0 or later.
Kassly Desktop: Stock In, Bulk Stock-In and receiving a purchase order Batch number and expiry date. Receiving needs version 1.16.0 or later.
Field Notes
Batch Number Up to 100 characters. Free text — Kassly does not generate it. Spaces at either end are ignored.
Manufacture Date Recorded and shown on the product's Batches card.
Expiry Date Must not be earlier than the manufacture date. This is the field alerts key off. Required for items marked Require expiry date on stock-in (see below).

On the phone app, the Expiry date field takes either:

  • a full date, year first: 2026-11-11 (or 2026/11/11), or
  • the month, the way most medicine packs print it: 11/2026, 11/26, 2026-11, NOV 2026 or November 2026. A month on its own means the last day of that month, so 11/2026 is 30 November 2026.

An EXP in front, as on the pack, is ignored. A day-first date such as 11/11/2026 is not read; the app asks you to "Enter the expiry date as YYYY-MM-DD, or the month as MM/YYYY."

A batch is one batch number and expiry date for one product (and variant) at one branch or warehouse. Receiving the same batch number and expiry again at the same place tops the batch up. A delivery with an expiry date but no batch number is still tracked as a batch; it just shows — for the number.

Stock received without a batch number or an expiry date is unbatched. It counts toward on-hand like any other stock but never belongs to a batch.

Items that must have an expiry date

For medicines and food, you can make Kassly refuse any delivery of an item that comes in without its expiry date. Every unit then lands in a dated batch and shows up on the expiry alerts.

  1. Open the product (Catalog → Products → the product → edit).
  2. Under Options, make sure Track Stock is on.
  3. Turn on Require expiry date on stock-in and save.

The switch only appears when Track Stock is on, and Kassly refuses it without Track Stock: "Turn on Track Stock to require an expiry date." If your kind of business is Pharmacy / Botika, it is already on for every new product you add.

From then on, every stock-in of that item needs an expiry date:

Where Without an expiry date
Stock In, at a branch or a warehouse Refused. The Expiry Date field is marked required: "Amoxicillin 500mg needs an expiry date. Enter the date printed on the pack."
Bulk Stock-In table Refused, with the item named. Nothing on the list is stocked in until it is fixed.
Bulk Stock-In Excel sheet, and a warehouse's Import opening stock sheet The whole upload is refused and the row is listed. Fill in the date and upload again.
Receiving a purchase order The receipt is refused, with the item named. Nothing on it is received until the date is filled in.

Kassly checks this itself, so it applies whichever screen or app you use. The Kassly app (1.33.0 or later) also flags the missing date on its Stock In and receiving screens before sending, and so does Kassly Desktop (1.16.0 or later) on Bulk Stock-In and receiving.

It does not apply to stock that leaves and comes back: removals, stock counts, transfers, voids and returns are not affected. A count that finds more than expected still adds the extra as unbatched (see Stock counts below). Turning the switch on does not change stock you already have. Anything on hand without a date stays unbatched.

Which batch goes first

Every time stock leaves a location, Kassly takes it out of that location's batches in first expiry, first out (FEFO) order:

  1. the batch with the earliest expiry date goes first,
  2. batches with no expiry date go last,
  3. between batches with the same expiry, the one received first goes first.

What differs is how expired batches are treated. That depends on why the stock is leaving:

Stock leaving as Which batches it takes
A sale, including recipe ingredients, modifiers linked to a stock item and package components Usable batches first, in FEFO order. Expired batches only once every usable batch is empty.
A transfer when it ships Usable batches first
Breaking a box into pieces Usable batches first
Parts used on a job order, and a commissary production run consuming its inputs Usable batches first
An Adjustment (removal) Expired batches first, plain FEFO
A stock count that comes in lower than the system figure Expired batches first
A return to the supplier The lot you picked first, then plain FEFO. See Supplier returns.

The reason: nobody hands a customer an expired box on purpose. When a sale goes through, the fresh stock is what actually left the shelf, so that is what Kassly takes. The expired batch stays on the Expiry Alerts page until you remove it. A removal with the reason Expired, on the other hand, is exactly the expired stock, so it takes the expired batches first.

A batch is expired once your store's date is past its expiry date. The expiry date itself still counts as usable: a batch that expires on 15 March 2027 sells as usable stock all of 15 March and is expired from 16 March.

"Your store's date" is the calendar date in the Philippines, changing at midnight. A Business Day Start after midnight does not move it. Everything that counts days goes by it: which batch a sale takes, the expired-stock warning at the counter, the product's Batches card and the Expiry Alerts page. At 7:30 in the morning of 16 March, that batch already shows Expired 1d ago everywhere.

Kassly records which batches each movement drew from, so putting stock back returns it to the same batch:

  • Voiding a sale puts the units back into the batches the sale took them from.
  • A resellable return goes back into its batch. Returning the same sale in several goes never puts more into a batch than the sale took out of it. Damaged returns stay out of stock, as before.
  • A transfer carries its batches: when it is received, the destination gets the same batch numbers and expiry dates. Because a transfer takes usable stock first, expired batches stay behind at the source. Cancelling a shipped transfer puts the batches back at the source.

There is no batch picker at the till, and Kassly does not tell the cashier which physical unit to hand over. Keep the shelf in the same order Kassly uses: earliest expiry at the front, and expired stock off the shelf.

Breaking a box keeps its batch

When a box is broken into pieces with a Decomposition, the pieces get the batch number and expiry date of the box they came from. A box of 100 tablets from batch A, expiring March 2027, becomes 100 loose tablets of batch A expiring March 2027, so the loose stock stays on the expiry alerts.

This applies whether you break the box yourself or Kassly does it during a sale, for a decomposition set to Auto-decompose on sale. If the boxes came from more than one batch, the pieces are shared out between those batches in proportion. Pieces from a box that was not in any batch are unbatched.

Decompositions need the Recipe Costing add-on. See Ingredients and recipes.

Writing off expired stock

Record the write-off as an Adjustment (reason Expired). A removal takes expired batches first, so the write-off comes out of them, and they drop off the Expired list once they are empty.

Selling more than a batch holds

Kassly never blocks a sale on stock (see Stock levels). When a sale needs more than the batches hold, it empties them and the rest of the sale is unbatched. A batch never goes below zero.

A later delivery does not go back and cover the part that was oversold. The new batch keeps everything that arrived with it. So after an oversell the batches can add up to more than on-hand for a while. The next stock count corrects that (see below). The oversold alert already asks you to recount.

Stock counts

A count is the truth:

  • Counted lower than the system figure: the difference comes out of the batches, expired first, like any removal.
  • Counted higher: the extra is unbatched. Nobody knows which batch it came from, so Kassly does not guess.
  • After any count, if the batches add up to more than the counted quantity, Kassly trims them, earliest expiry first, until they fit.

To put found stock into a specific batch, record it as a Stock In with that batch number and expiry instead of counting it up.

Expired stock at the counter

The POS asks before you sell an item whose stock left on record is expired. There is no selling screen in the browser, so this is on the Kassly app (version 1.33.0 or later) and Kassly Desktop (version 1.16.0 or later).

When adding an item would have to come out of expired stock, the POS asks first:

Only expired stock left

"Only expired stock of Amoxicillin 500mg is left on record (3 expired, expiry date Mar 15, 2027). Check the shelf for a newer pack before selling."

The date is the expiry date of the oldest expired batch, as printed on the pack, so the cashier can find that pack on the shelf. It was the batch's last usable day; the stock expired the day after.

Choose Cancel to leave it out, or Sell anyway. The warning never blocks the sale: the shelf may hold a fresh delivery nobody recorded, and the cashier can see it.

How the POS decides: the usable stock is the branch's on-hand minus its expired units. The question comes up when what is already in the cart plus what you are adding is more than that. It works the same whether you tap the item, scan it or pick a variant or modifier.

The app learns which stock has expired each time it syncs with Kassly, so the warning also works offline, from the last sync.

The Batches card

Open a product (Products → the product). If it has batches with stock left, a Batches card lists them, soonest expiry first:

Column What it shows
Batch The batch number (and variant, if any)
Location The branch or warehouse holding it
Expiry The date and a status badge
Remaining What is left of the batch
Received How much of this batch was received here in total

The status badge is the same as on the Expiry Alerts page (see the table below), counted from your store's date, so the card and the page always agree.

Under the table, a line such as "5 on hand at Makati not in a batch" shows stock at that location that is not in any batch.

The Expiry Alerts page

Open Inventory → Expiry Alerts. Only batches that still have stock left are listed, so a batch disappears as soon as it is sold, removed or transferred away. An expired batch stays listed until you remove it, since sales take usable stock first.

Counter What it counts
Expired Batches Batches past their date that still have stock
Expired Units Left The quantity left in those batches
Expiring Soon Batches due within the window, from today
Critical (7d) Of those, the ones due within a week

Below that, a View toggle between Expiring and Expired, a Days Ahead box and a search across product, SKU and batch number. Days Ahead opens on your store's expiry alert window (see below), and you can change it for this visit, from 1 to 365 days.

The table shows Product, Batch, Location, Remaining, Expiry Date and a status badge:

Days until expiry Badge
Already past Expired nd ago, red
0 Expires today, red
1–7 nd left, red
8–30 nd left, amber
More than 30 nd left, plain

Location is a branch or a warehouse — batches are reported from both. See Warehouses.

On the phone and desktop apps

App Where Notes
Kassly app Oversight → Expiry alerts Expired and expiring batches at your branch, plus warehouses, within your store's window. Needs Inventory Pro and the View Stock Levels & Inventory permission. Online only.
Kassly Desktop Expiry in the sidebar Opens on your store's window. The window picker offers 7 to 180 days and your store's own setting. Needs Inventory Pro.

The expiry alert window

How far ahead Kassly warns you is a store setting. Open Settings → Inventory Settings and find Expiry alerts.

Setting Default Range
Warn this many days ahead 30 days 1 to 365 days, whole days

The window sets:

  • where the Days Ahead box on the Expiry Alerts page starts,
  • the expiring batches on Reports → Inventory Summary,
  • Oversight → Expiry alerts in the Kassly app, and Expiry in Kassly Desktop,
  • the daily expiry notice.

Pharmacies often use 90 to 180 days, to leave time to return stock to the supplier before it expires.

Owners and Managers can see the setting. Saving it needs the Edit Store, Receipt & Inventory Settings permission, which by default means the Owner. See Inventory settings.

The daily expiry notice

Every morning at 08:00 Manila time Kassly checks every store that holds stock with an expiry date. If there is something to report, it sends one notification to every Owner and Manager:

What it found Notification
Expired batches still in stock Expired stock on hand: "2 expired batches still in stock. Pull them off the shelf and record the removal."
Batches expiring within your window Expiry Alert: "3 product batches expiring within 30 days."
Both One notification titled Expired stock on hand, with both sentences

It is counted fresh each morning, so a batch you have sold out of or written off stops being counted the next day. An expired batch keeps appearing every morning until you remove it. See Notification settings.

Pharmacy details

A pharmacy needs more than a brand name to find a drug. The product form has a Pharmacy Details card:

Field Notes
Generic Name Up to 255 characters, such as Paracetamol. Searchable, so typing it finds every brand.
Strength Up to 50 characters, such as 500 mg.
Dosage Form Up to 50 characters. Suggests Tablet, Capsule, Syrup and other common forms, but takes any text.
FDA Registration No. (CPR) Up to 50 characters, such as DR-XY12345.
Prescription only (Rx) Shows an Rx badge on the item.

The card always shows if your kind of business is Pharmacy / Botika. Other stores see it only on an item that already has pharmacy details.

Where the details show:

Where What you see
The product page on the web Rx · Prescription only and Expiry required on stock-in badges, Generic Name, Strength / Form and FDA Registration No.
The product search in Catalog → Products Matches the generic name
The POS on the Kassly app (1.33.0 or later) and Kassly Desktop (1.16.0 or later) The generic name and strength under the brand name, and an Rx tag. The POS search matches the generic name.

Rx is a label, not a control. Kassly does not ask for a prescription, record one, or stop a sale of an Rx item. Checking the prescription is up to the pharmacist at the counter.

Pharmacy details are entered on the product form. The product import sheet has no columns for them.

Batches from before this feature

Before batches were used up, Kassly only knew what each batch had received. When batch balances were switched on, Kassly worked out a starting balance for every existing batch: each location's current on-hand was given to its most recently received batches first, and older batches were treated as used up. The batches never add up to more than on-hand, and anything left over is unbatched.

If that does not match your shelf, do a stock count, or write off the batches that are really gone with an Adjustment.

Limits

  • No batch picker. You cannot choose which batch a sale or removal takes from the screens; the rules above decide. Only a supplier return lets you pick the lot.
  • Manufacture date is informational. Nothing sorts or alerts on it.
  • The expired-stock warning is advisory. It never stops a sale, and it is only as fresh as the POS's last sync.
  • Older apps. The Kassly app before 1.33.0 has no expiry field on its Stock In sheet or when receiving a purchase order, and no expired-stock warning. Kassly Desktop before 1.16.0 has neither the warning nor batch fields when receiving. Update the app if you track expiry.

Where to go next