- Removed the "دکتر" prefix from doctor names in various components and API responses to ensure consistency and clarity. - Updated the AppointmentDetailPage, CommentsPage, DashboardPage, RatingsPage, SecretariesPage, and other relevant files to reflect the changes in doctor name formatting. - Adjusted API documentation to align with the new naming conventions. - Implemented validation to prevent the creation of clinics without a name and restricted users to a single clinic. - Added tests to verify that doctor names are stored without titles and that clinic creation adheres to the new validation rules.
157 lines
7.7 KiB
Markdown
157 lines
7.7 KiB
Markdown
# Doctor Profile Claim API (تصاحب پروفایل پزشک ایمپورتشده)
|
|
|
|
> **Controller:** `App\Doctor\Controller\DoctorClaimController` — منطق در `App\Doctor\Service\DoctorClaimService`
|
|
> **مصرفکننده:** سایت عمومی Nobat724 (همهٔ دامنهها) + پنل ادمین
|
|
|
|
پزشکِ ایمپورتشده از نظام پزشکی (`owner_status = unclaimed`) توسط پزشک واقعی تصاحب میشود.
|
|
احراز هویت سمت سرور با **API.ir** انجام میشود (شاهکار: تطبیق موبایل↔کدملی؛ PersonInfo: تطبیق
|
|
کدملی+تاریخ تولد و نام). هیچ درخواستی از فرانت به API.ir نمیرود و توکن API.ir هرگز به کلاینت
|
|
نمیرسد. claim پس از تطبیق موفق **خودکار** نهایی میشود (بدون approve ادمین — تصمیم مستند در
|
|
`.claude/prompt/irimc-import-complete.md` §۲.۲).
|
|
|
|
## چرخهٔ وضعیت
|
|
|
|
```
|
|
unclaimed ──claim/transfer شروع──▶ pending_transfer ──موفق──▶ claimed
|
|
▲ │شکست تطبیق/خطای استعلام
|
|
└───────────────────────────────────┘ (برگشت، قابل تلاش مجدد)
|
|
```
|
|
|
|
هر تلاش یک رکورد ممیزی در `doctor_claim_requests` میسازد — کد ملی فقط **hash sha256** و
|
|
موبایل فقط **mask شده** ذخیره میشود؛ هیچ دادهٔ هویتی خام در DB یا لاگ نمیماند.
|
|
|
|
---
|
|
|
|
## GET `/api/v1/doctor/{uuid}/claim-info`
|
|
|
|
**Permission:** عمومی (بدون JWT) — فقط برای رندر دکمهٔ «آیا شما این پزشک هستید؟»
|
|
|
|
### Response `200`
|
|
```json
|
|
{ "success": true, "data": { "claimable": true, "owner_status": "unclaimed" } }
|
|
```
|
|
|
|
| کد | حالت |
|
|
|---|---|
|
|
| `404` | پزشک یافت نشد |
|
|
|
|
---
|
|
|
|
## POST `/api/v1/doctor/{uuid}/claim`
|
|
|
|
**Permission:** `IS_AUTHENTICATED_FULLY` — کاربر با OTP لاگین شده (موبایلش تأییدشده است)
|
|
**Rate limit:** limiter `doctor_claim` — ۵ تلاش در ساعت بهازای هر (کاربر، پزشک)
|
|
**Captcha:** ALTCHA — بدنه باید payload کپچا بفرستد (`CaptchaGuard::assertValid`)؛ در dev با `ALTCHA_ENABLED=false` بیاثر است، در prod اجباری. خطا → `ERR_CAPTCHA_001` (۴۲۲).
|
|
|
|
### Request
|
|
```json
|
|
{
|
|
"national_code": "0010007700",
|
|
"birth_date": "1371/1/1",
|
|
"first_name": "فرخنده",
|
|
"last_name": "حسینی",
|
|
"mobile": "09121234567",
|
|
"altcha": "<payload کپچا>"
|
|
}
|
|
```
|
|
|
|
| فیلد | الزامی | قاعده |
|
|
|---|:---:|---|
|
|
| `national_code` | ✅ | ۱۰ رقم (ارقام فارسی پذیرفته و نرمال میشوند) |
|
|
| `birth_date` | ✅ | شمسی `Y/m/d` |
|
|
| `first_name` / `last_name` | ✅ | با هویت ثبت احوال و نام پروفایل تطبیق داده میشود (نرمالسازی ي/ی، ك/ک، نیمفاصله — `PersianText`) |
|
|
| `mobile` | ❌ | اگر داده شود، باید با موبایل حساب کاربری یکی باشد وگرنه `422 ERR_CONFLICT_001`؛ اگر خالی باشد از موبایل کاربر لاگینشده استفاده میشود |
|
|
| `altcha` | prod | payload کپچای ALTCHA |
|
|
|
|
### مراحل سرور (اتمیک/ضد race)
|
|
|
|
1. قفل `PESSIMISTIC_WRITE` روی ردیف پزشک → اگر `unclaimed` نبود `409`؛ اگر کاربر از قبل پزشکی دارد `409`؛ اگر کد ملی متعلق به کاربر دیگری است `409` — سپس `pending_transfer` + رکورد ممیزی (تراکنش کوتاه، بدون فراخوان خارجی داخل قفل).
|
|
2. شاهکار (`ApiIrService::shahkarMatch`) — تطبیق موبایل کاربر با کد ملی. اگر API.ir پیکربندی نشده باشد، این گام skip و مبنا موبایلِ OTP-تأییدشده است.
|
|
3. `PersonInfo` — تطبیق کدملی+تاریخ تولد؛ `alive=false` → رد.
|
|
4. تطبیق نام: ورودی کاربر ↔ هویت تأییدشده ↔ نام پروفایل (بدون پیشوند «دکتر»).
|
|
5. نهاییسازی اتمیک: `user_id` → کاربر واقعی، `ROLE_DOCTOR`، `national_code_verified=true`، `owner_status=claimed`، حذف امنِ کاربر جانشین (فقط با `ROLE_UNCLAIMED_DOCTOR` و بدون پزشک دیگر).
|
|
6. پیامک خوشآمد (تمپلیت `welcome`، async).
|
|
|
|
> شکست در گامهای ۲-۴ پروفایل را به `unclaimed` برمیگرداند تا پزشک واقعی بتواند دوباره تلاش کند.
|
|
> نوبتدهی (`active_doctor_appointment`) خاموش میماند تا مالک جدید برنامهٔ کاری تعریف کند.
|
|
|
|
### Response `200`
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {
|
|
"status": "claimed",
|
|
"claim": { "uuid": "…" },
|
|
"doctor": { "uuid": "…", "name": "فرخنده حسینی" }
|
|
}
|
|
}
|
|
```
|
|
|
|
### Errors
|
|
| کد | HTTP | حالت |
|
|
|---|---|---|
|
|
| `ERR_AUTH_001` | 401 | بدون لاگین |
|
|
| `ERR_NOT_FOUND_001` | 404 | پزشک یافت نشد |
|
|
| `ERR_CONFLICT_001` | 409 | پروفایل قابل تصاحب نیست / کاربر پزشک دیگری دارد / درخواست همزمان دیگری در جریان است |
|
|
| `ERR_PROFILE_001` | 409 | کد ملی قبلاً برای کاربر دیگری ثبت شده است |
|
|
| `ERR_VALIDATION_001/002` | 422 | کد ملی/تاریخ/نام نامعتبر |
|
|
| `ERR_IDENTITY_001` | 422 | عدم تطبیق هویت (شاهکار/ثبت احوال/نام) — پیام عمومی، ضد enumeration |
|
|
| `ERR_RATE_LIMIT_001` | 429 | عبور از سقف تلاش |
|
|
| `ERR_EXTERNAL_001` | 502 | خطا/تایماوت API.ir |
|
|
| `ERR_EXTERNAL_002` | 503 | API.ir پیکربندی نشده (env) |
|
|
|
|
---
|
|
|
|
## POST `/api/v1/admin/doctors/{uuid}/transfer`
|
|
|
|
انتقال دستی (ابزار پشتیبانی ادمین) — مستند کامل در `docs/api/doctor-import.md`.
|
|
**Permission:** `ROLE_ADMIN`. بدنه `{ "mobile": "09…" }`. همان نهاییسازی claim را اجرا میکند
|
|
و رکورد ممیزی با `verification_method="admin_manual"` میسازد.
|
|
|
|
---
|
|
|
|
## GET `/api/v1/admin/doctor-claims`
|
|
|
|
**Permission:** `ROLE_ADMIN` — لیست ممیزی درخواستهای claim برای پشتیبانی عملیاتی.
|
|
|
|
| Query | پیشفرض | توضیح |
|
|
|---|---|---|
|
|
| `status` | همه | `pending` \| `completed` \| `failed` |
|
|
| `page` / `limit` | 1 / 20 (سقف 50) | صفحهبندی استاندارد |
|
|
|
|
### Response `200` (paginated)
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": [
|
|
{
|
|
"uuid": "…",
|
|
"status": "completed",
|
|
"doctor": { "uuid": "…", "name": "فرخنده حسینی" },
|
|
"mobile_masked": "0912***4567",
|
|
"verification_method": "apiir_personinfo+shahkar",
|
|
"failure_reason": null,
|
|
"created_at": 1783750000,
|
|
"completed_at": 1783750040
|
|
}
|
|
],
|
|
"meta": { "totalRecords": 1, "totalPages": 1, "currentPage": 1 }
|
|
}
|
|
```
|
|
|
|
`failure_reason` فارسی و انسانیخوان است تا ادمین بدون خواندن لاگ سرور علت شکست را ببیند.
|
|
|
|
---
|
|
|
|
## env های مرتبط
|
|
|
|
| متغیر | نقش |
|
|
|---|---|
|
|
| `APIIR_*` (baseUrl/token موجود `ApiIrService`) | استعلام شاهکار و PersonInfo |
|
|
| `CRAWLER_SERVICE_TOKEN` | فقط برای لاگین سرویسی کرالر (`doctor-import.md`) — ربطی به claim ندارد |
|
|
|
|
## تستها
|
|
|
|
`tests/Doctor/DoctorClaimTest.php` (۱۱ سناریو، API.ir همیشه mock — هیچ تستی به سرویس واقعی
|
|
درخواست نمیزند) و `tests/Shared/PersianTextTest.php` (نرمالسازی نام).
|