Status: scoping note — decisions 1, 2 and 4 settled 2026-10-06; the build spec is drafted; no build authorised yet. Written 2026-10-06 at Matthew's steer — "it needs to be
within the scope of an operating pharmacy — that is how we should treat this matter" — as the test for
deciding what counter and till work is in scope. It reads a Gemini design exchange (kept at
Counter Til) against the live counter build,
and where the sketch and the code disagree, the code wins. Build facts checked in
/root/work/pharmacy_booking_app (pharmacy_counter, C1–C13, accepted 2026-09-14) on 2026-10-06.
A counter or till feature is in scope only if an operating pharmacy needs it to trade. Three ranks:
Anything a pharmacy never does at a counter is out, however good the sketch looked. The sketch was drawn
without sight of the codebase, so much of it describes things that already exist — at another screen,
under a ratified name.
| A counter must… | Already built |
|---|---|
| take a basket of OTC items | Basket / BasketLine, VAT band per line, priced from ERPNext's price list (basket.html) |
| collect the NHS prescription charge | AddNHSChargeView against a dispensing token — opaque token reference only (D4.4) |
| open and close a till | TillSession: opening float, counted cash, variance reported, never smoothed (till.html) |
| know the day's takings | services.takings_for — by tender method and VAT band, read from the submitted invoices, not computed here |
| take the money | one Tender per sale with change; cash or card recorded from the standalone terminal, no card data ever (D4.1/D4.3) |
| give a receipt | printed through the browser and emailable (D4.7) |
| refund or void | Refund with reason and actor — RP or Business Manager only (D4.5) |
| move the stock | consumed through Phase 3's service with the batch, returned to its batch on a refund |
| record a patient's contact preference or opt-out | already Phase 5 — pharmacy_notifications Preference with opt_out, opt_out_at, opt_out_reason |
| reconcile the money to the books | already Phase 6 — pharmacy_accounting: expected reimbursement, unmatched receipts, period-end pack |
The counter takes ERPNext's price_list_rate as net (services.price_for_item) and the running
estimate adds the standard rate on top (services.estimate_totals); ERPNext then adds the VAT when the
document posts. A shelf-priced £2.50 standard-rated item is charged £3.00 — an overcharge on every
standard-rated line, and the one product decision left open by Phase 4's verification (recorded on
index and
Phase 4).
Recommendation (unchanged): settle that counter prices are VAT-inclusive as displayed to the customer —
mechanism OpenCode's choice, with the one hard constraint that the invoice's tax arithmetic stays
ERPNext's.
Parked 2026-09-16 at Matthew's direction ("for now we'll keep everything as it is"). Every act already
carries its own operator — Basket.operator, Tender.operator, Refund.actor,
TillSession.opened_by/closed_by, the audit actor — so attribution is not the problem. What was unsettled
was whether a till session is per operator or per counter, and whether the counter is a shared
terminal or a device each. It decides the till's shape, so it had to come before any till redesign.
Matthew's ruling, 2026-10-06: per operator — the individual using the till, on their own login. So a
session is opened by a named operator with their own float, closed by that operator with their own cash-up
count, and its takings and variance are theirs. This does not require overlapping sessions: the code
already enforces a single open session per client database (TillSession.Meta.constraints —
one_open_till_session, a partial unique index on status='OPEN', deliberately a database constraint and
not clean(), which cannot survive concurrent requests), and services.open_till refuses a second. A
handover therefore becomes close then open: the outgoing operator counts the drawer and closes, the
incoming operator opens with the counted figure as their float — the natural handover count on a single
physical drawer. Two operators open at once would mean two drawers and two terminals, which is multi-till
and stays out of v1. What changes in the build: TillSession.current() becomes the operator's open
session, opening while another operator holds the till refuses naming them and pointing at handover,
and the till and takings screens address "your session".
An unwatched basket keeps its OPEN status and appears in the dashboard's open-basket list, but there is
no park control, no label and no queue order. On a busy counter, "park this, serve the next, come back"
has to be one visible act.
A barcode is matched inside the search (forms.AddLineForm — item_name/item_code contains, barcode
exact), so a scanned item narrows a dropdown and the operator picks it. Workable, but two acts per item
where a counter expects one.
receipt.html prints the lines, net/VAT/total, tender, change and the free-text receipt_footer;
CounterSettings holds only nhs_prescription_charge, counter_customer_name and receipt_footer. There
is no premises name and address, no VAT registration number and no GPhC premises number. What is actually
required — HMRC for a retail VAT receipt, GPhC for what must be displayed — must be checked before we
assert either, then decided.
Float in, counted cash, variance out. Nothing records the cash taken out of the drawer to the safe or
banked, which is how a trading day actually ends.
D4.3 settled one tender per sale (2026-09-14, "all as recommended") and Tender.basket is a
OneToOneField; Tender.Method is cash or card. Matthew told Gemini a split would be useful, so this is a
scope change to decide, not a defect.
Tender.Method is CASH / CARD only. If the client takes other money at the counter (an account sale, a
transfer), say so; if not, leave it.
Preference / opt-out) and is not a till control.| # | Decision | Outcome |
|---|---|---|
| 1 | Counter prices VAT-inclusive as displayed | SETTLED 2026-10-06 — yes. Closes Phase 4's last finding. The invoice's tax arithmetic stays ERPNext's; the mechanism (an inclusive price list, or the inclusive figure computed from ERPNext's own tax detail) is OpenCode's choice |
| 2 | Till session per operator or per counter | SETTLED 2026-10-06 — per operator, sequential (close then open at handover) — see §2 above |
| 3 | Split tender in or out | Recommended out — D4.3 stands unless Matthew says the client's trade needs it |
| 4 | Receipt identity fields (premises, VAT number, GPhC premises number) | CHECKED 2026-10-06 against HMRC VAT Notice 700 §16.6.1/16.6.2. A retailer must be able to give a VAT invoice when a taxable person asks, and a retailers' VAT invoice (£250 or less) must carry the name, address and VAT registration number, the time of supply, a description identifying the goods, and for each VAT rate the total payable including VAT and the rate; VAT inside an inclusive price is the VAT fraction (rate ÷ (100 + rate)). No source was found requiring the GPhC premises number on a receipt — display-only config, not asserted |
The build brief is drafted: The Counter Views — Inclusive Prices, the Operator's Till, and the Counter at
Work (2026-10-06) — decisions 1 and 2 as
settled, split tender out on D4.3, decision 4 resolved above. Awaiting Matthew's read before it goes to
OpenCode. No code, config or host was changed by this note.