# 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.

- **What it is:** Pharmacy retail on Odoo Community stock: dispense by batch with expiry checked, refuse what may not be handed over, controlled drugs register.
- **For:** Industries
- **Odoo versions:** 16.0, 17.0, 18.0, 19.0, 20.0 - Community and Enterprise
- **Runs on:** Odoo.sh or Odoo you host yourself - not Odoo Online, which accepts no third-party apps
- **Price:** 149 EUR, one-time, on the Odoo Apps Store
- **Technical name:** bambooforge_pharmacy
- **Not for:** Said before you buy, because it is the half that decides whether the app fits:; Dispense a controlled item that is not lot-tracked, or the same controlled script twice.; Let a lot-tracked medicine leave without the box that left the shelf.; Rewrite a dispensing record once handed over - on the server, not merely in the form.
- **Manual:** https://docs.bambooforge.dev/bambooforge_pharmacy/
- **Live demo:** https://demo20.bambooforge.dev/odoo/action-bambooforge_pharmacy.action_bf_pharmacy_dispense
- **Store page:** https://apps.odoo.com/apps/modules/20.0/bambooforge_pharmacy
- **Manual last updated:** 2026-10-15

---
# 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, 19.0 or 20.0 with stock, product_expiry and
account. No Enterprise module.

## How we make sure it works

Before every release, **121 automated checks** run against this app on Odoo 16, 17, 18, 19 and 20.
Among them:

- The same controlled script cannot be presented twice.
- The watch does not raise the same batch twice.
- A drug with a register behind it cannot be de scheduled.
- A counter user cannot hand over a schedule four item.
- A counter user cannot hand over a schedule two item.

The complete list is in the source you receive.

## What it does not do

Said before you buy, because it is the half that decides whether the app fits:

- Dispense a controlled item that is not lot-tracked, or the same controlled script twice.
- Let a lot-tracked medicine leave without the box that left the shelf.
- Rewrite a dispensing record once handed over - on the server, not merely in the form.

## Install

1. Copy `bambooforge_pharmacy` into your addons path and update the app list, or install the ZIP from the Odoo Apps Store.
2. Install **Pharmacy Management & Retail POS**.
3. Open **Pharmacy** - its menus are Dispensing, Stock, Controlled drugs register.

## Questions people ask

****Which Odoo versions does it run on?****

Odoo 16.0, 17.0, 18.0, 19.0, 20.0, on the series you buy it for; each series is its own listing.

****Community or Enterprise?****

Both. It needs no Enterprise app.

****What does it need?****

The standard Inventory, Invoicing, product_expiry apps. No extra Python package.

****Where does it show up?****

Under the **Pharmacy** menu (Dispensing, Stock, Controlled drugs register).

****Does anything leave my server?****

No. It makes no call outside your Odoo server: no telemetry, no licence check, no third-party service.

****What happens if I uninstall it?****

Odoo drops the app's own tables and fields; the standard records it worked on (contacts, orders, products) stay as they are.

## Support

[support@bambooforge.dev](mailto:support@bambooforge.dev) - answered within two business days.
OPL-1; one purchase covers one production database plus its staging
copies.
