# 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`
```json
{
    "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` |

```json
{ "code": "dental", "name": "دندانپزشکی", "sort_order": 2 }
```

### Response `201`
```json
{
    "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:
```json
{"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`
```json
{
    "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](./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:

```json
{"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:

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