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، هر ۶۰ ثانیه یک پزشک را مستقیم در کلینیکپرو ذخیره میکند. کندیِ عمدی بهخاطر سقفِ اندپوینت عکسِ نظام پزشکی است (بعد از ~۱۰ درخواست موقتاً بلاک میشود).
- لاگین یکبار با کاربر
0000000000و رمز عبور، دریافت JWT (در ۴۰۱ دوباره لاگین). - گرفتن یک پزشک از منبع: فایل
doctors.jsonموجود، یا خزش زندهی یک شهر. - نرمالایز و نگاشت به شناسههای مرجع (تخصص/استان/شهر).
POSTبه اندپوینت ایمپورت، دریافتuuidپزشک.- دانلود عکس پروفایل از نظام پزشکی، آپلود به کلینیکپرو، و اتصال به پروفایل.
- مکث ۶۰ ثانیه و تکرار؛ کدهای انجامشده برای resume ذخیره میشوند.
- در پایان، کاربر
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 (احراز هویت پزشک + تأیید ادمین + انتقال مالکیت).