feat(docs): add laser treatment plan and related ADRs for multi-session treatments

This commit is contained in:
hamed
2026-08-06 15:23:06 +03:30
parent 1a07dad17c
commit 1d43475724
70 changed files with 15001 additions and 3234 deletions
@@ -0,0 +1,13 @@
# Treatment Sessions are separate from Appointments
Multi-session treatments (laser courses, botox top-ups) need a session that keeps its number and its
clinical record across cancellations and reschedules, and they must not lock eight months of calendar
slots the moment a course starts. So a Treatment Session is its own record owning the clinical data,
holding an optional reference to an Appointment; booking a session creates the Appointment, and
rescheduling swaps it without disturbing the session.
## Considered Options
Making the Appointment itself the session was rejected: session numbering would break on every
cancellation, and pre-creating every future session as a real Appointment would occupy slots months
ahead for patients who may never attend.
@@ -0,0 +1,11 @@
# A Treatment Case snapshots its areas
The areas a service covers are derived from the catalog category graph, which the clinic manager can
edit at any time. Because a session's area records are a medical record, a Treatment Case copies its
area list at creation and never re-derives it; editing the category graph afterwards affects only
cases opened from then on.
## Consequences
An in-flight case will not pick up a category change. If a clinic genuinely needs that, it has to be
an explicit re-sync action, not a silent side effect of editing settings.
@@ -0,0 +1,19 @@
# Resource-backed appointments are guarded by occupancy, not the doctor slot key
An appointment's `active_slot_key` is `doctor_id:slot_start` under a unique index, which assumes the
doctor is the thing being occupied. Once a doctor supervises several devices that assumption breaks:
the second booking in the same hour on a different device is rejected. Resource occupancy already
guards those bookings at the database level via `uniq_bucket_resource_seat (resource_id, bucket_at,
seat)`, so an appointment that carries a resource leaves `active_slot_key` null and lets occupancy be
the sole authority; only resourceless legacy bookings keep the doctor key.
## Considered Options
Rekeying on the resource (`r{resource_id}:{slot_start}`) was rejected because it silently defeats
`ClinicResource.capacity`: a room seating three would reject its second patient, and the unique index
knows nothing about seats, buffers, or setup and cleanup time.
## Consequences
Any booking path that omits the resource falls back to the doctor key. Those paths have to be found
and made resource-aware, or they end up with weaker protection than they have today.
@@ -0,0 +1,14 @@
# Session parameters are JSON, described by the resource type
What an operator records for a treated area splits in two: facts every specialty shares — which
resource, when it started and finished, the note — and readings that belong to one kind of device,
like a laser's energy, pulse and shot count. The shared facts are real columns on the session's area
record; the device-specific readings live in a `parameters` JSON column, and the field list that
drives the form and its validation is declared on the resource type. Adding a dentistry or RF device
is then a settings change, not a migration.
## Consequences
Aggregate reporting over a JSON key (average energy per area over a quarter) has no index behind it
and will be slow. If such a report becomes a first-class requirement, the specific keys get promoted
to real columns rather than the whole design being reversed.
@@ -0,0 +1,13 @@
# Treatment workflows are tagged services, not a configurable engine
Everything that differs between specialties as *data* — categories, protocol steps, resource types,
form fields — is already stored as data. What is left is behaviour: when a case opens, what happens
when a session completes. That is expressed as a `TreatmentWorkflow` interface with one
implementation per practice domain, resolved through a Symfony tagged-service registry, so booking
code never names a specialty and a new one is a new class rather than a core change.
## Considered Options
A data-driven state machine editable by clinic managers was rejected: no clinic manager will author a
state machine, so in practice only developers would use it — code with worse syntax, no type safety
and no debugger.
@@ -0,0 +1,14 @@
# The clinical record of a session is separate from its visit record
`PatientSession` already means "an attended visit that gets billed" — it is created when an
Appointment is confirmed and owns services, insurance shares, discounts and payments. A Treatment
Session, by contrast, exists from the moment a course is planned, long before any booking, and must
survive reschedules. So the two stay separate: a Treatment Session holds the plan and the clinical
record (its area records, devices and readings) and carries no money, while the Visit Record keeps
the financial side and is reached through the Appointment.
## Consequences
A session's full picture spans four records — case, session, appointment, visit — so queries are
longer. In exchange, neither side can silently become a second source of truth for the other. Any
money field appearing on a Treatment Session is a design error.