feat(docs): add scenario documentation for importing doctors from IRIMC and managing profile ownership
This commit is contained in:
@@ -6,8 +6,9 @@
|
||||
|
||||
وارد کردن یک پزشک از سازمان نظام پزشکی (`membersearch.irimc.org`) **بدون شماره موبایل**.
|
||||
برخلاف `POST /api/v1/admin/doctors` (که موبایل معتبر ایرانی میخواهد)، این اندپوینت برای
|
||||
هر پزشک یک **کاربر جانشینِ غیرفعال** با شناسهٔ مصنوعی میسازد و پروفایل را در وضعیت
|
||||
`unclaimed` ذخیره میکند تا بعداً به پزشک واقعی منتقل شود.
|
||||
هر پزشک یک **کاربر جانشینِ غیرفعال** با شناسهٔ مصنوعی و نقشِ اختصاصی `ROLE_UNCLAIMED_DOCTOR`
|
||||
میسازد و پروفایل را در وضعیت `unclaimed` ذخیره میکند تا بعداً به پزشک واقعی منتقل شود.
|
||||
این نقش، هم کاربر جانشین را قابلشناسایی میکند و هم مبنای حذفِ امنِ او پس از انتقال است.
|
||||
|
||||
مصرفکنندهٔ اصلی: خزندهٔ پایتون (`clinicpro-crawler/pipeline.py`) که با کاربر «مالک
|
||||
سیستمی» (`0000000000`) لاگین میکند و هر ~۶۰ ثانیه یک پزشک را میفرستد.
|
||||
@@ -107,6 +108,42 @@
|
||||
|
||||
---
|
||||
|
||||
## انتقال مالکیت به پزشک واقعی
|
||||
|
||||
> **Endpoint:** `POST /api/v1/admin/doctors/{uuid}/transfer` — permission `ROLE_ADMIN`
|
||||
|
||||
مالکیت یک پروفایلِ `unclaimed` را به پزشک واقعی منتقل میکند.
|
||||
|
||||
```json
|
||||
// Request
|
||||
{ "mobile": "09120000000" }
|
||||
```
|
||||
|
||||
مراحل اتمیک:
|
||||
|
||||
1. کاربر واقعی بر پایهٔ `mobile` پیدا یا ساخته میشود (باید موبایل معتبر ایران باشد).
|
||||
2. اگر کاربر واقعی از قبل صاحب پزشک دیگری باشد → `409`.
|
||||
3. `user_id` پروفایل به کاربر واقعی تغییر میکند، `ROLE_DOCTOR` به او داده میشود،
|
||||
`owner_status = claimed` و `claimed_at = now` و `managed_by = null` میشود.
|
||||
4. **کاربر جانشین حذف میشود** — فقط اگر واقعاً `ROLE_UNCLAIMED_DOCTOR` داشته باشد و
|
||||
دیگر هیچ پزشکی به او وصل نباشد (حذف امن).
|
||||
|
||||
```json
|
||||
// Response 200
|
||||
{ "success": true, "data": {
|
||||
"uuid": "…", "owner_status": "claimed",
|
||||
"transferred_to": "09120000000", "placeholder_deleted": true
|
||||
} }
|
||||
```
|
||||
|
||||
| کد | حالت |
|
||||
|---|---|
|
||||
| `404` | پزشک یافت نشد |
|
||||
| `409` | قبلاً `claimed` است، یا کاربر مقصد پزشک دیگری دارد |
|
||||
| `422` | موبایل نامعتبر |
|
||||
|
||||
---
|
||||
|
||||
## کاربر سیستمی و چرخهٔ عمر
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# ایمپورت پزشکان نظام پزشکی به کلینیکپرو و مدیریت مالکیت پروفایل
|
||||
|
||||
> سند فنی
|
||||
|
||||
این سند سناریو و پیادهسازیِ انجامشده را برای خواندن آسان جمع میکند: از دیتای سازمان نظام پزشکی پزشکان وارد کلینیکپرو میشوند، و چون این دیتا شماره موبایل ندارد، مکانیزمی برای ذخیرهی «بدونمالک» و انتقال بعدیِ مالکیت به پزشک واقعی طراحی شده است.
|
||||
|
||||
---
|
||||
|
||||
## ۱. مسئله و محدودیتها
|
||||
|
||||
در سیستم فعلی هر پزشک بهصورت اجباری و یکتا به یک کاربر متصل است و هر کاربر هم شماره موبایلِ یکتا و غیرتهی میخواهد. اما رکوردهای نظام پزشکی موبایل ندارند؛ پس نه میتوان کاربر ساخت و نه پزشکِ بدون کاربر ذخیره کرد.
|
||||
|
||||
| محدودیت کد فعلی | پیامد |
|
||||
|---|---|
|
||||
| کاربر برای پزشک اجباری است (`user_id NOT NULL`) | پزشک بدون کاربر ذخیره نمیشود |
|
||||
| رابطه پزشک↔کاربر یکتاست (`unique user_id`) | «همه به یک کاربر» ممکن نیست |
|
||||
| موبایل کاربر اجباری و یکتاست | بدون موبایل کاربر ساخته نمیشود |
|
||||
| اندپوینت ساخت پزشک موبایل معتبر میخواهد | برای ایمپورت انبوه مناسب نیست |
|
||||
|
||||
---
|
||||
|
||||
## ۲. مدل مالکیت پروفایل
|
||||
|
||||
هر پروفایل یکی از سه وضعیت را دارد:
|
||||
|
||||
- **unclaimed (بدونمالک):** ایمپورتشده، هنوز به پزشک واقعی وصل نیست؛ نوبتدهی آنلاین خاموش.
|
||||
- **pending_transfer (در انتظار انتقال):** پزشک واقعی درخواست تصاحب داده و منتظر تأیید است.
|
||||
- **claimed (تصاحبشده):** مالکیت به کاربر واقعی پزشک منتقل شده و کنترل کامل دارد.
|
||||
|
||||
---
|
||||
|
||||
## ۳. راهحل انتخابشده و دلیل آن
|
||||
|
||||
بهجای «اتصال همهی پزشکان به یک کاربر سیستمی» (که قید یکتای `user_id` آن را نقض میکند)، مدل «کاربر جانشین بهازای هر پزشک» پیاده شد: برای هر پزشکِ ایمپورتشده یک کاربرِ غیرفعال با شناسهی مصنوعی ساخته میشود. این کار هیچکدام از کوئریهای موجود روی جدول `users` را نمیشکند.
|
||||
|
||||
- این کاربر جانشین نقشِ اختصاصی `ROLE_UNCLAIMED_DOCTOR` میگیرد؛ این نقش هم او را قابلشناسایی میکند و هم مبنای حذفِ امنِ او پس از انتقال است.
|
||||
- پس از انتقال مالکیت به پزشک واقعی، **کاربر جانشین (فیک) حذف میشود** — فقط اگر واقعاً همان نقش را داشته باشد و دیگر هیچ پزشکی به او وصل نباشد.
|
||||
- کاربر سیستمی `0000000000` نقش «مدیر و فراخوانِ API» را دارد (در ستون `managed_by` ثبت میشود)، نه صاحبِ ردیفهای پزشک.
|
||||
|
||||
---
|
||||
|
||||
## ۴. تغییرات بکاند (clinicpro)
|
||||
|
||||
ستونهای جدید جدول `doctors` (بههمراه migration؛ رکوردهای موجود بدون تغییر میمانند):
|
||||
|
||||
| ستون | مقدار هنگام ایمپورت | توضیح |
|
||||
|---|---|---|
|
||||
| `owner_status` | `unclaimed` | وضعیت مالکیت |
|
||||
| `source` | `irimc` | منبع رکورد |
|
||||
| `source_ref` | `profile_url` | شناسهی مبدأ برای ممیزی |
|
||||
| `managed_by` | id کاربر سیستمی | مدیرِ پروفایل |
|
||||
| `claimed_at` | `null` | زمان انتقال مالکیت |
|
||||
|
||||
**اندپوینت جدید:** `POST /api/v1/admin/doctors/import` (فقط `ROLE_ADMIN`).
|
||||
|
||||
- بدون موبایل کار میکند؛ برای هر پزشک کاربر جانشین میسازد و پروفایل را `unclaimed` ذخیره میکند.
|
||||
- idempotent روی `(source, medical_system_code)`: اجرای مجدد رکورد را بهروزرسانی میکند نه تکراری.
|
||||
- پروفایلِ `claimed` را بازنویسی نمیکند (مالک واقعی اولویت دارد).
|
||||
|
||||
**دستور کنسول:** `app:system-owner` برای ساخت/فعال/غیرفعال کردن کاربر `0000000000`.
|
||||
|
||||
**اندپوینت انتقال مالکیت:** `POST /api/v1/admin/doctors/{uuid}/transfer` با بدنهی `{ mobile }`: پروفایل را به کاربر واقعی میدهد، `ROLE_DOCTOR` اضافه میکند، وضعیت را `claimed` میکند و کاربر جانشین را حذف میکند.
|
||||
|
||||
---
|
||||
|
||||
## ۵. جریان کرالر (pipeline.py)
|
||||
|
||||
کرالر بهجای فقط ذخیرهی JSON، هر ۶۰ ثانیه یک پزشک را مستقیم در کلینیکپرو ذخیره میکند. کندیِ عمدی بهخاطر سقفِ اندپوینت عکسِ نظام پزشکی است (بعد از ~۱۰ درخواست موقتاً بلاک میشود).
|
||||
|
||||
1. لاگین یکبار با کاربر `0000000000` و رمز عبور، دریافت JWT (در ۴۰۱ دوباره لاگین).
|
||||
2. گرفتن یک پزشک از منبع: فایل `doctors.json` موجود، یا خزش زندهی یک شهر.
|
||||
3. نرمالایز و نگاشت به شناسههای مرجع (تخصص/استان/شهر).
|
||||
4. `POST` به اندپوینت ایمپورت، دریافت `uuid` پزشک.
|
||||
5. دانلود عکس پروفایل از نظام پزشکی، آپلود به کلینیکپرو، و اتصال به پروفایل.
|
||||
6. مکث ۶۰ ثانیه و تکرار؛ کدهای انجامشده برای resume ذخیره میشوند.
|
||||
7. در پایان، کاربر `0000000000` از طریق API غیرفعال میشود (با `--deactivate-on-finish`).
|
||||
|
||||
دو ماژول اضافهشده: `clinicpro_client.py` (کلاینت API) و `pipeline.py` (ارکستراتور).
|
||||
|
||||
---
|
||||
|
||||
## ۶. نحوهی اجرا
|
||||
|
||||
ساخت و فعالسازی کاربر سیستمی در بکاند:
|
||||
|
||||
```bash
|
||||
ddev exec php bin/console app:system-owner 0000000000 --password=SECRET --activate
|
||||
```
|
||||
|
||||
اجرای migration:
|
||||
|
||||
```bash
|
||||
ddev exec php bin/console doctrine:migrations:migrate
|
||||
```
|
||||
|
||||
اجرای کرالر از فایل، با عکس و غیرفعالسازی در پایان:
|
||||
|
||||
```bash
|
||||
CLINICPRO_PASSWORD=SECRET python pipeline.py --mode file --file output/یاسوج/doctors.json --deactivate-on-finish
|
||||
```
|
||||
|
||||
یا خزش زندهی یک شهر:
|
||||
|
||||
```bash
|
||||
CLINICPRO_PASSWORD=SECRET python pipeline.py --mode crawl --city یاسوج --province "کهگیلویه و بویراحمد"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ۷. نکتهی مهم: captcha در لاگین
|
||||
|
||||
مسیر لاگین `/api/v1/user/login` از `CaptchaGuard` رد میشود و چون `ALTCHA_ENABLED=true` است، یک payload کپچا میخواهد. تا وقتی یکی از اینها انجام نشود، لاگین کرالر با خطای `ERR_CAPTCHA_001` رد میشود:
|
||||
|
||||
- گذاشتن `ALTCHA_ENABLED=false` در `.env.local` روی سرور کرالر (سادهترین برای dev)، یا
|
||||
- افزودن استثنا در `PasswordAuthenticator` که برای کاربر سیستمی کپچا را رد کند، یا
|
||||
- لاگین سرویس با یک هدر سرّیِ مورد اعتماد.
|
||||
|
||||
---
|
||||
|
||||
## ۸. فایلهای ساخته/تغییریافته
|
||||
|
||||
| فایل | کار |
|
||||
|---|---|
|
||||
| `clinicpro/src/Doctor/Entity/Doctor.php` | فیلدهای مالکیت + متد انتقال |
|
||||
| `clinicpro/migrations/Version20260711120000.php` | افزودن ستونها و ایندکسها |
|
||||
| `clinicpro/src/Admin/Controller/AdminApiController.php` | اندپوینتهای `importDoctor` و `transferDoctor` |
|
||||
| `clinicpro/src/Auth/Command/SystemOwnerCommand.php` | دستور کاربر سیستمی |
|
||||
| `clinicpro/docs/api/doctor-import.md` | مستند API |
|
||||
| `clinicpro-crawler/clinicpro_client.py` | کلاینت API کلینیکپرو |
|
||||
| `clinicpro-crawler/pipeline.py` | ارکستراتور ایمپورت زنده |
|
||||
|
||||
---
|
||||
|
||||
## ۹. مراحل بعدی پیشنهادی
|
||||
|
||||
- اجرای migration و یک ایمپورت آزمایشی با `--limit 2` روی محیط خودت برای تست سرتاسری.
|
||||
- حل موضوع کپچا برای اجرای headless کرالر.
|
||||
- در فاز بعد: فرایند «تصاحب پروفایل» در Nobat724 (احراز هویت پزشک + تأیید ادمین + انتقال مالکیت).
|
||||
Reference in New Issue
Block a user