TEMPORARY WORKING PLAN — created 2026-09-05. Lives here so both Hermes and Matthew can refer to it across sessions. REMOVE WHEN COMPLETE: once the financial testing pass (Phase 8) is done and docs are published, delete this page with /opt/wiki-manage.py --prune development/projects/dental_app/financial-billing-test-plan. Companion to the ERPNext integration plan: Dental App — ERPNext Integration Plan.
Dental clinical/ops testing (charting, perio, radiographs, FP17 claims) is complete. This pass tests the financial & billing workflows end-to-end on test-client-dental, including the ERPNext leg (VPS3, test-client.accounts.dental.veripath.co.uk). ERPNext integration is code-complete and Phase-C verified (2026-08-18); what remains is the user-facing live test pass + doc review. One doc page reviewed at a time (user rule). Dummy/synthetic data only. Bugs found → OpenCode via handoff.
Two patient payment intake paths (both Stripe, per Matthew 2026-09-05):
- Path A — at the surgery: staff-initiated payment taken in-person (exact UI to be confirmed in Phase 0/1 against the live app).
- Path B — via the personal web app: patient pays through the portal PWA (shared portal,
/dental/* views).
Both paths land on DentalTransaction (total_amount, deposit_paid, PENDING/PARTIAL/PAID/REFUNDED, Stripe refs) via DentalPaymentConfig (deposit %, GBP, Stripe test keys).
Private dental insurance — untouched so far; the app has no private-insurance/capitation concept (only the NHS "insurer" customer in ERPNext for FP17 reimbursement). Matthew will research and bring context; scope decided AFTER this testing pass is complete. Do not build or document anything for it yet.
- Confirm demo users on test-client-dental: DA / DPM / ADMIN / superuser (Financial nav), clinician (Invoice view only), receptionist (must NOT see Financial/Invoices/UDA). Patient portal demo user for Path B.
- Confirm
DentalPaymentConfig for practice 55: deposit %, currency GBP, Stripe TEST keys.
- Identify the exact UI for both payment paths: Path A staff payment screen(s) (check-in / appointment / dedicated page?) and Path B portal payment view. This becomes doc fact.
- Snapshot ERPNext baseline on
test-client.accounts.dental: current Sales Invoice + Payment Entry counts (scenario creates must be identifiable).
- Seed known-value fixtures: 2–3 private appointments (£95 / £240 / £450), 1–2 NHS appointments with band charges.
- Verify SSO "Open in ERPNext" for the manager user (Keycloak, no re-login).
- Book private appointment → deposit collected (test BOTH paths: Path A at surgery, Path B via portal) →
DentalTransaction: total_amount, deposit_paid, status PARTIAL (or PENDING pre-payment).
- Financial Dashboard 4 cards + per-practice breakdown update correctly.
- Doc: private-ledger-billing (review pass + refresh screenshots).
- Balance collection post-treatment (both paths) → transaction PAID, Stripe refs recorded.
- Leave one transaction PENDING; confirm visible as money owed.
- REFUND path: test if a UI exists; if none, record as documented gap (do not invent).
- Check whether any non-Stripe recording exists (e.g. receptionist cash/cheque) — doc only what exists.
- Practice financials screen: revenue, deposits collected, NHS/private split, DNA rate, FP17 claims card.
- Doc: private-ledger-billing (finish) + any payment screens.
- Run NHS claims through to Transmitted/Accepted with controlled band + transaction totals (setup for Phases 4–5). No new doc (fp17-claim-submission already live-tested); note financial-side deltas only.
- On ACCEPTED claim click Reconcile Payment:
- split correct vs 2025/26 band charge (e.g. £73.50 band 2 of £95 total → £21.50 patient share);
- transaction → PARTIAL/PAID, RECONCILED log event;
- TWO local invoices (NHS Reimbursable → payer "NHS"; Patient Co-payment → patient), both submitted in ERPNext → Sent.
- ERPNext side: ACC-SINV names, amounts, customers (NHS in Commercial group), income accounts, GL entries.
- Doc: co-payment-splitting + invoicing (ERPNext) review passes — incl. the SEATS footnote: enum choice exists in code but nothing generates SEATS invoices (B8 decision 2026-08-18: seat billing belongs to VeriPath platform infra). Decide: footnote or drop the row.
- Collect patient co-pay + reconcile NHS share →
sync_payment_to_erpnext → Payment Entry per invoice → Paid in local list AND ERPNext.
- Optionally force a worker failure to watch retry/backoff (60/120/240 s, max 3) — code-verified; decide in-test.
- Local invoice flips to Paid with ERPNext Payment Entry ref.
- Map statuses Draft/Sent/Paid/Overdue/Cancelled to what actually occurs in these flows.
- Doc: invoicing (finish).
- Create allocations per clinician/practice for 2025/26 (create / edit / deactivate flows).
- Production derived from submitted claims: band → 1/3/12 UDA; only Transmitted/Accepted/Partially-paid count; period = transmission date; private = 0.
- Dashboard cards, per-clinician rows, progress-bar colours, period filter.
- Doc: uda-auditing (review pass).
- Re-verify role gating live on every financial screen for every role (DA/DPM/AD/SU yes; clinician invoices-only; receptionist/patient 403).
- SSO link-through from a second user; ERPNext customer records sane; test data cleaned between scenarios.
- Folds in open Hermes follow-ups: role-permissions-matrix + walkthrough-test-users RBAC sections.
- One page at a time (user review): private-ledger-billing → co-payment-splitting → invoicing → uda-auditing (+ fp17 financial bits if changed).
- Regenerate stale screenshots from live demo; update wiki guides + repo copies (
content/user-manuals/); commit; /opt/deploy-manuals.sh; byte-verify on manuals.dental.
- Update housekeeping log; leave OpenCode handoff note for fixes found.
- Then remove this page (
--prune).
- Practice id: the tenant's single active practice is pk 57 "Test Client Dental Practice" (tenant
test-client-dental, DB test_client_dental on central PG). The plan/ERPNext references to practice 55 are stale (earlier data state) — use 57.
- FINDING 1 CONFIRMED (£471 vs £0): 11 transactions, ALL PAID, all dated Jan/Feb/Aug 2026 (pre-September) → dashboard Revenue = all-time sum (£471.30) while Practice Financials = month-scoped (£0). Revenue scoping mismatch real on screen.
- FINDING 3 (new): REJECTED claim has NO billing path. Claim 213 (Tom Bennett, £25.80, tx PAID) — only action is "Return to Draft" (fix + resubmit). No invoice/bill generated; no "convert rejected NHS claim to private patient bill" flow exists. Business question for Matthew; docs must state current behaviour.
- ERPNext config (was EMPTY, now PROVISIONED 2026-09-05):
dental_dentalerpnextconfig row exists for practice 57 (is_active, keys set, realm veripath) — "Open in ERPNext" SSO + invoice sync unblocked. Verified live in container. (Original finding below kept for the audit trail: config was empty at Phase 0; re-provision was the second OpenCode note.)
- Live claim set ready for Phase 4: 210 DRAFT (James Carter, 19 Aug), 211 TRANSMITTED (Priya Sharma, 28 Aug), 212 ACCEPTED (Maria Lopez, 23 Aug, £70.70 — ideal Reconcile Payment candidate), 213 REJECTED (Tom Bennett, 12 Aug). No Invoice rows in tenant yet.
- NHS/Private split drill-down: counts derive from month appointments (8 NHS / 6 private / 0 DNA = 14 in Sept); the appointments list (Quick Actions → View Appointments) shows per-row Band n / Private badges — that is the breakdown view (no separate funding-stream report exists).
- Deposits: all 11 tx have deposit_paid = 0.00 (none collected via deposit flow yet) — Phase 1/2 must exercise deposit collection to see Deposits Collected populate.
- Revenue tile → month-scope (DECIDED, change request for OpenCode): Financial Dashboard Total Revenue card + per-practice Revenue column should display revenue billed in the current month (matching Practice Financials) to remove the all-time/month confusion. Batch with other fixes after this testing pass unless Matthew says otherwise. RAISED to OpenCode 2026-09-05 (from-hermes note, Status OPEN) together with the revenue-breakdown accountability ask (Item B below). ✅ IMPLEMENTED + VERIFIED 2026-09-05: shipped c25db51a (deployed, dental suite 106 green); financial_dashboard month-scopes transactions; Practice Financials has the Revenue Breakdown ledger (funding-stream badges, collected vs still-due chips, empty state). Both from-hermes finance notes marked HANDLED.
- REJECTED FP17 claim → patient bill as shortfall (DECIDED business rule, PARKED — no code now): if an FP17 claim is rejected, it becomes a bill to the patient as if it were an insurance shortfall (context: private dental insurance paying towards covered treatment; patient pays the shortfall). No implementation this pass; record for product — may merge with the parked private-insurance research thread.
- View Appointments from Practice Financials (Quick Actions) = same generic appointment list as /dental/appointments/ — UX QUESTION OPEN. Matthew: "we have some work here to do". Candidate shapes (awaiting steer): month-filtered list; add payment-status/funding columns; or a separate revenue-by-appointment drill-down. Nothing implemented.
- £471 accounting (intro-page material): all-time £471.30 = 11 ledger transactions = £323.20 seven non-claim appointments + £148.10 four FP17-claim appointments (subset, not separate). FP17 claims-list total £159.70 = sum of per-claim
total_nhs_fee column (28.30/28.30/74.80/28.30) — a different basis (NHS fee per band), shown as "£" on the claims list. Invoice list empty = no claim reconciled yet (invoices are born only on Reconcile Payment / payment sync).
- Demo-data hygiene: seeded claims use 2024/25-era fees (patient_charge £25.80/£70.70; total_nhs_fee £28.30/£74.80) vs app
NHS_BAND_CHARGES 2025/26 (£26.80/£73.50/£319.10). Claim 212 (band 2, tx £70.70) is BELOW the 2025/26 band-2 charge (£73.50) — reconciling it now would yield nhs_share £73.50 > tx total (patient_share £0): an edge case. Seed a fresh band-2 claim with total > band charge (e.g. £95) for the clean Phase-4 demo, or deliberately test the edge.
Live re-verify of c25db51a DONE on screen: month-scope + Revenue Breakdown behave as shipped. September revenue £0 everywhere is CORRECT — 14 Sept appointments exist but zero September transactions (all 11 tx, £471.30, are Jan–Aug, all PAID). The confusion was presentational (see below). All items below RAISED to OpenCode 2026-09-06 in ONE from-hermes note (Status OPEN) — the "house in order" change request.
- PRODUCT STATEMENT — one practice per tenant. "Each tenant represents one practice — we will tackle multiple practices belonging to one organisation when required." Financial UI to stop per-practice framing: financial_dashboard (loops all active practices → "Per-Practice Breakdown" table) reshaped as THE practice's overview; header names the practice; no per-practice table. Presentation-only; DentalPractice model unchanged (future multi-practice).
- Payments nav BUG (fix first): base_dental.html:89 "Payments" →
{% url 'partner:stripe_config' %} = /partner/stripe-config/ (GP partner app). Dental has NO payment-config screen (DentalPaymentConfig is admin-only, dental/admin.py:82; only webhook route exists). Options for OpenCode: (a) build practice-scoped dental payment-config screen (Path-A deposit intake needs a home) or (b) hide the link until then. Matthew: sort this first.
- View Appointments quick action — DIRECTION SET (resolves item 3 above): show the month-scoped appointments that relate to the month's figures, with per-appointment funding/payment picture (funding badge + claim link, payment status) so the amount is accountable per row.
- Count semantics: NHS/Private counts include CANCELLED (Sept: 8 NHS = 7 live + 1 cancelled; 6 private = 5 live + 1 cancelled); a DNA appt would double-count in NHS/Private AND DNA. Matthew: DNA is a key indicator — keep. Recommend: activity cards count non-CANCELLED month appts, mutually exclusive buckets. Revenue vs activity needs labels/copy ("No payments recorded in yet — revenue appears when deposits, balances or FP17 reconciliations are recorded").
- FP17 claims card (carried): Pending = DRAFT only, Submitted = SUBMITTED only; Transmitted/Accepted/Rejected/Partially-paid invisible. Included in the OpenCode ask (cheap fix or defer + say so).
Session note 2026-09-06 (post-OpenCode c361d53e): house-in-order items shipped + verified on screen by Matthew ("looking more organised"). Stripe config DONE: practice 57 cfg 52 previously held PLACEHOLDER test keys (...l_demo); real Stripe TEST keys copied from the GP booking app PaymentConfig (appointments.models_payment, 3 test practices all same account — source validated live: TEST mode, key valid) into DentalPaymentConfig practice 57 (pub/sec/whsec, encrypted at rest, deposit 20%/GBP/active preserved, round-trip verified, nothing printed/logged). End-to-end proof: PaymentIntent created via the app's own path (dental.payment.get_stripe_client, practice 57) — pi_1UChZy..., £21.50 test, requires_payment_method, client_secret issued → test-card payments possible. NEW FINDING (Phase-1 blocker): no payment-intake UI is wired. dental/payment.py create_payment_intent/confirm_deposit have ZERO callers in app code; no DentalTransaction creation path outside seeding; Payment Settings screen (nav Payments → /dental/settings/payments/, payment_config views) is config-only. Path A (surgery deposit/balance) needs a small OpenCode build: transaction-create + intent/confirm action on appointment/booking flow (and/or confirm Path B portal state). Also: GP-source webhook secret ends xxxxxx (looks masked at source) — verify against the Stripe dashboard before webhook testing (payment_intent.succeeded → PAID flip).
- Invoicing guide lists Seats Subscription as an invoice type — enum exists, nothing generates it (see Phase 4).
- Refund UI existence unverified — Phase 2 will confirm.
- Receptionist role: takes bookings/payments per role matrix, but Financial/Invoices/UDA nav hidden — Phase 0/7 confirm where receptionist payments happen (Path A staff screen may be receptionist-accessible while Financial dashboards are not).
¶ Findings and decisions (2026-09-07 - ERPNext access restored; UI-trim mission started)
- ERPNext dental access FULLY restored (realm fix + Notification Type doctype + assets + setup-wizard flags - see from-hermes notes + housekeeping-log handover 2026-09-07). Click-through /dental/invoices/ to "Open in ERPNext" works; lands on ACC-SINV-2026-00026.
- NEW FINDING (UX): claim-invoice linkage invisible + amount-basis confusion. NHS-customer ERPNext invoice (GBP 70.70) = claim 212 (Maria Lopez, b2, ACCEPTED) NHS_REIMBURSABLE from its reconcile, but: no invoice column on claims list, no claim column on invoice list, no invoices section on claim detail; claims-list Fee column shows total_nhs_fee (GBP 74.80) vs invoice GBP 70.70 (patient_charge basis) - nothing matches ERPNext amounts. RAISED to OpenCode (from-hermes 2026-09-07, low priority, batchable). DOC PASS must explain NHS band fee (total_nhs_fee) vs patient charge (patient_charge) vs invoiced amount (patient_charge-based split) - covers invoicing + co-payment-splitting guides.
- ERPNext UI trim (Ongoing mission): spec at development/projects/dental_app/erpnext-ui-trim; OpenCode code parts (role mapping, blocked-modules at creation/backfill, mapper) OPEN + ACTIVE; Hermes site-level config (workspaces/settings hides) after code lands. Dashboards config = LATER (post-testing, Matthew).
- Testing resumes: FP17 walkthrough (submit 210, advance 211 to ACCEPTED, reconcile; fresh GBP 95 band-2 course for the two-invoice + patient co-pay demo) + Stripe payment test still QUEUED.
¶ Findings and decisions (2026-09-08 — private income must reach ERPNext)
-
LIVE FINDING: private payments do not reach ERPNext (by design gap, now confirmed). Matthew recorded a CARD payment on appt 1294 (Tom Bennett, HYGIENIST private, £60, tx 515) via the new record-payment screen — locally PERFECT (tx PAID, real Stripe TEST PaymentIntent pi_1UDMTxV05DCU8 at 10:42Z) but nothing appeared in ERPNext accounting. Worker log: "DentalTransaction #515: no synced ERPNext invoice found" → SKIPPED 'No ERPNext invoice'. Code-verified root cause: ERPNext Sales Invoices are only created by the NHS FP17 reconcile flow; private appointments are billed locally (PENDING tx at confirm-arrival) but have NO ERPNext invoice flow (flagged future work at price-list v1). ERPNext would therefore only ever hold NHS money.
-
PRODUCT DECISION (Matthew): private payments MUST flow into ERPNext — "otherwise how are such payments accounted for? It's still the same business at the end of the day." ERPNext = the full practice books.
-
RAISED to OpenCode 2026-09-08 (from-hermes note, Status OPEN): build the private Sales Invoice + Payment Entry leg mirroring the NHS pattern — recommended hook = confirm-arrival billing (invoice created + submitted when the private tx is billed, accrual parity with NHS); local Invoice rows via existing Invoice model (type TRANSACTION unused, or new PRIVATE type); sync_payment_to_erpnext then works unchanged; idempotency guard needed. Backlog decision flagged: demo PENDING 1295/1297/1298 (£80) + already-PAID 515.
-
Untested today / next: the NHS co-pay ERPNext leg is still the working live path — collect the £18.40 co-pay on appt 1292 (PATIENT_COPAY invoice ACC-SINV-2026-00028 SENT) to see Payment Entry + PAID flip end-to-end while the private build is pending.
-
LIVE BUG (2026-09-08): co-pay Payment Entry sync fails with 417 — TWO root causes, RAISED to OpenCode. Matthew collected the £18.40 co-pay (Maria Lopez appt 1292, tx 517 → PATIENT_COPAY ACC-SINV-2026-00028): local side perfect (tx PAID, Stripe TEST PI captured, invoice PAID locally) but ERPNext stayed Unpaid — all 4 sync attempts (initial + 3×60s retries) 417 EXPECTATION FAILED on healthcare_accounting.api.create_payment_entry. (1) sync_payment_to_erpnext passes amount = tx.total_amount (£95) not the co-pay invoice's £18.40 outstanding → ERPNext allocation validation rejects it; (2) the dental ERPNext site rounds GBP to whole pounds (grand 18.40 → rounded/outstanding 18.00; NHS 76.60 → 77.00 — currency precision 0dp, likely from lean provisioning; check GP site too). Bonus finding: ERPNextClient logs drop frappe's _server_messages, so the real reason never surfaced. Recovery post-fix: re-enqueue sync for tx 517 (no ERPNext residue; erpnext_synced False). Demo invoices 14/15/16 carry the wrong ERPNext outstanding until repaired/re-created — decision pending.
-
FP17 WORKFLOW REDESIGN — ON HOLD (2026-09-08, Matthew to research the real NHSBSA/FP17 process first). Matthew's decisions in discussion (not yet raised to OpenCode): (1) nothing financial at booking; (2) at ATTENDANCE the claim AND its invoice(s) are raised together — a claim must never exist without an invoice; (3) no "will it/won't it be accepted" grey area — submission should only be possible for validated claims, so acceptance is deterministic; REJECTED becomes an exceptional error path, not a normal money-flow stage; (4) invoices raised at attendance won't need reversing (service delivered = money due). Open questions parked: whether both NHS invoices (band share + co-pay) are raised at attendance (needs a course total — recommend NHS attendance also creates a course transaction at the price-list amount); what reconcile means once it stops being the invoice-birth point (recommend: NHS settlement trigger). Matthew is researching the real FP17 process before finalising; Hermes to spec the consolidated OpenCode change request after. NOTE: the 2026-09-08 notes already raised (private income → ERPNext; 417 co-pay sync bugs) are independent and stand.
-
FP17 WORKFLOW REDESIGN — RESOLVED 2026-09-08 (supersedes the ON-HOLD marker above). Matthew researched the real process (wiki gp_booking_app/fp17_claims) and locked the decisions: charges follow TREATMENT COMPLETION not attendance (CoT is the billing unit — build now); clinician completes each appointment, course closure triggers claim/charges; SPLIT PAYMENTS REMOVED (the NHS £76.60 + patient £18.40 co-pay construction never happens in real practice — NHS patient pays the statutory band charge or £0-exempt with evidence; NHS income = claim acceptance → UDA credit → contract payment); no acceptance grey area (validate before transmit); demo re-prep after build. Full spec + phasing: development/projects/dental_app/fp17-course-billing-redesign. RAISED to OpenCode 2026-09-08 (from-hermes, Status OPEN). This supersedes the 07-Sep check-in billing decision (b181a295) — financial testing of the pre-redesign flows (phases 1–5 of this plan) is effectively PAUSED until the redesign lands; remaining testable now: local-side mechanics unaffected by the redesign (e.g. private payment capture works; ERPNext leg blocked on the 417/GBP-precision fixes).