Add dental module phase 4 and phase 5 documentation, including treatment estimates, clinical operations, and preset content

- Introduced phase 4 documentation detailing treatment estimates, data models, state machines, API endpoints, and new dashboard metrics.
- Added phase 5 documentation covering consumables, lab operations, periodontal charts, sterilization cycles, clinical images, and a checklist for professional review.
- Created a preset content document outlining default dental service packages and protocols for clinics.
This commit is contained in:
hamed
2026-08-20 16:21:08 +03:30
parent 601d211d6f
commit 2f030edef1
12 changed files with 1794 additions and 3 deletions
@@ -0,0 +1,13 @@
# Practice domain belongs to the tenant, not only to the clinic
`practice_domain_id` originally lived on `clinics`, but every piece of operational data in this
codebase is owned by an `(entity_type, entity_id)` pair where `entity_type` is `doctor` or `clinic`.
A solo practice is a `doctor` tenant, so it could never declare a practice domain at all — the beauty
domain has the same hole, it simply had not been noticed. The column is therefore added to `doctors`
as well and read through a single `PracticeDomainResolver` that takes an `EntityContext`, so no
caller has to know which kind of tenant it is looking at.
## Considered Options
Letting a doctor inherit the domain of a clinic they work at was rejected: a doctor with no clinic
would stay domainless, and a doctor working at two clinics with different domains would be ambiguous.
@@ -0,0 +1,15 @@
# Teeth are not Treatment Areas
A Treatment Area is a `CatalogCategory` snapshotted onto a case, which made "one category per tooth"
look like a free way to get dental charting. It was rejected: FDI tooth numbering is a universal fact,
not a per-clinic taxonomy, so it would duplicate 32 to 52 identical rows into every clinic's service
tree, surfaces would need a further level below that, and the persistent condition of a tooth — missing,
crowned, implanted years before the patient ever arrived — has nowhere to live on a settings row.
A tooth is instead an FDI `smallint` on the record that targets it, and tooth condition is its own
snapshot table in the Dental context.
## Consequences
Tooth condition must be updated after each visit, and that projection lives in exactly one class rather
than being spread across controllers. In exchange, rendering a chart is one query and never a replay of
history.
@@ -0,0 +1,15 @@
# Dental attributes of a service live in an extension table, not on ServiceItem
Whether a service is priced per tooth, per surface or per canal — and whether booking it must ask for a
tooth at all — is dental-only knowledge, but `ServiceItem` is shared by every practice domain. Those
attributes therefore sit in a one-to-one `dental_service_profiles` row in the Dental context, keyed by
`service_item_id`, so a beauty clinic carries no dental columns and the next domain is not invited to add
its own set to the shared table. The cost is a join whenever the dental profile is needed, which is the
same pattern the codebase already uses elsewhere.
## Consequences
The reverse choice was made deliberately one level down: the tooth and surfaces a visit line was actually
billed for are columns on `SessionService` itself, because that row is the clinical and financial record
of the visit rather than shared configuration, and splitting it would allow a billed line to lose its
target unnoticed.
@@ -0,0 +1,13 @@
# Domain-specific dashboard metrics come from tagged providers
`DashboardController` already serves four role dashboards from 803 lines and fifteen dependencies, so
branching each of them on practice domain would double four code paths and make every clinic pay for
queries only dentists need. Domain metrics instead come from a `DomainMetricProvider` interface resolved
by a tagged-service registry keyed on the practice domain code — the same shape as `TreatmentWorkflow`
in ADR 0005 — and the role endpoints simply attach whatever the provider returns.
## Consequences
Unlike the workflow registry there is no default implementation: a tenant with no domain, or a domain with
no provider, gets `null` and the panel renders no extra section. An empty metrics block is worse than an
absent one.