# Doctor API

> **`name` در برابر `display_name`:** `name` نامِ خام است (بدون عنوان — برای فرم ویرایش).
> `display_name` نامِ آمادهٔ نمایش با پیشوند «دکتر» است که بک‌اند تولید می‌کند
> (`PersianText::withDoctorTitle`) تا همهٔ کلاینت‌ها یکسان و بدون «دکتر دکتر …» نشان دهند؛
> در لیست و جزئیات پزشک هر دو حاضرند. برای نمایش از `display_name`، برای ویرایش از `name` استفاده کنید.

> فیلد `owner_status` (`claimed` | `unclaimed` | `pending_transfer`) به خروجی لیست و جزئیات پزشک
> اضافه شده است — پروفایل `unclaimed` (ایمپورت نظام پزشکی) در سایت دکمهٔ «تصاحب پروفایل» می‌گیرد
> (`docs/api/doctor-claim.md`) و نوبت‌دهی آنلاینش غیرفعال است.
>
> **امتیاز فقط برای پروفایل `claimed` منتشر می‌شود.** برای هر `owner_status` غیر از `claimed`
> (یعنی `unclaimed` / `pending_transfer`) فیلدهای `point` و `satisfaction` مقدار `null` برمی‌گردند —
> مقدار پیش‌فرض انتیتی (`3.5` / `60`) نشتی نمی‌کند تا امتیاز جعلی/`AggregateRating` جعلی ساخته نشود.
> بعد از تصاحب و claimed شدن، امتیاز واقعی به‌صورت خودکار برمی‌گردد.

> **Prefix:** `/api/v1/doctor`, `/api/v1/doctors`, `/api/v1/clinic-pro/doctor-address*`
>
> Numeric path params on the address routes (`doctor-address/{id}`, `doctor-addresses/{doctorId}`) require `\d+`; a non-numeric value returns a clean `404` instead of a `500`.

---

## POST `/api/v1/doctor`

Create a doctor profile for the authenticated user.

**Permission:** `AUTH` — any authenticated user

### Request Body (`application/json`)
```json
{
  "title": "علی احمدی",
  "gender": "male",
  "medical_system_code": "12345",
  "degree": "متخصص",
  "info": "توضیحات درباره پزشک",
  "specialties": [1, 2],
  "doctor_services": [3, 4]
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | ✅ | نام پزشک **بدون** عنوان. پیشوند «دکتر» سمت سرور با `PersianText::stripDoctorTitle()` حذف می‌شود؛ نمایش عنوان کار لایهٔ UI است. |
| `gender` | string | ❌ | `"male"` or `"female"` |
| `medical_system_code` | string | ❌ | Nظام پزشکی code |
| `degree` | string | ❌ | Academic degree |
| `info` | string | ❌ | Bio/description |
| `specialties` | integer[] | ❌ | Array of specialty IDs. تخصص‌ها درختی‌اند: هر شناسهٔ فرزند سمت سرور با تمام والدهایش تا ریشه گسترش می‌یابد، پس پاسخ ممکن است تخصص‌های بیشتری از ورودی داشته باشد (مثلاً ارسال «گوارش و کبد» → ذخیرهٔ «داخلی» + «گوارش و کبد»). شناسه‌های ناموجود نادیده گرفته می‌شوند. |
| `doctor_services` | integer[] | ❌ | Array of doctor service IDs |
| `activity_time` | integer | ❌ | Unix timestamp (ثانیه) تاریخ شروع فعالیت؛ مبنای محاسبهٔ `experience` (سال تجربه) در پاسخ |

### Response `201`
```json
{
  "success": true,
  "data": {
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "title": "علی احمدی",
    "gender": "male",
    "medical_system_code": "12345",
    "degree": "متخصص",
    "info": "...",
    "doctor_rate": null,
    "active_doctor_appointment": false,
    "specialties": [],
    "doctor_services": [],
    "created_at": 1717000000
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing or invalid token |
| `ERR_CONFLICT_001` | 409 | Doctor profile already exists for this user |
| `ERR_VALIDATION_002` | 422 | Missing required field |
| `ERR_VALIDATION_001` | 422 | نام پزشک شماره‌تلفن یا مقدار آزمایشی است |

---

## GET `/api/v1/doctor/{uuid}`

Get doctor detail with clinics.

**Permission:** `PUBLIC`

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | string (UUID) | Doctor UUID |

### Response `200`
```json
{
  "success": true,
  "data": {
    "data": {
      "uuid": "550e8400-...",
      "name": "علی احمدی",
      "display_name": "دکتر علی احمدی",
      "gender": "man",
      "medical_system_code": "12345",
      "degree": "specialist",
      "detail": "...",
      "img": [],
      "social_media": {
        "instagram": "https://instagram.com/dr.example",
        "telegram": "https://t.me/dr_example",
        "aparat": null,
        "youtube": null,
        "linkedin": null
      },
      "satisfaction": "60",
      "point": "3.5",
      "free_turn": "دوشنبه 09:00–13:00",
      "hours_of_work": "شنبه: 09:00–13:00 و 14:00–18:00 | یکشنبه: 09:00–13:00",
      "active": true,
      "owner_status": "claimed",
      "specialties": [{ "uuid": "...", "id": "1", "name": "قلب و عروق", "parent_id": null }],
      "expertise": [{ "uuid": "...", "id": "3", "name": "نوار قلب" }],
      "address": [],
      "state": [],
      "city": [],
      "clinics": [{ "uuid": "...", "name": "کلینیک الوند", "address": "...", "telephone": "..." }],
      "representation": { "id": 12, "uuid": "9c1...", "full_name": "علی محمدی" }
    }
  }
}
```

> ⚠️ **Double-nested:** Frontend extracts with `data?.data?.data`
>
> ℹ️ `representation` نماینده‌ی مالکِ پزشک است؛ برای پزشکِ بدون نماینده `null`.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |

---

## GET `/api/v1/clinic/my-doctor/{doctorUuid}`

Get doctor detail for clinic owner — only doctors who are members of the authenticated clinic.

**Permission:** `ROLE_CLINIC`

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `doctorUuid` | string (UUID) | Doctor UUID |

### Response `200`
```json
{
  "success": true,
  "data": {
    "data": {
      "uuid": "...",
      "title": "علی احمدی",
      "specialties": [...],
      "clinics": [{ "uuid": "...", "name": "کلینیک نور", "address": "...", "telephone": "..." }]
    }
  }
}
```

> ⚠️ **Double-nested:** Frontend extracts with `data?.data?.data`

> **Side note:** Returns only the authenticated clinic's data in the `clinics` array (not all clinics of the doctor).

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_VALIDATION_002` | 404 | Clinic or doctor not found |
| `ERR_AUTH_006` | 403 | Doctor is not a member of this clinic |

---

### Schedule Fields Notes

**تجمیع همهٔ برنامه‌ها (2026-07):** این فیلدها روی **همهٔ** برنامه‌های هفتگی پزشک محاسبه
می‌شوند — برنامهٔ مطب شخصی (`clinic_id IS NULL`) به‌علاوهٔ یک برنامه به ازای هر کلینیک.
پزشکی که برنامهٔ شخصی خالی/خاموش ولی برنامهٔ کلینیکیِ فعال دارد، `active=true` می‌گیرد؛
برنامهٔ یک محیط هرگز محیط دیگر را نمی‌پوشاند.

| Field | When at least one schedule is bookable | When none |
|-------|---------------------------------------|-----------|
| `free_turn` | نزدیک‌ترین روز/ساعت کاری از امروز، بین همهٔ برنامه‌های روشن (مثلاً «دوشنبه ۹:۰۰–۱۳:۰۰») | «نوبت آزادی موجود نیست» |
| `hours_of_work` | خلاصه ساعت‌های همان برنامه‌ای که `free_turn` را داده (ساعت‌های دو محل با هم ترکیب نمی‌شوند) | «برنامه کاری تنظیم نشده» |
| `active` | `activeDoctorAppointment && (∃ schedule: online_booking_enabled && has_active_sessions)` | `false` — نوبت‌دهی غیرفعال |

> **نوبت‌دهی آنلاین غیرفعال:** اگر `meta.online_booking_enabled` در **همهٔ** برنامه‌های پزشک
> `false` باشد، `free_turn` برابر `"نوبت‌دهی آنلاین غیرفعال است"` و `active` برابر `false`
> برمی‌گردد؛ `hours_of_work` در صورت وجود برنامه حفظ می‌شود. تا وقتی حتی یک برنامه روشن و
> دارای روز فعال باشد، همان مبنا قرار می‌گیرد.
>
> چنین پزشکی (همه برنامه‌ها خاموش، ولی فلگ `active_doctor_appointment` **روشن**) در لیست
> عمومی `GET /api/v1/doctors` **نمایش داده می‌شود** — چون هنوز فعال است — ولی با
> `bookableRank` پایین‌تر از پزشکان دارای نوبت مرتب می‌شود و تنها با `active=1` از نتایج حذف
> می‌گردد.
>
> **پزشک غیرفعال (`active_doctor_appointment` خاموش):** با غیرفعال‌کردن پزشک از پنل ادمین،
> او دیگر در لیست عمومی `GET /api/v1/doctors` ظاهر نمی‌شود (پیش‌فرض این اندپوینت پزشکان
> غیرفعال را کنار می‌گذارد). صفحهٔ تکی `GET /api/v1/doctor/{slug}` همچنان پاسخ می‌دهد
> (ادمین/کلینیک از همین اندپوینت برای مشاهده/ویرایش استفاده می‌کنند) و در بدنهٔ پاسخ فیلد
> خام `is_active` (`= active_doctor_appointment`، مستقل از داشتن برنامه) را برمی‌گرداند؛
> کلاینت عمومی مثل nobat724 با `is_active === false` صفحهٔ پزشک را `404` می‌کند. این با
> فیلد `active` (که `active_doctor_appointment && has_schedule` است) فرق دارد.

---

## GET `/api/v1/doctors`

List doctors with pagination and filters.

**Permission:** `PUBLIC`

### Query Parameters
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `page` | integer | ❌ | Default: 1 |
| `limit` | integer | ❌ | Default: 10. **حداکثر ۵۰** — مقادیر بزرگ‌تر بی‌صدا به ۵۰ کاهش می‌یابند. مقدار واقعاً اعمال‌شده در `meta.limit` برمی‌گردد؛ برای پیمایش کامل به `meta.totalPages` تکیه کن، نه به «تعداد آیتم کمتر از limit درخواستی» |
| `search` | string | ❌ | Search in title |
| `specialty_id` | integer | ❌ | Filter by specialty ID |
| `city_id` | integer | ❌ | Filter by city ID — شامل دکترهایی که آدرس شخصی‌شان (`doctor_addresses.city_id`, با `doctor_id` مقداردار) در آن شهر است یا از طریق کلینیکی که آدرس آن در آن شهر است (`doctor_addresses.clinic_id`) |
| `state_id` | integer | ❌ | Filter by province ID — بر اساس آدرس شخصی پزشک (`doctor_addresses.province_id`) یا آدرس کلینیک |
| `domain` | string | ❌ | دامنه‌ی سایتِ درخواست‌کننده. اگر دامنه‌ی یک **نماینده سراسری** باشد، فقط پزشکانِ همان نماینده برمی‌گردند و `city_id`/`state_id` نادیده گرفته می‌شوند؛ دامنه شهری/ناشناخته اثری ندارد |
| `gender` | string | ❌ | `man` یا `woman` |
| `degree` | string | ❌ | `expert`, `general`, `specialist`, `subspecialistplus` |
| `name` | string | ❌ | جستجوی `LIKE` روی نام پزشک |
| `sort` | string | ❌ | `ASC` یا `DESC` (پیش‌فرض `DESC`) — مرتب‌سازی ثانویه بر اساس `doctorRate` |
| `active` | `0`/`1` | ❌ | `1` → فقط پزشکان **دارای نوبت** (تعریف پایین). `0` → فقط پزشکانی که فلگ `active_doctor_appointment` آن‌ها خاموش است (کاربرد ادمین). بدون این پارامتر → فقط پزشکان **فعال** (`active_doctor_appointment` روشن)؛ پزشکان غیرفعال هرگز در لیست عمومی نمی‌آیند |

### مرتب‌سازی و تعریف «دارای نوبت»

پزشک **دارای نوبت** یعنی هر سه شرط برقرار باشد (همان تعریفی که فیلد `active` هر آیتم پاسخ را می‌سازد):
1. فلگ `active_doctor_appointment` روشن،
2. `WeeklySchedule` ثبت‌شده با حداقل یک سشن `active: true`،
3. `meta.online_booking_enabled` برابر `false` نباشد.

لیست همیشه اول پزشکان دارای نوبت را نشان می‌دهد و بعد بقیه را؛ داخل هر گروه بر اساس `doctorRate` و پارامتر `sort` مرتب می‌شود.

### Response `200`
```json
{
  "success": true,
  "data": [
    {
      "uuid": "...",
      "name": "علی احمدی",
      "display_name": "دکتر علی احمدی",
      "gender": "man",
      "degree": "specialist",
      "img": [],
      "specialties": [{ "uuid": "...", "id": "1", "name": "قلب و عروق" }],
      "satisfaction": "60",
      "point": "3.5",
      "free_turn": "دوشنبه 09:00–13:00",
      "hours_of_work": "شنبه: 09:00–13:00 و 14:00–18:00 | یکشنبه: 09:00–13:00",
      "active": true,
      "owner_status": "claimed",
      "city": [
        { "uuid": "7bfb989e-...", "id": "123", "name": "یاسوج", "parent": "23" }
      ],
      "state": [
        { "uuid": "7bfb5705-...", "id": "23", "name": "کهگیلویه و بویراحمد" }
      ]
    }
  ],
  "meta": {
    "totalRecords": 50,
    "totalPages": 3,
    "currentPage": 1,
    "limit": 50
  }
}
```

> ℹ️ `point` و `satisfaction` فقط برای `owner_status="claimed"` مقدار دارند؛ برای `unclaimed`/`pending_transfer` هر دو `null` هستند.

### اعتبارسنجی نام پزشک

`name` نمی‌تواند شماره‌تلفن (`^0?9\d{9}$`) یا مقدار آزمایشی (`test`، `تست`، `-`، `null`) باشد. این مقادیر در **هر** مسیر نوشتن با `422` رد می‌شوند — API عمومی، پنل ادمین، import و دعوت کلینیک — چون گارد روی خودِ Entity نشسته است (`App\Shared\Util\DisplayName`).

دلیل: نام پزشک در `<title>` و نتایج جست‌وجوی سایت عمومی رندر می‌شود؛ رکوردی با نام «09390039833» یک صفحهٔ بی‌ارزش ایندکس‌شدنی می‌سازد.

> دعوت پزشک توسط کلینیک، اگر نام ارسال نشود، دیگر شمارهٔ موبایل را به‌عنوان نام نمی‌نشاند — برچسب خنثای «پزشک دعوت‌شده» می‌گیرد تا خود پزشک پروفایلش را claim کند. (ریشهٔ آلودگی تولیدی همین بود.)

فرمان ممیزی رکوردهای موجود:

```bash
php bin/console app:audit-polluted-records          # فقط گزارش
php bin/console app:audit-polluted-records --force  # خارج‌کردن از انتشار (بدون حذف)
```

### `city` / `state` در پاسخ لیست

آرایه با حداکثر یک عضو — هم‌شکل با `city`/`state` در پاسخ جزئیات پزشک و پاسخ لیست کلینیک‌ها.

- منبع مکان **دقیقاً همان قاعده‌ای است که فیلتر `city_id`/`state_id` اعمال می‌کند**: اول آدرس شخصی پزشک (`doctor_addresses` با `doctor_id` مقداردار)، و اگر نداشت آدرس کلینیکی که عضو آن است (`doctor_addresses` با `clinic_id` مقداردار و `doctor_id` تهی). یعنی هر پزشکی که با `city_id=X` برگردد، در پاسخ هم همان شهر را اعلام می‌کند.
- پزشک چند-مطبی **یک شهر اصلی** می‌گیرد (اولین مکان یافت‌شده) — نه فهرست همهٔ شهرها.
- پزشک بدون هیچ آدرس: `"city": []` و `"state": []` (آرایهٔ خالی، نه `null`).
- `city[].parent` شناسهٔ استان است.
- استخراج مکان دسته‌ای انجام می‌شود (`DoctorRepository::findLocationsByDoctors`) — حداکثر دو کوئری ثابت، مستقل از تعداد پزشکان در صفحه.

> 🔗 مصرف‌کننده: `nobat724_front/app/sitemap.js` با این فیلد تشخیص می‌دهد هر پزشک به کدام دامنهٔ شهری تعلق دارد (canonical). تغییر شکل این فیلد قرارداد آن را می‌شکند.

---

## PATCH `/api/v1/doctor/{uuid}`

Update doctor profile.

**Permission:** `AUTH` — must be the owner (or `ROLE_ADMIN`)

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | string (UUID) | Doctor UUID |

### Request Body (`application/json`)
Same fields as POST (all optional), plus:

| Field | Type | Description |
|-------|------|-------------|
| `social_media` | object | Keys: `instagram`, `telegram`, `aparat`, `youtube`, `linkedin`. Each value must be a full valid URL or `null`. Any value that fails `FILTER_VALIDATE_URL` is silently stored as `null`. |

```json
{
  "social_media": {
    "instagram": "https://instagram.com/dr.example",
    "telegram": "https://t.me/dr_example",
    "aparat": null,
    "youtube": null,
    "linkedin": null
  }
}
```

### Response `200`
Updated doctor object (same structure as GET single).

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |

---

## DELETE `/api/v1/doctor/{uuid}`

Delete a doctor profile.

**Permission:** `IS_AUTHENTICATED_FULLY` — **admin** (any profile) **or the owner** of a `claimed` profile (`doctor.user === current user`). Other users get `403`.

> **Side effect:** the doctor's insurance configuration (`tenant_insurances`, `entity_insurance_pricing`, and their `tenant_service_coverages`) is purged in the same request — these reference the doctor via a polymorphic `entity_id` with no DB FK, so the cleanup is enforced in the application.

> **Guard:** a doctor with existing appointments cannot be deleted (the `appointments.doctor_id` FK would otherwise raise a `500`). The endpoint pre-checks and returns `409 ERR_CONFLICT_001` instead.

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `uuid` | string (UUID) | Doctor UUID |

### Response `200`
```json
{
  "success": true,
  "data": { "message": "پزشک با موفقیت حذف شد" }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not admin and not the owner of this claimed profile |
| `ERR_VALIDATION_002` | 404 | Doctor not found |
| `ERR_CONFLICT_001` | 409 | Doctor has existing appointments and cannot be deleted |

---

## POST `/file/upload/clinic_pro/doctor/field_image`

Upload doctor profile image.

**Permission:** `AUTH`

### Request
`Content-Type: multipart/form-data`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file` | binary | ✅ | Image file (max 5MB) |

### Response `200`
```json
{
  "success": true,
  "data": {
    "url": "https://clinic-pro.ddev.site/uploads/doctor/abc123.jpg",
    "uuid": "...",
    "filename": "abc123.jpg",
    "filemime": "image/jpeg",
    "filesize": 204800
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_FILE_001` | 422 | Invalid file type |
| `ERR_AUTH_001` | 401 | Missing token |

---

## GET `/api/v1/clinic-pro/doctor-addresses/{doctorId}`

Get all practice addresses for a doctor, including addresses of clinics the doctor is a member of.

**Permission:** `PUBLIC`

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `doctorId` | integer | Doctor's numeric ID (route requires `\d+`; non-numeric → 404) |

### Response `200`
```json
{
  "success": true,
  "data": [
    {
      "id": "1",
      "uuid": "...",
      "type": "personal",
      "clinic_id": null,
      "clinic_name": null,
      "name": "مطب تهران",
      "address": "تهران، خیابان...",
      "telephone": "02112345678",
      "active": true,
      "timezone": "Asia/Tehran",
      "map": { "latitude": "35.6892", "longitude": "51.3890" },
      "city": { "id": "1", "name": "تهران" },
      "province": { "id": "1", "name": "تهران" }
    },
    {
      "id": "5",
      "uuid": "...",
      "type": "clinic",
      "clinic_id": 12,
      "clinic_name": "کلینیک الوند",
      "name": null,
      "address": "اصفهان، خیابان...",
      "telephone": "03112345678",
      "active": true,
      "timezone": "Asia/Tehran",
      "map": { "latitude": null, "longitude": null },
      "city": { "id": "3", "name": "اصفهان" },
      "province": { "id": "2", "name": "اصفهان" }
    }
  ]
}
```

> **نکته:** آدرس‌های با `type: "clinic"` از کلینیک‌هایی که پزشک عضو آن‌هاست می‌آیند و `clinic_name` نام کلینیک را نشان می‌دهد.

> **`active` و `timezone` (افزوده‌شده در تسک شعبه):** هر آدرس یک «شعبه» است و این دو
> ویژگی روی خروجی **همهٔ** اندپوینت‌های آدرس ظاهر می‌شوند، چون از
> `DoctorAddress::toArray()` می‌آیند. هر دو ستون `NOT NULL DEFAULT` دارند، پس ردیف‌های
> قدیمی هم `active: true` و `timezone: "Asia/Tehran"` می‌دهند؛ تغییر additive است.
>
> `active` در فاز فعلی **فقط ذخیره می‌شود** و هیچ اثری بر محاسبهٔ اسلات ندارد. نوشتن
> این دو فیلد از راه اندپوینت‌های همین سند انجام نمی‌شود؛ برای آن
> `PATCH /api/v1/branch/{addressUuid}` است — همراه با ساعت کاری هفتگی و اتاق‌ها در
> [branch.md](branch.md).

---

## POST `/api/v1/clinic-pro/doctor-address`

Add a new practice address.

**Permission:** `AUTH` — must own the doctor profile

### Request Body
```json
{
  "name": "مطب تهران",
  "address": "تهران، خیابان ولیعصر",
  "telephone": "02112345678",
  "province_id": 1,
  "city_id": 3,
  "latitude": 35.6892,
  "longitude": 51.3890
}
```

| Field | Type | Required |
|-------|------|----------|
| `name` | string | ❌ |
| `address` | string | ✅ (frontend validation) |
| `telephone` | string | ✅ (frontend validation) |
| `province_id` | integer | ✅ (frontend validation) |
| `city_id` | integer | ✅ (frontend validation) |
| `latitude` | float | ❌ |
| `longitude` | float | ❌ |

### Response `201`
```json
{
  "success": true,
  "data": {
    "id": 1,
    "name": "مطب تهران",
    "address": "تهران، خیابان ولیعصر",
    "telephone": "02112345678",
    "latitude": 35.6892,
    "longitude": 51.3890
  }
}
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not the doctor owner |
| `ERR_NOT_FOUND_001` | 404 | Doctor not found |

---

## PATCH `/api/v1/clinic-pro/doctor-address/{id}`

Update a practice address.

**Permission:** `AUTH` — must own the doctor profile

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `id` | integer | Address ID |

### Request Body
Same fields as POST — all optional.

### Response `200`
Updated address object.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
| `ERR_NOT_FOUND_001` | 404 | Address not found |

---

## DELETE `/api/v1/clinic-pro/doctor-address/{id}`

Delete a practice address.

**Permission:** `AUTH` — must own the doctor profile

### Response `200`
```json
{ "success": true, "data": { "message": "آدرس حذف شد" } }
```

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_AUTH_001` | 401 | Missing token |
| `ERR_FORBIDDEN_001` | 403 | Not the owner |
| `ERR_NOT_FOUND_001` | 404 | Address not found |

---

## POST `/api/v1/clinic-pro/doctor-address/from-clinic/{clinicUuid}`

Create a doctor address automatically from a clinic's location.

**Permission:** `AUTH` — must own the doctor profile and be associated with the clinic

### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
| `clinicUuid` | string (UUID) | Clinic UUID |

### Response `201`
Address object created from clinic data.

### Errors
| Code | HTTP | Description |
|------|------|-------------|
| `ERR_FORBIDDEN_001` | 403 | Not associated with this clinic |
| `ERR_NOT_FOUND_001` | 404 | Clinic not found |
