# ایمپورت پزشکان نظام پزشکی به کلینیک‌پرو و مدیریت مالکیت پروفایل

> سند فنی

این سند سناریو و پیاده‌سازیِ انجام‌شده را برای خواندن آسان جمع می‌کند: از دیتای سازمان نظام پزشکی پزشکان وارد کلینیک‌پرو می‌شوند، و چون این دیتا شماره موبایل ندارد، مکانیزمی برای ذخیره‌ی «بدون‌مالک» و انتقال بعدیِ مالکیت به پزشک واقعی طراحی شده است.

---

## ۱. مسئله و محدودیت‌ها

در سیستم فعلی هر پزشک به‌صورت اجباری و یکتا به یک کاربر متصل است و هر کاربر هم شماره موبایلِ یکتا و غیرتهی می‌خواهد. اما رکوردهای نظام پزشکی موبایل ندارند؛ پس نه می‌توان کاربر ساخت و نه پزشکِ بدون کاربر ذخیره کرد.

| محدودیت کد فعلی | پیامد |
|---|---|
| کاربر برای پزشک اجباری است (`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 (احراز هویت پزشک + تأیید ادمین + انتقال مالکیت).
