# ایمپورت پزشکان نظام پزشکی به کلینیک‌پرو و مدیریت مالکیت پروفایل > سند فنی این سند سناریو و پیاده‌سازیِ انجام‌شده را برای خواندن آسان جمع می‌کند: از دیتای سازمان نظام پزشکی پزشکان وارد کلینیک‌پرو می‌شوند، و چون این دیتا شماره موبایل ندارد، مکانیزمی برای ذخیره‌ی «بدون‌مالک» و انتقال بعدیِ مالکیت به پزشک واقعی طراحی شده است. --- ## ۱. مسئله و محدودیت‌ها در سیستم فعلی هر پزشک به‌صورت اجباری و یکتا به یک کاربر متصل است و هر کاربر هم شماره موبایلِ یکتا و غیرتهی می‌خواهد. اما رکوردهای نظام پزشکی موبایل ندارند؛ پس نه می‌توان کاربر ساخت و نه پزشکِ بدون کاربر ذخیره کرد. | محدودیت کد فعلی | پیامد | |---|---| | کاربر برای پزشک اجباری است (`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 (احراز هویت پزشک + تأیید ادمین + انتقال مالکیت).