Merge branch 'dev' into main
# Conflicts: # docs/api/doctor.md
This commit is contained in:
+63
-15
@@ -24,7 +24,7 @@ Create a doctor profile for the authenticated user.
|
||||
### Request Body (`application/json`)
|
||||
```json
|
||||
{
|
||||
"title": "دکتر علی احمدی",
|
||||
"title": "علی احمدی",
|
||||
"gender": "male",
|
||||
"medical_system_code": "12345",
|
||||
"degree": "متخصص",
|
||||
@@ -36,7 +36,7 @@ Create a doctor profile for the authenticated user.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `title` | string | ✅ | Full name with title |
|
||||
| `title` | string | ✅ | نام پزشک **بدون** عنوان. پیشوند «دکتر» سمت سرور با `PersianText::stripDoctorTitle()` حذف میشود؛ نمایش عنوان کار لایهٔ UI است. |
|
||||
| `gender` | string | ❌ | `"male"` or `"female"` |
|
||||
| `medical_system_code` | string | ❌ | Nظام پزشکی code |
|
||||
| `degree` | string | ❌ | Academic degree |
|
||||
@@ -51,7 +51,7 @@ Create a doctor profile for the authenticated user.
|
||||
"success": true,
|
||||
"data": {
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"title": "دکتر علی احمدی",
|
||||
"title": "علی احمدی",
|
||||
"gender": "male",
|
||||
"medical_system_code": "12345",
|
||||
"degree": "متخصص",
|
||||
@@ -71,6 +71,7 @@ Create a doctor profile for the authenticated user.
|
||||
| `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 | نام پزشک شمارهتلفن یا مقدار آزمایشی است |
|
||||
|
||||
---
|
||||
|
||||
@@ -92,7 +93,7 @@ Get doctor detail with clinics.
|
||||
"data": {
|
||||
"data": {
|
||||
"uuid": "550e8400-...",
|
||||
"name": "دکتر علی احمدی",
|
||||
"name": "علی احمدی",
|
||||
"gender": "man",
|
||||
"medical_system_code": "12345",
|
||||
"degree": "specialist",
|
||||
@@ -152,7 +153,7 @@ Get doctor detail for clinic owner — only doctors who are members of the authe
|
||||
"data": {
|
||||
"data": {
|
||||
"uuid": "...",
|
||||
"title": "دکتر علی احمدی",
|
||||
"title": "علی احمدی",
|
||||
"specialties": [...],
|
||||
"clinics": [{ "uuid": "...", "name": "کلینیک نور", "address": "...", "telephone": "..." }]
|
||||
}
|
||||
@@ -174,13 +175,26 @@ Get doctor detail for clinic owner — only doctors who are members of the authe
|
||||
|
||||
### Schedule Fields Notes
|
||||
|
||||
| Field | When schedule exists | When no schedule |
|
||||
|-------|---------------------|-----------------|
|
||||
| `free_turn` | نزدیکترین روز/ساعت کاری از امروز (مثلاً «دوشنبه ۹:۰۰–۱۳:۰۰») | «نوبت آزادی موجود نیست» |
|
||||
| `hours_of_work` | خلاصه ساعتهای روزهای فعال با `\|` جداشده | «برنامه کاری تنظیم نشده» |
|
||||
| `active` | `online_booking_enabled && has_active_sessions` | `false` — نوبتدهی غیرفعال |
|
||||
**تجمیع همهٔ برنامهها (2026-07):** این فیلدها روی **همهٔ** برنامههای هفتگی پزشک محاسبه
|
||||
میشوند — برنامهٔ مطب شخصی (`clinic_id IS NULL`) بهعلاوهٔ یک برنامه به ازای هر کلینیک.
|
||||
پزشکی که برنامهٔ شخصی خالی/خاموش ولی برنامهٔ کلینیکیِ فعال دارد، `active=true` میگیرد؛
|
||||
برنامهٔ یک محیط هرگز محیط دیگر را نمیپوشاند.
|
||||
|
||||
> **نوبتدهی آنلاین غیرفعال:** منبعِ فعال/غیرفعال بودن نوبتدهی آنلاین، فیلد `meta.online_booking_enabled` در `WeeklySchedule` پزشک است. اگر `false` باشد، صرفنظر از سشنهای برنامهی هفتگی، `free_turn` همیشه `"نوبتدهی آنلاین غیرفعال است"` و `active` برابر `false` برمیگردد؛ `hours_of_work` در صورت وجود برنامه حفظ میشود. چنین پزشکی در لیست عمومی `GET /api/v1/doctors` نمایش داده میشود ولی پایینتر از پزشکان دارای نوبت قرار میگیرد و با فیلتر `active=1` حذف میشود؛ صفحهی تکی `GET /api/v1/doctor/{slug}` همچنان قابل دسترسی است.
|
||||
| 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` در صورت وجود برنامه حفظ میشود. تا وقتی حتی یک برنامه روشن و
|
||||
> دارای روز فعال باشد، همان مبنا قرار میگیرد.
|
||||
>
|
||||
> چنین پزشکی (همه خاموش) در لیست عمومی `GET /api/v1/doctors` **نمایش داده میشود** — این
|
||||
> اندپوینت فیلتر `active` پیشفرض ندارد — ولی با `bookableRank` پایینتر از پزشکان دارای
|
||||
> نوبت مرتب میشود و تنها با `active=1` از نتایج حذف میگردد. صفحهٔ تکی
|
||||
> `GET /api/v1/doctor/{slug}` همیشه قابل دسترسی است.
|
||||
|
||||
---
|
||||
|
||||
@@ -194,7 +208,7 @@ List doctors with pagination and filters.
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | integer | ❌ | Default: 1 |
|
||||
| `limit` | integer | ❌ | Default: 20 |
|
||||
| `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`) |
|
||||
@@ -222,7 +236,7 @@ List doctors with pagination and filters.
|
||||
"data": [
|
||||
{
|
||||
"uuid": "...",
|
||||
"name": "دکتر علی احمدی",
|
||||
"name": "علی احمدی",
|
||||
"gender": "man",
|
||||
"degree": "specialist",
|
||||
"img": [],
|
||||
@@ -232,19 +246,53 @@ List doctors with pagination and filters.
|
||||
"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"
|
||||
"owner_status": "claimed",
|
||||
"city": [
|
||||
{ "uuid": "7bfb989e-...", "id": "123", "name": "یاسوج", "parent": "23" }
|
||||
],
|
||||
"state": [
|
||||
{ "uuid": "7bfb5705-...", "id": "23", "name": "کهگیلویه و بویراحمد" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"totalRecords": 50,
|
||||
"totalPages": 3,
|
||||
"currentPage": 1
|
||||
"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}`
|
||||
|
||||
Reference in New Issue
Block a user