3 Commits
Author SHA1 Message Date
hamedandClaude Opus 5 c7a3b88b32 feat(treatment): bind an appointment to a chosen session, and free it on cancel
Two holes in how a course's later appointments were made.

The link from the unbooked queue carried nothing — `/admin/appointments/new`
with no parameters — so the secretary retyped the patient and the service, and
which case the appointment joined was inferred from the service they happened to
pick. A patient with two open courses had no way to say which one they meant,
and picking the wrong service silently opened a third case. (The suggestion link
did pass slot_start and resource_uuid, but the create page never read either.)

POST /api/v1/my/appointment now takes an optional treatment_session_uuid.
SessionBookingLink validates it — same tenant, still unbooked, case open, same
patient — and reserves that session. Confirm-time attachment steps aside when
the appointment already holds a session. The booking form states in words which
session, which course and which patient it is about to book, read from a new
GET /api/v1/treatment-session/{uuid}.

Nothing ever detached a session from its appointment, so a cancelled booking
left the session `booked` forever, and since findNextUnbooked requires
"has no appointment", it could never return to the queue. Cancellation and
no-show now release it back to `planned`. A finished session is history and is
left alone.

The system still never books the next appointment by itself — it only suggests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 15:44:22 +03:30
hamedandClaude Opus 5 a331aab2b8 fix(treatment): open the treatment case when confirming from the panel
confirmWithPayments — the path behind POST /appointment/{uuid}/confirm, which
is how a secretary actually confirms — created the patient session but never
called TreatmentCaseStarter. Only onConfirmed did. So an appointment on a
service with an active protocol was confirmed and paid, and no treatment case
or sessions were ever created; the staff panel had nothing to list.

Every existing test in OpenCaseOnConfirmTest drove onConfirmed, which is why
the gap survived. Added one that drives confirmWithPayments; it fails without
the fix.

Also adds app:treatment:backfill-cases, mirroring
app:appointment:backfill-sessions: it reports confirmed appointments on a
protocol service that have no case, and with --fix replays the starter and
prints the exception the logger would otherwise keep to itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 12:24:29 +03:30
hamedandClaude Opus 5 252e20bfe9 feat(treatment): select treatment behaviour by practice domain, not by if-branch
Everything that differs between specialties as data is already stored as data.
What is left is behaviour — when a case opens, what happens once a session ends —
so it becomes a TreatmentWorkflow resolved through a tagged-service registry.
The booking path calls one collaborator and never names a specialty; adding
dentistry is a new class, not an edit to confirmation.

A clinic that has chosen no practice domain still gets working multi-session
courses: DefaultTreatmentWorkflow answers for null and for any code without a
dedicated implementation, keeping "unset means behave as today, not error".
LaserTreatmentWorkflow is deliberately empty beyond claiming `beauty` — it is the
seam where laser-specific behaviour will land without disturbing anyone else.

Session due dates are anchored to the previous session's actual finish, so a
patient who comes twenty days late shifts the rest of their course instead of
getting the next session while it can still do nothing. Only the next session is
recomputed; later ones keep their estimate because they are anchored to nothing
yet.

Attachment targets the first session without an appointment rather than the
first open one: a patient booking again mid-course was otherwise matched to the
session that already had a booking, and the second appointment went nowhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 17:23:51 +03:30