BFBambooForge Labs

Pharmacy Management & Retail POS

Pharmacy retail on Odoo Community stock: dispense by batch with expiry checked, refuse what may not be handed over, controlled drugs register.

Buy on the Odoo Apps StoreOpen the live demoIndustries€290Community & Enterprise

Available for Odoo 16.0, Odoo 17.0, Odoo 18.0, Odoo 19.0. Technical name bambooforge_pharmacy.

Odoo 16.0Odoo 17.0Odoo 18.0Odoo 19.0
Full walkthrough on a live Odoo 19.0 database, with subtitles. It ends with what this app deliberately does not do.

Pharmacy Management & Retail POS

What it is

A retail pharmacy on Odoo Community, built around the three things that get a pharmacy fined or sued: the batch, the expiry, and the register.

Medicines are products with rules

Active ingredient (what a pharmacist substitutes on), strength, form, ATC code, prescription-only, controlled schedule, maximum days supply, cold chain, and the counselling note the pharmacist has to say out loud. A controlled item that is not lot-tracked is refused outright - a register that cannot name the batch is not a register.

Dispensing checks before it hands over

  • An expired batch is refused, with the expiry date in the message, read in the shop's own timezone - a box printed "30/09" is saleable all day on the 30th, wherever the shop is.

  • A lot-tracked medicine cannot leave without naming the box that left the shelf; otherwise a recall cannot be answered.

  • Prescription-only needs the prescriber on the record.

  • A controlled item needs the prescriber's licence number and a checked ID before anything is handed over.

  • More days than the medicine allows is refused.

  • The same controlled script cannot be dispensed twice: the prescription number and the prescriber's licence are compared with what has already been supplied.

  • A dispensing record cannot be rewritten once handed over: it is what an inspector reads. Nor can its items, nor its state - the buttons are the only way through, on the server and not merely in the form.

  • Only a pharmacist may hand over anything with a schedule on it, Schedule II to V, and that is checked on the server rather than by hiding a button.

The controlled drugs register

Written automatically, one entry per controlled item, with the batch, the quantity, a running balance, who supplied it, to whom and on whose authority. Append-only: entries cannot be edited or deleted - and they cannot be typed either. No role holds create rights on the register; the counter writes it, and the running balance is derived from the line above rather than accepted from whoever is asking. A return writes a receipt line, and a destruction writes its own entry with the witness who watched it.

Receipts, destructions and corrections go in through the same register. A wholesaler delivery writes itself the moment the transfer is validated - read off the locations the stock actually travelled between, so a return to the wholesaler on the same incoming operation type is booked out rather than in, and moving a box from the shelf to the cabinet is not counted as a second delivery. Everything else is recorded through a wizard that asks the questions the book has to answer: what happened, to which batch, how much, and - for a destruction - who watched it. Nothing goes round the register: the wizard calls the same elevated writer the counter does, and the balance is still derived under the same lock.

A pharmacy going live already has stock in the cabinet, so the book can be opened from what is on the shelf: one opening correction per controlled medicine, equal to the quantity really in the company's custody, quarantine included. It shows the numbers before they are signed for, it refuses to run twice, and a medicine that already has entries is left alone. Without it the balance starts at zero and the first hand-over prints a negative number that can never be edited away.

Who may write which line: a receipt is the pharmacist's, because it is a physical fact at goods-in and making it wait for the manager means either the book waits or a login is shared. A destruction and a correction are the manager's - they are the two entries that can make a discrepancy disappear.

Medicine that comes back

A return is recorded through a wizard that asks the questions a return has to answer: why it came back, how much of it (one of three boxes is a return), and where it goes. It never goes back on the saleable shelf - it goes into a quarantine location the module creates, out of the availability check and out of the FEFO suggestion - or it is destroyed in front of a named witness, which writes both a stock scrap and a register entry. A posted invoice is credited with a linked, posted, reconciled credit note; a draft one is cancelled outright.

Stock, expiry and money

  • Dispensing creates the delivery with the right lot and takes it out of stock; the customer invoice follows the handover, never the other way round.

  • Lots carry days to expiry and a shelf-life state, and the suggested batch is the one expiring first (FEFO) - a pharmacy that picks the newest box writes off stock every quarter. Expired, quarantined and other companies' batches are never suggested.

  • A daily job raises what is about to go out of date while it can still be returned to the wholesaler, and addresses the work to a named, active pharmacy manager for that company - never to OdooBot.

Roles

The counter prepares a dispensing and sells over-the-counter items. Only a pharmacist can hand over a controlled drug. The manager configures and can delete a draft - and only the manager can set the control regime on a product: the schedule, prescription-only and the maximum days supply are manager-only fields with a tracked history, so re-importing the product master from a supplier file cannot quietly de-schedule morphine.

Requirements

Odoo Community 16.0, 17.0, 18.0 or 19.0 with stock, product_expiry and account. No Enterprise module.

Support

support@bambooforge.dev - answered within two business days. OPL-1; one purchase covers one production database plus its staging copies.

Screens

Dispense - bambooforge_pharmacy
Dispense
Expiry - bambooforge_pharmacy
Expiry
Register movement - bambooforge_pharmacy
Register movement
Register open - bambooforge_pharmacy
Register open
Register - bambooforge_pharmacy
Register