From a5e3408e85e3f665657ff3082ebe5661f31e3760 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 11 Jul 2026 09:03:21 +0330 Subject: [PATCH] feat(docs): add API documentation for doctor import from IRIMC --- docs/api/doctor-import.md | 129 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 docs/api/doctor-import.md diff --git a/docs/api/doctor-import.md b/docs/api/doctor-import.md new file mode 100644 index 00000000..2c468d6a --- /dev/null +++ b/docs/api/doctor-import.md @@ -0,0 +1,129 @@ +# Doctor Import (IRIMC) API + +> **Endpoint:** `POST /api/v1/admin/doctors/import` +> **Permission:** `ROLE_ADMIN` +> **Controller:** `App\Admin\Controller\AdminApiController::importDoctor` + +وارد کردن یک پزشک از سازمان نظام پزشکی (`membersearch.irimc.org`) **بدون شماره موبایل**. +برخلاف `POST /api/v1/admin/doctors` (که موبایل معتبر ایرانی می‌خواهد)، این اندپوینت برای +هر پزشک یک **کاربر جانشینِ غیرفعال** با شناسهٔ مصنوعی می‌سازد و پروفایل را در وضعیت +`unclaimed` ذخیره می‌کند تا بعداً به پزشک واقعی منتقل شود. + +مصرف‌کنندهٔ اصلی: خزندهٔ پایتون (`clinicpro-crawler/pipeline.py`) که با کاربر «مالک +سیستمی» (`0000000000`) لاگین می‌کند و هر ~۶۰ ثانیه یک پزشک را می‌فرستد. + +--- + +## مدل مالکیت (ستون‌های جدید `doctors`) + +| ستون | مقدار هنگام ایمپورت | توضیح | +|---|---|---| +| `owner_status` | `unclaimed` | `claimed` \| `unclaimed` \| `pending_transfer` | +| `source` | `irimc` | `manual` (پیش‌فرض رکوردهای قدیمی) \| `irimc` | +| `source_ref` | `profile_url` | شناسهٔ رکورد مبدأ برای ممیزی | +| `managed_by` | id کاربر ادمینِ فراخوان | کاربری که پروفایل را مدیریت می‌کند | +| `claimed_at` | `null` | زمان انتقال مالکیت (هنگام claim پر می‌شود) | + +رکوردهای موجود در migration مقدار `manual` + `claimed` می‌گیرند تا رفتارشان تغییر نکند. + +--- + +## idempotency + +کلید یکتای منطقی: `(source, medical_system_code)`. + +- اگر پزشکی با همان `source`+`medical_system_code` وجود نداشته باشد → **ساخته** می‌شود (`201`). +- اگر وجود داشته باشد و `owner_status != claimed` → **به‌روزرسانی** می‌شود (`200`). +- اگر وجود داشته باشد و `owner_status == claimed` → **رد** می‌شود (`200`, `skipped: "claimed"`) + تا دادهٔ مالک واقعی بازنویسی نشود. + +--- + +## Request + +```json +{ + "name": "دکتر فرخنده حسینی", + "medical_system_code": "145657", + "source": "irimc", + "source_ref": "https://membersearch.irimc.org/member/profile?id=…", + "gender": "woman", + "degree": "general", + "info": "دکترای حرفه‌ای پزشکی", + "specialties": [1], + "states": [23], + "cities": [123] +} +``` + +| فیلد | الزامی | توضیح | +|---|:---:|---| +| `name` | ✅ | نام کامل پزشک | +| `medical_system_code` | ✅ | کد نظام پزشکی (کلید idempotency) | +| `source` | — | پیش‌فرض `irimc` | +| `source_ref` | — | `profile_url` یا شناسهٔ مبدأ | +| `gender` | — | `man` \| `woman` | +| `degree` | — | `general` \| `expert` \| `specialist` \| `subspecialistplus` | +| `info` | — | متن تخصص/توضیح | +| `specialties` / `states` / `cities` | — | آرایهٔ **شناسه‌های مرجع** (id) | + +> `gender`/`degree` باید با ثابت‌های `Doctor::GENDERS` / `Doctor::DEGREES` سازگار باشند. +> `specialties`/`states`/`cities` اگر داده شوند مجموعهٔ فعلی را **جایگزین** می‌کنند. + +--- + +## Response + +### `201 Created` (ساخته شد) +```json +{ "success": true, "data": { "uuid": "…", "created": true } } +``` + +### `200 OK` (به‌روزرسانی شد) +```json +{ "success": true, "data": { "uuid": "…", "created": false } } +``` + +### `200 OK` (رد به‌دلیل تصاحب‌شده) +```json +{ "success": true, "data": { "uuid": "…", "created": false, "skipped": "claimed" } } +``` + +### `422` (اعتبارسنجی) +```json +{ "success": false, "data": null, "errors": [ { "code": "…", "message": "کد نظام پزشکی الزامی است", "field": "medical_system_code" } ] } +``` + +--- + +## عکس پروفایل + +پس از ایمپورت، عکس با دو مرحله اضافه می‌شود (همان مسیر پزشکِ عادی): + +1. `POST /file/upload/clinic_pro/doctor/field_image` — بدنه: باینری خام؛ هدر + `Content-Disposition: attachment; filename="145657.jpg"`. پاسخ: `{ fid, uuid, url, filename, filemime, filesize }`. +2. `PATCH /api/v1/doctor/{uuid}` با بدنهٔ `{ "image_data": <پاسخ مرحلهٔ ۱> }` تا به + آرایهٔ `images` پروفایل افزوده شود. + +--- + +## کاربر سیستمی و چرخهٔ عمر + +```bash +# ساخت/فعال‌سازی کاربر مالک سیستمی (کرالر با این لاگین می‌کند) +php bin/console app:system-owner 0000000000 --password= --activate + +# غیرفعال‌سازی پس از پایان کار (کرالر هم با --deactivate-on-finish این را از طریق API انجام می‌دهد) +php bin/console app:system-owner 0000000000 --deactivate +``` + +کاربر باید `ROLE_ADMIN` و `status=1` داشته باشد تا لاگینِ رمزی (`POST /api/v1/user/login`) +و فراخوانی این اندپوینت ممکن باشد. + +> ⚠️ **captcha:** مسیر `/api/v1/user/login` از `CaptchaGuard` رد می‌شود و این guard وقتی +> `ALTCHA_ENABLED=true` باشد (مقدار فعلی `.env`) یک payloadِ altcha می‌خواهد. برای اجرای +> بدونِ‌مرورگرِ کرالر یکی از این‌ها لازم است: +> ۱) روی همان سرور `ALTCHA_ENABLED=false` در `.env.local` (ساده‌ترین برای dev)، یا +> ۲) افزودن یک استثنا در `PasswordAuthenticator` که برای کاربر مالک سیستمی captcha را رد کند، +> یا ۳) لاگین سرویس با یک هدر سرّیِ مورد اعتماد. تا وقتی این حل نشود، لاگین کرالر با ۴۲۲ +> (`ERR_CAPTCHA_001`) رد می‌شود.