Files
clinicpro/docs/scenarios/ایمپورت-پزشکان-نظام-پزشکی.md

8.6 KiB

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

سند فنی

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


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

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

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


۶. نحوه‌ی اجرا

ساخت و فعال‌سازی کاربر سیستمی در بک‌اند:

ddev exec php bin/console app:system-owner 0000000000 --password=SECRET --activate

اجرای migration:

ddev exec php bin/console doctrine:migrations:migrate

اجرای کرالر از فایل، با عکس و غیرفعال‌سازی در پایان:

CLINICPRO_PASSWORD=SECRET python pipeline.py --mode file --file output/یاسوج/doctors.json --deactivate-on-finish

یا خزش زنده‌ی یک شهر:

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