This is the worklist behind the pharmacy guides: the everyday tasks that take place in a pharmacy, from
a user's point of view, with every role represented. Each numbered item becomes one guide page, and the same
page is then followed through by the reviewer, logging in as that role and working the steps for real.
How we work through it (agreed 2026-09-15):
This list is living: items are added, split or refined as we go, and it is expected to change.
Status key — Published: the page exists. Planned: not yet written. Partly covered: some of it
appears inside another page. Blocked: cannot be tested until something is put right first.
Testing works by logging in as the role. Every test user below lives on the client tenant, which is
the platform's test model because it is a real SIAAS deployment — its own database on the client's own
box, its own Keycloak realm, its own tunnel.
Log in at https://test-client-pharmacy.pharmacy.veripath.co.uk/ — start there every time. The app
redirects to the Keycloak login with that tenant's realm already selected and returns you to the dashboard
afterwards. Do not bookmark or hand-type the Keycloak URL: it carries a one-time state and nonce.
| Username | Role | Name shown in the app | What it opens |
|---|---|---|---|
tc.rp.one |
Responsible Pharmacist | Testford Pharmington | groups A, B, C, E, F |
tc.rp.two |
Responsible Pharmacist | Synthetta Chemsworth | the second pharmacist — for hand-over, and to see the one-on-duty refusal |
tc.test.user |
Responsible Pharmacist | Test User | group A's history — the tenant's existing RP log rows name this pharmacist |
tc.bm.one |
Business Manager | Bess Marginwell | group G, and the staff user-management screen |
tc.ca.one |
Counter Assistant | Cass Tillworth | group D |
tc.tech.one |
Dispensing Technician | Tess Trayborne | the Technician's own view of group B and the dispensary screens |
All four roles are live, so no item on this plan is blocked on a missing login. What each role can and
cannot reach is in Roles and Permissions.
Passwords are deliberately not published here. The credential is held host-local on the platform —
never in the wiki and never in git — a decision already taken for the pharmacy tenant. Ask the platform
owner for it directly. These are synthetic accounts with no patient data behind them.
One host to ignore:
pharmacy.veripath.co.uk, the "apex". It is a platform-owned tenant with no client
instance behind it, so it does not operate as SIAAS, and it is to be retired (decision 2026-09-16 —
see the apex tenant). Itsdemo.*identities are not
part of this plan, no guide is written or tested against it, and a login from one host is unknown at the
other.
A pharmacy cannot lawfully operate without a Responsible Pharmacist on duty at the premises, so this is
where the operational day starts and ends.
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| A1 | Sign on duty as the Responsible Pharmacist at the start of a shift | Pharmacist | Published — Responsible Pharmacist — Sign On & Sign Off |
| A2 | Hand over and sign off duty at the end of a shift | Pharmacist | Published — same page |
| A3 | Check who is on duty now, and who was on duty on a given day | Any staff member (read only) | Published — same page |
Group A is complete: all three items live on one page, illustrated with three figures —
Responsible Pharmacist — Sign On & Sign Off.
Marked finished by the reviewer 2026-09-16; group B is the next part.
Test as: tc.rp.one for the sign-on, then tc.rp.two to see the refusal and take the hand-over.
The core of the pharmacy's work: a prescription arrives from the spine and is worked through until it is
ready for the patient. Two roles share it, with deliberate hand-offs.
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| B1 | Claim a prescription that has arrived from the spine | Pharmacist, Technician | Published — Dispensing — Claim a Prescription |
| B2 | Assemble the prescription | Pharmacist, Technician | Published — Dispensing — Assemble a Prescription |
| B3 | Perform the accuracy check | Pharmacist only | Published — Dispensing — The Accuracy Check |
| B4 | Dispense — record the clinical act | Pharmacist only | Published — Dispensing — Dispense a Prescription |
| B5 | Endorse — declare the charge and record the reimbursement claim | Pharmacist only | Published — Dispensing — Endorse a Prescription (finished 2026-10-02) |
| B6 | Release or cancel a token (not collected here / prescription cancelled) | Pharmacist, Technician | Published — Dispensing — Release or Cancel a Claim |
| B7 | Look back over dispensing history | Pharmacist, Technician | Published — Dispensing — Dispensing History |
Test as: tc.rp.one for every step. tc.tech.one repeats B1, B2, B6 and B7 to prove the Technician's own
view — B3, B4 and B5 are Pharmacist-only and should refuse a Technician.
Group B is complete — B1–B7 are all published (Claim a Prescription,
Assemble a Prescription,
The Accuracy Check,
Dispense a Prescription,
Endorse a Prescription,
Release or Cancel a Claim,
Dispensing History). B7 was taken out of order, because the
history screen is what the reviewer uses to see the effect of the other steps. The tenant already holds a
prescription parked in every state, so the group can be walked end to end without seeding anything:
| Token | State | Patient (synthetic) | Good for |
|---|---|---|---|
demo-token-available-1 |
Claimed (was claimable at EPS) | Testpatient Alfa | was B1's claimable token — claimed 2026-09-17, so it now sits in the same state as demo-token-claimed-1 |
demo-token-available-2 |
Available to claim | Testpatient Lima | B1 — the claimable prescription (2 items, acute) |
demo-token-available-3 |
Available to claim | Testpatient Mike | B1 — a single-item repeat issue, so the Items column differs from the row above |
demo-token-available-4 |
Available to claim | Testpatient November | B1 again, as the second dispensing role (tc.tech.one) — claiming consumes a token, so keep a spare |
demo-token-claimed-1 |
Claimed | Testpatient Bravo | B2 — assemble |
demo-token-assembled-1 |
Assembled | Testpatient Charlie | B3 — accuracy check |
demo-token-checked-1 |
Accuracy checked | Testpatient Delta | B4 — dispense |
demo-token-dispensed-1 |
Dispensed | Testpatient Echo | B5 — endorse |
demo-token-cancelled-1 |
Cancelled | Testpatient Golf | showing a withdrawn prescription |
B5 — two corrections pending (2026-09-19, both have since landed — see the entry below). The page itself was
corrected on 2026-09-19 and is accurate to
the app as it runs today; two changes wait on other work. (1) The endorsement reference box is being removed from
the app — it was a free-text input with no NHSBSA counterpart, and it is not a reference the pharmacy is required
to obtain (see Regulatory Content Audit). Until the
removal is deployed, treat the box as ignorable and leave it empty; when it goes, this guide loses the passage
entirely. (2) The declaration wording follows the NHSBSA coded exemption values once that change is built — the
dispense screen (guide B4) is where that shows. When both land, B5 gets a final pass and its figure a re-shoot, and
is then finished.
B5 finished (2026-10-02, session 52) — both corrections have landed, and neither page needed rewording. The two
builds were done on 2026-09-19 (0a55eea: the NHSBSA EPS exemption/charge-paid codes; 5b4e135: the endorsement
reference dropped) and were deployed before this session. Checked against the running app, not the log:
B5 already carries the note that older records may still show a reference, and the endorse step is a button with
nothing to fill in (/dispensary/<pk>/ renders one submit control and no reference input), so the passage the
pending note anticipated removing is already gone; B4 already describes the coded exemption list, and this session
added one line naming the list's first entry — "— no exemption (charge due) —" (states.py:
('', '— no exemption (charge due) —')), which is the blank value, since a reader choosing from a dropdown is not
"leaving it blank". Three figures re-shot live as tc.rp.one and byte-verified — b4-dispense-form.png (asset
147), b4-dispensed-token.png (148) and b5-dispensed-token.png (150) — because they were captured before
the 19 September changes. b5-endorsed-token.png was left as it is, deliberately: the endorsements it shows were
recorded before the field was removed, and B5 explains that an older record may still carry a reference.
Fixtures checked 2026-09-19: three prescriptions are available to claim (demo-token-available-2/3/4, seeded 2026-09-19 through the app's own mock-EPS path, synthetic identifiers only) so B1 can be walked as often as it needs to be — claiming consumes one, and the queue then shows the rest. demo-token-available-1 was claimed on 2026-09-17 and now sits in the same state as demo-token-claimed-1, so two tokens are Claimed and an assemble can be walked repeatedly. The rest of the states above are as the table says.
Nothing else was touched: no existing token, state or history row was reset, released or re-claimed when the new fixtures were added. The claimable list is read from the spine each time the queue is opened, so the queue figure on guide B1 shows exactly these three rows.
Expect the stock warning on a dispense (checked 2026-09-18): the items on every seeded token carry no
SNOMED code, and the client tenant has ERPNext stock configured — so dispensing demo-token-checked-1 will
move no stock and the prescription will show the amber "Dispensed without a stock movement for at least one
item" line. That is the app behaving as designed (a stock problem never blocks a dispense), not a fault; guide
B4 documents it.
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| C1 | Receive a delivery (goods-in) | Pharmacist, Technician, Business Manager | Published — Stock — Receive a Delivery |
| C2 | Check stock levels and the low-stock report | All four roles (read) | Published — Stock — Check Stock Levels |
| C3 | Run the expiry check | All four roles (read) | Published — Stock — Run the Expiry Check |
| C4 | Record an adjustment or wastage | Pharmacist, Technician | Published — Stock — Record an Adjustment or Wastage |
| C5 | Sync stock positions with the ledger | Pharmacist, Technician, Business Manager | Published — Stock — Sync Stock Positions |
| C6 | Order stock from a supplier and export the order file | Pharmacist, Technician, Business Manager | Published — Stock — Order Stock and Export the Order File |
| C7 | Manage the pharmacy's supplier list | All four roles (read); Pharmacist, Technician, Business Manager (add/edit/deactivate) | Published — Stock — Manage the Supplier List |
| C8 | Set up and run the order satellite on the pharmacy PC | Business Manager (set up); Pharmacist, Technician, Business Manager (run it) | Published (2026-09-29) — Stock — Set Up and Run the Order Satellite |
C1 is published (2026-09-19). Written against the app as it now stands, including the goods-in receipt page
and the safe retry for a failed ERPNext posting. Test as tc.tech.one. The tenant already holds one receipt —
DEMO-GIN-0001, Submitted, ERPNext entry MAT-STE-2026-00008, four lines — which is the worked example in the
guide; recording a new delivery posts a real (synthetic) Stock Entry into the client's ERPNext ledger, so walk it
with a delivery the pharmacy actually wants recorded. One limitation is documented in the guide: the app cannot
record a problem with a delivery (a short, damaged or rejected item), so that has to be handled outside it.
C2 corrected (2026-10-02, session 52). Two small drifts, both closed. The page claimed the low-stock list was
Low stock in the top menu — it is one of the buttons under the overview's figures, which is what the guide now
says; and the five price columns (f05fc3f) the stock-managing roles see beside the alert level are now named, with
the Counter Assistant's page stated to be without them. Both C2 figures were re-shot live as tc.ca.one on 2 October
2026 (c2-stock-overview.png, c2-low-stock.png, bytes verified) so the page matches the app as it now stands.
C3 is published (2026-09-21). Written against the expiry report as it actually behaves: one list holding the
expired and the short-dated together, earliest expiry first; the two grey warnings (expired / critical);
the 90-day and 30-day bands as a setting the page does not print; and the fact that FEFO already refuses an expired
batch, so the report is how the pharmacy finds the stock it will otherwise sit on. The page has no button on it, so
the "what to do" points at C4. Captured as tc.ca.one (the read-only role) and the Pharmacist's table was compared
to it cell by cell — byte-identical, so all four roles see the same list. The worked example is the tenant's own
cached positions: DEMO-AMOX-B3 (expired) and DEMO-AMOX-B2 (critical).
C3 corrected (2026-10-02, session 52). Two drifts, both closed on the page, and the illustration rebuilt rather than
left describing a state that no longer existed. (1) The refresh now prunes. 508aea3 deletes cache rows in the
synced (item, warehouse) scope that ERPNext did not report, only after the scope was read without error. Live the
same day: the tenant's cache held 7 rows and a refresh reported 5, and the two the ledger had stopped reporting
— DEMO-AMOX-B3 / B2, this page's worked example — were removed, leaving the report empty. The "a batch that
has left the shelf still shows a quantity" row is gone, replaced by the removal behaviour, and the page now carries
both states: a listed batch, and the "Nothing is expiring within the warning band." answer. (2) The report gained
five price columns for the three stock-managing roles (f05fc3f), so the byte-identical claim above no longer
holds for the table: the Counter Assistant gets the five list columns, the RP / Technician / Business Manager
thirteen. Proved live on the same show_prices path by capturing the low-stock report as both roles (8 columns
against 3). The illustration is the live page again: two short-dated batches were received through the app's own
goods-in on 2026-10-02 (GIN-20261002-6C6D66, ERPNext MAT-STE-2026-00033 — DEMO-AMOX-B4, 24 packs, expiry 20 Oct;
DEMO-SALB-B2, 12 packs, expiry 1 Dec; synthetic throughout), the refresh then reported 7, and the guide's example
table and figure are that page, captured as tc.ca.one (asset 160, bytes verified). The expired case cannot be
staged: goods-in refuses a past date and ERPNext refuses to move an expired batch, so the guide states plainly that
an expired row is found rather than created. The knock-on it created — C2's low-stock page carrying the same five
columns, undocumented — was closed in the same session (see the C2 correction above).
C4 is published (2026-09-21). Written against the adjustment form as it actually behaves: RP and Technician only
(the Business Manager and the Counter Assistant are both refused — verified, HTTP 403); a required free-text reason
of up to 64 characters; a quantity that must be above zero (zero and negatives are refused, the direction carries
the sign); optional batch and expiry; a decrease posted as a Material Issue and an increase as a Material
Receipt with no valuation against it (a real purchase belongs in goods-in, C1). Two of the app's rough edges are
written into it as troubleshooting rows, not as features: a date the app cannot read comes back with no message
anywhere, and the red "could not be posted to ERPNext. Please try again." invites a retry that is not safe
(OpenCode confirmed the adjustment has no idempotency guard, so a half-completed post can be doubled). Test as
tc.tech.one — but note that posting a real adjustment moves the client's own ERPNext ledger, so work it with a
correction the pharmacy wants recorded. The form shows nothing on hand, so read C2/C3 first; both guide figures are
live captures (the second is a refused entry, which posted nothing).
C4 amended (2026-10-02, session 52) — one row added, and the retry passage split in two. The page's warning about the
red banner covered only the double-post risk; it now also covers the case the banner cannot distinguish — ERPNext
refusing the entry outright, which no retry can fix. Found live while staging C3's expired example: an increase
naming an expired batch is refused (HTTP 417, BatchExpiredError). Read rather than guessed, for the boundary: in
the deployed ERPNext 15.121.6, StockController.validate_serialized_batch() skips the expiry check when
purpose in ("Material Issue", "Material Transfer") — so an increase (Material Receipt) can never record expired
stock, while a decrease (Material Issue) can write it off. That is what this guide's "expired — disposal"
advice rests on, and it is unchanged. The guide now names the refusal as its own troubleshooting row, says plainly that
the app cannot yet tell the two failures apart, and the Direction row warns that an increase cannot name a batch whose
date has gone. The app-side fix is authorised and briefed to OpenCode (2026-10-02, in
from-hermes): tell a 4xx refusal from a post-then-fail, carry ERPNext's own reason,
leave the ambiguous wording alone, and test both branches. When it lands the page drops "not yet" and gains the new
string.
C5 is published (2026-09-21). Written straight after running the refresh on the client tenant: the button reported
"Refreshed 5 stock position(s)." and Last position refresh moved from Sept. 19, 2026, 4:04 p.m. to
Sept. 21, 2026, 3:21 p.m. — the guide's figure is that page. The refresh is POST-only (opening its address
directly gives 405), the Counter Assistant is refused (403), and there is no scheduled job behind it
(checked CELERY_BEAT_SCHEDULE: no stock task), so the timestamp is the only guarantee of currency. The 5-versus-7
gap is now documented in the guide rather than hidden: the tenant's cache holds seven rows and the refresh
reported five, because the two it did not report back (both Amoxicillin batches, last synced 2026-09-14 / 09-15) are
never removed — the cache-upserts-never-prunes behaviour already noted to OpenCode. Run it with tc.tech.one;
it changes nothing in the ledger and no fixture is consumed.
C5 corrected (2026-10-02, session 52). The note above is the September state, and two of its claims no longer hold.
The refresh prunes (508aea3): a cached position ERPNext does not report is deleted, item by item and pool by
pool, once that scope has been read without error — so the 5-versus-7 gap is gone (the cache now holds what ERPNext
reports and nothing else), and C5's four claims to the contrary are replaced, including the "it never removes a
position" paragraph and the "neither counted nor removed" row. The failure behaviour is now stated exactly: a refresh
that fails leaves everything it did not read as it was, and never empties the cache. Figure re-shot live as
tc.tech.one on 2 October 2026 — the refresh reported "Refreshed 7 stock position(s).", with Last position
refresh moving from 2:55 p.m. to 3:01 p.m. (c5-refresh-result.png, bytes verified). Still POST-only (a GET is
405), still refused for the Counter Assistant (403), and still nothing scheduled: CELERY_BEAT_SCHEDULE and both
boxes' cron were checked again, so the timestamp remains the only guarantee of currency.
Group C is now complete — C1–C7 all published, C7 last on 2026-09-28.
C7 is published (2026-09-28), written the day the supplier record's new portal fields were verified live, so
the guide documents the record as it now stands rather than twice. It covers the list's eight columns and what each
means, adding a supplier, editing one, the two things that cannot be changed (a name already in use; the name of a
supplier that has been delivered against — "Deactivate it and add a new supplier instead."), the Portal block
with its evidence rule and the DEMO marker, and deactivate/reactivate. Verified live, role by role: all four roles
read the list, the Counter Assistant sees it with no buttons and is refused a supplier's own page (403,
because that page carries the price-file history); the seven refusals are quoted word for word, and after each of the
attempted writes the list was byte-identical — including the three portal-evidence sentences. Deactivating a
supplier was exercised and reverted: it left the goods-in dropdown ("Supplier … deactivated."), and reactivating put
it back — both recorded in the audit trail. The three figures (the list as the Business Manager, a complete DEMO
portal record, and the read-only Counter Assistant view) were built from the app's own CSS block, not Bootstrap.
Test as tc.bm.one, with tc.ca.one for the read-only comparison.
C6 is published (2026-09-21), the day the feature shipped. It documents the whole ordering cycle as it actually
behaves: the build screen reads the cached positions and shows Last position refresh; quantities are
pre-filled from the item's order-up-to level (else back to its alert level) and are whole packs only —
"…: quantity must be a whole number of packs" is the refusal; lines are dropped and quantities changed on the
draft's Edit lines, because the build screen has no drop control even though its copy mentions dropping them
(a wording nit worth a later fix); Export CSV downloads
<supplier>-order-<reference>.csv and locks the lines; and the Against order box on the goods-in form lists the
exported orders and refuses one from a different supplier. All three figures are live captures, and the exported file
is quoted verbatim in the guide from a real download: 2345678,32 / 0123456,21 /
DEMO-BARCODE-0003,5 — the leading zero kept, the PIP-less item falling back to its barcode. The file is headerless
since 2026-09-29 (the standard export carries no header row — see the File Contract),
so C6's quoted block was corrected to match on 2026-10-02. Test as
tc.tech.one. The tenant now holds two orders — ORD-20260921-898C9C (Exported, 3 lines, exported twice) and
ORD-20260921-D123C9 (Draft, 2 lines, left deliberately so the edit and drop path can be walked) — and the demo
tenant's only low item is DEMO-IBU-400 (on hand 8, level 20, suggested 32), so the build screen shows one line.
C6's first figure was re-shot 2026-10-02. The image had been captured 2026-09-21, ten days before the
ordering view shipped, so the guide's text described the new screen while the picture beside it showed the old one.
c6-order-build.png is now a live capture of the ordering view — the four-offer header, the NHS reimbursement beside
the margin, and the basket bar — taken as tc.tech.one and byte-verified after upload.
Test as: tc.bm.one for the supplier list (its figures are the Business Manager's view, with
tc.ca.one beside it proving the read-only role), tc.tech.one for the goods-in and the adjustment, tc.rp.one for the sync, tc.ca.one to
confirm read-only stock is open to a Counter Assistant too — that is the role C3 was captured as, and the same
login proves the read gate on all three stock read screens.
Walk-in trade. The Counter Assistant owns this, and the other roles can staff it.
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| D1 | Sell an over-the-counter item | Counter Assistant (any staffing role) | Published — Counter — Sell an Over-the-Counter Item (2026-10-06) |
| D2 | Add the NHS prescription charge to a sale | Counter Assistant (any staffing role) | Published — Counter — Add the NHS Prescription Charge (2026-10-06) |
| D3 | Take payment and close the sale | Counter Assistant (any staffing role) | Published — Counter — Take Payment and Close the Sale (2026-10-06) |
| D4 | Give the customer a receipt (print or email) | Counter Assistant (any staffing role) | Published — Counter — Give the Customer a Receipt (2026-10-06) |
| D5 | Refund or void a sale | Pharmacist, Business Manager | Published — Counter — Refund or Void a Sale (2026-10-06) |
| D6 | Look at the till and the day's takings | Counter Assistant (any staffing role) | Published — Counter — The Till and the Day's Takings (2026-10-06) |
Test as: tc.ca.one — it exists now, so this group tests as the Counter Assistant. D5 as tc.rp.one or
tc.bm.one.
Verified live 2026-10-06 (spec: The Counter Views).
Every screen in this group is now the delivered, verified build — inclusive shelf prices (the price shown is the
price charged), the operator's till with a hand-over that names the holder, park and resume, a scan that adds the
line only on an exact match, the receipt as a retailers' VAT document, and the cash routine that records the float
carried forward and the cash banked. 28/28 live checks against the client's own ERPNext, through the committed
verifier veripath-ops fa46eb0 (scripts/verify-pharmacy-counter-views-live.py); the full record is on the spec
page. All six guides in this group are published off those verified screens, one at a time, 2026-10-06: D1,
D2, D3,
D4, D5
and D6 — the group is complete, and each still owes
Matthew's walk-through.
Data notes for the figures: the tenant's counter identity is now seeded and its VAT registration number is
deliberately blank (so a receipt figure shows no VAT number); and the charge picker offers every dispensed or
endorsed token — ten of them on 2026-10-06, of which three were collectable (demo-token-dispensed-1,
demo-collection-ready-1, demo-collection-optedout-1). D2's figures were taken on demo-token-dispensed-1, and
its two refusals on demo-accounting-claim-2 (an exempt declaration) and demo-counter-token-1 (already collected).
Basket 18 was then completed as the D3/D4 example — ACC-SINV-2026-00029, cash 20.00, change 6.90 — and is left
standing as a completed sale. Basket 19 (ACC-SINV-2026-00030, £2.50 taken by card) was made for the D6 figures
and then reversed as the D5 example: refund, reason recorded, stock returned to the batch it was sold from, and
the invoice and payment cancelled in the client's books. The till session 20 was closed as the D6 example —
counted 112.10 against expected 113.10, a variance of −1.00, 50.00 banked and 62.10 carried forward; the till is
left closed, so D6's walk-through opens with opening it. (A guard probe opened and closed one throwaway session,
float 10.00, immediately afterwards.)
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| E1 | Hand a prescription over and record the collection | All four roles | Published — Collection — Hand a Prescription Over (2026-10-06) |
| E2 | See which prescriptions are ready but uncollected | Pharmacist, Technician, Business Manager | Published — Collection — See Which Prescriptions Are Ready but Uncollected (2026-10-06) |
| E3 | Send or resend a collection notification | Pharmacist, Technician, Business Manager | Published — Collection — Send or Resend a Collection Notification (2026-10-06) |
| E4 | See the delivery trail for a notification | Pharmacist, Technician, Business Manager | Published — Collection — See the Delivery Trail for a Notification (2026-10-06) |
| E5 | Record a patient's contact preference | Pharmacist, Technician, Business Manager | Published — Collection — Record a Patient's Contact Preference (2026-10-06) |
| E6 | Record a patient opt-out | All four roles | Published — Collection — Record a Patient Opt-out (2026-10-06) |
Test as: tc.rp.one, or tc.tech.one for the E3–E5 staff view. E1 and E6 should be reachable by
tc.ca.one too — worth confirming, as the Counter Assistant is excluded from the contact-detail surfaces.
Written off the live screens 2026-10-06 (no build spec; the screens are as delivered). All six are published,
one at a time, each from live captures on the demo tenant. The role gates were checked live: the Counter
Assistant reaches the collection dashboard, the handover form and the handover history (200) and is refused the
uncollected list, the trail and the contact-preference screen (403); the pharmacist, technician and business
manager reach all six. E1's answer to the open question above: the Counter Assistant can reach E1, but E6 is
not reachable in the UI for them — the opt-out lives on the contact-preference screen, which the CA is refused,
although the opt-out view itself allows all four roles (verified: a CA POST to the opt-out URL is accepted and reports
Patient opted back in to notifications.). Filed to OpenCode. One further finding, also filed: the Collection
screen's Dispensed column shows the record's updated_at, while the uncollected list shows the true dispense time
— token demo-collection-uncollected-1 reads 2026-09-15 on the dashboard and 2026-09-05 on the uncollected
list. Fixtures used: handovers recorded on demo-accounting-claim-1 (Collected) and demo-accounting-claim-2
(Returned to the shelf); the failed collection-ready on demo-collection-failed-1 was resent and now reads Sent
with 2 attempts; demo-token-dispensed-1 gained a contact preference (SMS 07700 900456); demo-collection-uncollected-1
was opted out and straight back in (unchanged); demo-collection-optedout-1 (opted out) untouched.
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| F1 | Complete or update the premises profile (trading name, address, contact) | Business Manager, Pharmacist | Published — Premises Profile & Registration Details |
| F2 | Record the GPhC/ODS identifiers and the verification evidence | Business Manager, Pharmacist | Published — same page |
Outstanding on group F: illustrations.
Test as: tc.rp.one (Pharmacist, as the page is written) — and tc.bm.one to confirm the Business
Manager reaches it too.
The figures the owner and the bookkeeper have to answer for. The whole group is Business Manager only.
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| G1 | The accounting dashboard | Business Manager | Planned |
| G2 | Review the expected reimbursement for each endorsed claim | Business Manager | Planned |
| G3 | Record an actual receipt from the NHSBSA and see the variance | Business Manager | Planned |
| G4 | Work the unmatched receipts list | Business Manager | Planned |
| G5 | The takings and VAT report | Business Manager | Planned |
| G6 | The stock valuation report | Business Manager | Planned |
| G7 | Gross margin by band (NHS dispensation vs retail) | Business Manager | Planned |
| G8 | Produce the period-end pack for the bookkeeper | Business Manager | Planned |
Test as: tc.bm.one — it exists now, so group G is testable. Every other role, including the Responsible
Pharmacist of the same tenant, should be refused.
| # | Everyday task | Who does it | Guide |
|---|---|---|---|
| H1 | Understand what each role can and cannot reach | Every staff member | Published — Roles and Permissions in the Pharmacy App |
Test as: any login — the page describes the gates; the tests above prove them.