Files
clinicpro/docs/api/practice-domain.md
T
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

5.3 KiB

Practice Domain API

Prefix: /api/v1/practice-domains, /api/v1/practice-domain

A practice domain is the field a clinic operates in — beauty, dentistry, orthopaedics. It is a configuration key, not a marketing label: treatment workflows bind to its code, so the code is immutable once created. It is deliberately not Specialty, which stays a descriptive label for the public booking site.

The table is global (registered in GlobalTables::ENTITIES); a clinic points at zero or one of its rows through clinics.practice_domain_id, which is nullable. NULL means "not configured" and keeps today's behaviour — it is never an error.


GET /api/v1/practice-domains

List domains, ordered by sort_order then name.

Permission: IS_AUTHENTICATED_FULLY. Callers without ROLE_ADMIN see only active rows, so a clinic manager cannot pick a domain the platform has retired. ROLE_ADMIN sees inactive ones too, to be able to switch them back on.

Response 200

{
    "success": true,
    "data": [
        {
            "uuid": "8c7bfd18-9159-11f1-b98b-f28fd8aa5db5",
            "code": "beauty",
            "name": "کلینیک زیبایی",
            "sort_order": 0,
            "active": true,
            "has_workflow": true
        }
    ]
}

has_workflow می‌گوید آیا پیاده‌سازیِ TreatmentWorkflow مخصوصِ این کد ثبت شده یا حوزه فقط رفتار پیش‌فرض می‌گیرد. حوزهٔ بی‌workflow کار می‌کند — دوره‌های چندجلسه‌ای‌اش باز می‌شوند — ولی رفتار اختصاصی ندارد، و پنل ادمین پلتفرم باید همین را نشان دهد نه اینکه مدیر حدس بزند.

Errors:

Code HTTP توضیح
ERR_AUTH_001 401 بدون توکن

POST /api/v1/practice-domains

Create a domain.

Permission: ROLE_ADMIN (platform admin only). A clinic manager creating its own domain would produce a domain with no workflow behind it, and the misconfiguration would stay invisible until the first protocol-driven booking.

Request Body (application/json)

Field Type Required توضیح
code string ^[a-z0-9_]{1,40}$، یکتا در کل جدول، بعد از ساخت تغییرناپذیر
name string نام نمایشی فارسی
sort_order int پیش‌فرض 0
{ "code": "dental", "name": "دندانپزشکی", "sort_order": 2 }

Response 201

{
    "success": true,
    "data": {
        "uuid": "af5063de-753f-444a-ba95-05ec2ffe13be",
        "code": "dental",
        "name": "دندانپزشکی",
        "sort_order": 2,
        "active": true
    }
}

Errors:

Code HTTP توضیح
ERR_VALIDATION_001 422 بدنهٔ نامعتبر، یا کد خارج از الگو، یا کد تکراری (field: code)
ERR_VALIDATION_002 422 name خالی است (field: name)
ERR_FORBIDDEN_001 403 کاربر ROLE_ADMIN نیست
ERR_AUTH_001 401 بدون توکن

Real 422 for a duplicate code:

{"success":false,"data":null,"errors":[{"code":"ERR_VALIDATION_001","message":"حوزه فعالیتی با این کد از قبل وجود دارد","field":"code"}]}

PATCH /api/v1/practice-domain/{uuid}

Update a domain's display fields.

Permission: ROLE_ADMIN.

code is ignored if sent. Workflow implementations are resolved by code, so renaming it would silently detach a live clinic from its workflow.

Request Body (application/json)

Field Type توضیح
name string فقط اگر ناتهی باشد اعمال می‌شود
sort_order int
active bool

Response 200

{
    "success": true,
    "data": {
        "uuid": "af5063de-753f-444a-ba95-05ec2ffe13be",
        "code": "dental",
        "name": "دندان‌پزشکی",
        "sort_order": 5,
        "active": true
    }
}

Errors:

Code HTTP توضیح
ERR_VALIDATION_001 422 بدنهٔ درخواست نامعتبر است
ERR_VALIDATION_002 404 حوزه فعالیت یافت نشد
ERR_FORBIDDEN_001 403 کاربر ROLE_ADMIN نیست

Assigning a domain to a clinic

There is no dedicated endpoint. The existing PATCH /api/v1/clinic/{uuid} accepts one more key — see clinic.md.

Body اثر
"practice_domain_uuid": "<uuid>" حوزه ست می‌شود
"practice_domain_uuid": "" یا null حوزه پاک می‌شود
کلید اصلاً نباشد حوزهٔ فعلی دست‌نخورده می‌ماند

An unknown uuid is rejected rather than ignored, because a silently dropped selection would only surface at the first protocol-driven booking:

{"success":false,"data":null,"errors":[{"code":"ERR_VALIDATION_002","message":"حوزه فعالیت یافت نشد","field":"practice_domain_uuid"}]}

GET /api/v1/clinic/{uuid} returns the current value under data.data.practice_domain, null when unset:

{"uuid":"8c7bfd18-9159-11f1-b98b-f28fd8aa5db5","code":"beauty","name":"کلینیک زیبایی","sort_order":0,"active":true}