One sitting, five stops, about 15 minutes. Import a wholesaler's price file, see the prices on the stock screens,
build an order from them in the ordering view — each item's best four offers, the NHS reimbursement, and the
advisory margin beside it — read the minimum-order advisory — and finish on the step that is new: what the
pharmacy paid beside what the NHS will reimburse, with the rule that produced the figure named on the screen.This is the walkthrough deferred on 2026-09-22 ("we'll need to get some walkthrough pages done once we are done with
the development work"), written now because the development work has stopped moving: pricing stages 1–4 are live
and verified, and phase 8 — the comparison — is live and verified (2026-09-27). It is the click-by-click companion
to the pricing plan and
the phase-8 spec.
| Host | https://test-client-pharmacy.pharmacy.veripath.co.uk/ — the client tenant, which is the test model: its own database on the client's own box, its own realm, its own tunnel |
| Log in as | Stop 1 tc.bm.one (Bess Marginwell, Business Manager — the only role that may import a price file) · Stops 2–5 tc.tech.one (Tess Trayborne, Dispensing Technician) |
| Passwords | Not on this page, by design. The credential is held host-local on the platform (mode-600) and never enters the wiki or git. Ask the platform owner for it |
| Never do this | Do not use a real price list. Every number in this walkthrough comes from the seeded synthetic files, and the import screen says so on its face |
| The file you will import | The checked-in synthetic example synthetic-wholesaler-demo-price-list-01.csv — printed in full at Stop 1, ready to save as a .csv. The five example files live in the repository at pharmacy_stock/management/commands/example_price_files/ |
One rule worth saying out loud before the first click: the app reads price files, it does not trust them. A line it
cannot match to a stocked item, or whose price it cannot parse, is kept and shown with its reason rather than
quietly dropped — and you will see that at Stop 1.
Where: Stock → Price files → Import a price file.
The file, ready to save. Copy the block below into a file called
synthetic-wholesaler-demo-price-list-01.csv — it is the checked-in example, every value is invented, and it is
deliberately imperfect so that the counts you are about to see are real rather than theoretical:
Product Code,Product Description,Price,Pack
0123456,Synthetic amoxicillin 500mg capsules (DEMO),£1,234.56,21 capsules
2345678,Synthetic ibuprofen 400mg tablets (DEMO),0.89,84 tablets
5000124439750,Synthetic salbutamol 100mcg inhaler (DEMO),3.50,200 doses
9999999,Synthetic item the pharmacy does not stock (DEMO),2.00,10
0123456,Synthetic duplicate of the first code (DEMO),1.20,21 capsules
DEMO-SNOMED-999,Synthetic line with an unusable price (DEMO),not-a-price,28 tablets
Saving it on a Windows PC. Paste the block into Notepad and use File → Save as, choose All files (.) and
name it synthetic-wholesaler-demo-price-list-01.csv — otherwise Windows saves it as .csv.txt and the import will
refuse it. Leave the encoding on UTF-8 (the default on current Windows): the file contains a £, and a file saved
in an older ANSI encoding turns that one line into price is not a number instead of the accepted price the counts
below assume. Either way nothing is damaged — the app records what it found — but the counts will differ by one line.
Which supplier, and which price date. Import it for Synthetic Wholesaler (DEMO) with the price date
1 September 2026 — that is the file already seeded for that supplier and date, so you will meet the supersede
warning and watch the earlier copy turn Superseded. Or pick another supplier, or today's date, to add a fresh row
and change nothing that is already there. Either way the file you import is the one that shows its lines.
Do this: choose the supplier, set the price date (the date the prices are for, not today), attach the file
(.csv, .txt or .tsv) — drag it onto the page, or use the file picker; both do the same thing — then continue.
One file at a time: each file is recorded against one supplier and one price date, so a drop of several files is
declined. On the next step, confirm which of the file's columns is the code and which is the
price — the dropdowns are the file's own headers, with the first rows shown so you can see what you are choosing — set
the basis (per pack / per unit / not stated), and commit.
You should see, on the price-file list (Stock → Price files), one row per imported file with four counts that
describe exactly what the app did with every line:
6 read · 3 matched · 1 unmatched · 2 rejected — with the status Parsed
and on the file's own page, every line the app would not use, with the reason it kept it:
| Line in the file | What the app records | Why |
|---|---|---|
9999999 |
Unmatched | no matching item — no stocked item carries that code |
0123456 (the second line with this code) |
Rejected | duplicate code in file |
DEMO-SNOMED-999 |
Rejected | price is not a number |
One line worth looking at twice: £1,234.56 on amoxicillin is accepted, not refused — the parser strips a
currency symbol and thousands separators, so that becomes a real price of £1,234.56. That is deliberate: a real
wholesaler file carries exactly this sort of nonsense, and the app's answer is not to hide it but to show it with its
age and its source, which is what Stops 2 to 4 are about. This screen is what makes a "why is this item priced like
that?" question answerable.
(A note on the file the seed already imported: once a file has been superseded, its own page has no lines left to
show — the superseding removed them and only the counts remain — so the page reads "Every line matched a stocked
item." The lines above are the ones on the file you import this time.)
Why it matters: the counts are the honest record. A file that "worked" because 40% of its lines vanished is worse
than a file that says 40% is unusable. The screen also warns you when the import will supersede a file that is
already live for that supplier and date.
Where: Stock → Low / zero stock (/stock/low-stock/), Stock → Expiry report (/stock/expiry/), and
Stock → Suppliers → <a supplier> for its price-file history.
You should see the same four price columns on both reports, whatever the number of wholesalers:
Best price (supplier) · Spread · Prices on file · Freshness — and, since phase 8, NHS reimbursement
Live, today:
| Item | On hand | Best price (supplier) | Spread | Freshness | NHS reimbursement |
|---|---|---|---|---|---|
| Ibuprofen 400mg tablets | 8.00 | £0.79 (Synthetic Wholesaler Two) | £0.26 | Current, 27 Sep | £2.07 — 20% deducted |
| Synthetic stale-price item | 5.00 | £4.20 (Synthetic Wholesaler Four) | — | Stale — 18 working days, 01 Sep | Not linked to a dm+d pack yet — no code that matches a pack |
| Synthetic one-day-price item | 5.00 | £1.75 (Synthetic Wholesaler Three) | — | Current, 25 Sep | Not linked to a dm+d pack yet — no code that matches a pack |
Three things to point at while you are here:
£0.00.Where: Stock → Orders → New order (/stock/orders/new/).
Do this: the build screen is now the ordering view — one grid where each low item shows its best four supplier
offers (each naming its own supplier, its price and basis, and the file's freshness), the best pick the app
proposes (changeable on the line), the NHS reimbursement with the rule that produced it, and the margin beside
it. Set quantities, create each supplier's draft from the basket bar at the foot, then export the CSV.
You should see rows such as:
Synthetic stale-price item — proposed from the preferred supplier with the reason "no usable price — proposed
from the preferred supplier" (its only price is stale) · basket value £26.25
The rule that matters: the code rule wins over price, always. A supplier is only ever proposed for an item it
can actually be given a line for — PIP code first, then barcode. A cheaper supplier that cannot be told what to send
is not a cheaper supplier.
Where: the same build screen, and the order once created.
You should see each supplier's line in the basket bar — its value, and — when it falls short of that
wholesaler's contractual minimum — a line like:
£X of a £Y minimum — £Z short
It is an advisory, and it stays one. It never blocks the order, never changes a line, and never moves a quantity. A
supplier with no minimum recorded shows nothing at all — silence, not a zero. The minimum is the wholesaler's
threshold for the pharmacist's judgement, not a software limit.
This is the step the whole programme was built for, and it has three faces.
Stock → Low / zero stock → Amoxicillin 500mg capsulesEverything about one item on one page:
Linked by Barcode / GTIN to pack 1037711000001109 — Amoxicillin 500mg capsules 21 capsule. Matched against dm+d
release 3170926; evidence: barcode 05012617008855.£1.04 NHS reimbursement per pack — Basis: £1.30 (dm+d tariff price), Part VIIIA Category M. Rule: 20%
deductible as Part VIIIA Category M (Drug Tariff 2026-09). Releases: dm+d 3170926; Drug Tariff 2026-09; concession
notices 2026-09.Your price: £2.10 Synthetic Wholesaler Two (DEMO) (Per pack)
Point at the arithmetic, because it is the whole argument: £1.30 less 20% = £1.04, and this pharmacy is buying the
pack at £2.10. That is the buy-above-reimbursement case — visible, not buried, and advisory only: it colours
nothing red, warns about nothing and blocks nothing. The pharmacist decides.
One blemish as this page was written (2026-09-27): the line immediately under Your price prints a raw internal
value ({'key': … }) instead of its text. Reported for a one-line template fix; the figures above it are correct.
On the low-stock, expiry and order screens the NHS reimbursement column carries the same figure with its rule —
£1.04 — 20% deducted — so nobody has to leave the list they are working in to check it. Where an item has no
purchase price on file the column says exactly that ("£3.61 — no supplier price on file") rather than a misleading
blank, and where the item is not yet linked to a pack it names the reason ("Not linked to a dm+d pack yet — no code
that matches a pack").
Two rows in the demo catalogue are deliberately unbridgeable — that is not a fault, it is the honest state:
| Item | What the screen says | Why |
|---|---|---|
| Ibuprofen 200mg tablets (own brand) | no code that matches a pack | its barcode is not a dm+d GTIN |
| the two "REIMB" demo items | no code to match on | no barcode and no PIP code |
Any stock item that carries no link offers a "Link by hand to a dm+d pack (VMPP id)" box. A hand-made link records
who made it and when, is written to the audit trail, and holds until superseded — the automatic resolver never
overwrites it.
Say this before you leave the screen: nothing here is a quotation. Every figure is reproduced from the month's
published rules — the tariff price or the concession price, the deduction scale, the Single Activity Fee — and each one
carries the pack, the rule and the releases it came from.
NHS reimbursement − best usable per-pack price, derived and read-only, advisory and gross — it excludes the Single Activity Fee, which is per| What | Where |
|---|---|
| The plan, the stages and the decisions | Supply Orders Part 3 — Wholesaler Pricing |
| The comparison: the spec and the live verification | The Client Comparison — Phase 8 |
| The reimbursement arithmetic | The Derived Pricing — Phase 7 |
| The rehearsal that runs itself | veripath-ops/scripts/verify-client-comparison-live.py (client surfaces, 64 checks) and verify-client-comparison-host.py (the store-side evidence, run in the app container) |
| The user-guide wording for ordering | C6 — Order Stock and Export the Order File (rewritten for the ordering view, 2026-10-01) |
VeriPath internal. Dummy/synthetic data only — no patient-identifiable information appears in this walkthrough, and no
credential or internal address is recorded here. Every figure quoted was read from the live test client on 2026-09-27.