A practice domain is the field a clinic operates in — beauty, dentistry —
and unlike Specialty it is configuration, not a label: treatment workflows
will bind to its code, so the code is immutable once created and only a
platform admin can mint one. A clinic that has not chosen a domain keeps
behaving exactly as it does today.
Assignment reuses PATCH /api/v1/clinic/{uuid} rather than adding a second
endpoint. An unknown domain uuid is rejected instead of silently dropped,
because a lost selection would only surface at the first protocol-driven
booking.
Also corrects ADR-0003: resource occupancy does not in fact guard the panel
booking path, which writes appointments.resource_id and no occupancy row at
all, so the doctor slot key cannot simply be dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4.8 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
}
]
}
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}