feat: enhance doctor import process with source profile ID for improved idempotency and deduplication
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
| `owner_status` | `unclaimed` | `claimed` \| `unclaimed` \| `pending_transfer` |
|
||||
| `source` | `irimc` | `manual` (پیشفرض رکوردهای قدیمی) \| `irimc` |
|
||||
| `source_ref` | `profile_url` | شناسهٔ رکورد مبدأ برای ممیزی |
|
||||
| `source_profile_id` | UUID داخل `source_ref` | شناسهٔ authoritative پروفایل نظام پزشکی؛ کلید اصلی idempotency |
|
||||
| `managed_by` | id کاربر ادمینِ فراخوان | کاربری که پروفایل را مدیریت میکند |
|
||||
| `claimed_at` | `null` | زمان انتقال مالکیت (هنگام claim پر میشود) |
|
||||
|
||||
@@ -31,19 +32,31 @@
|
||||
|
||||
## idempotency
|
||||
|
||||
کلید یکتای `(source, medical_system_code)` — از این نسخه **در سطح دیتابیس** هم unique است
|
||||
(`uniq_doctors_source_code`، migration `Version20260711150000`)؛ درخواست همزمانِ همان پزشک
|
||||
**اولویت کلید:** اول `source_profile_id` (شناسهٔ authoritative پروفایل نظام پزشکی، استخراجشده
|
||||
از UUID داخل `source_ref`)، سپس `(source, medical_system_code)`. هر دو در سطح دیتابیس unique
|
||||
هستند (`uniq_doctors_source_profile` و `uniq_doctors_source_code`)؛ درخواست همزمانِ همان پزشک
|
||||
با retry داخلی به مسیر update میرود و هرگز رکورد تکراری نمیسازد.
|
||||
|
||||
- اگر پزشکی با همان `source`+`medical_system_code` وجود نداشته باشد → **ساخته** میشود (`201`).
|
||||
- اگر وجود داشته باشد و `owner_status != claimed` → **بهروزرسانی** میشود (`200`).
|
||||
- اگر وجود داشته باشد و `owner_status == claimed` → **رد** میشود (`200`, `skipped: "claimed"`)
|
||||
تا دادهٔ مالک واقعی بازنویسی نشود.
|
||||
- **همان پروفایل با کد متفاوت:** اگر پزشکی با همان `source`+`source_profile_id` وجود داشته
|
||||
باشد → **بهروزرسانی** میشود، حتی اگر `medical_system_code` عوض شده باشد. یک پروفایل irimc
|
||||
هرگز دو رکورد نمیسازد (تست: `DoctorImportTest::testSameProfileIdWithDifferentCodeUpdatesInsteadOfDuplicating`).
|
||||
- اگر پروفایل شناسه نداشت (قالب `source_ref` ناشناخته) → روی `(source, medical_system_code)`
|
||||
fallback میشود؛ رفتار رکوردهای `manual`/`seed` بدون تغییر میماند (چند `NULL` در
|
||||
یونیکایندکس MariaDB تداخل نمیگیرد).
|
||||
- اگر پزشکی نبود → **ساخته** میشود (`201`).
|
||||
- اگر بود و `owner_status != claimed` → **بهروزرسانی** (`200`).
|
||||
- اگر بود و `owner_status == claimed` → **رد** (`200`, `skipped: "claimed"`) تا دادهٔ مالک
|
||||
واقعی بازنویسی نشود.
|
||||
- **تکرار بین منابع:** اگر پزشکی با همان `medical_system_code` ولی `source` متفاوت
|
||||
(مثلاً ثبت دستی در پنل) وجود داشته باشد → **رد** میشود (`200`, `skipped: "duplicate"`)؛
|
||||
نه رکورد جدیدی ساخته میشود و نه رکورد موجود بازنویسی میشود. `uuid` همان رکورد موجود
|
||||
برگردانده میشود (تست: `DoctorImportTest::testManualDoctorWithSameCodeIsNeverDuplicated`).
|
||||
|
||||
> **پروفایلهای همنامِ متمایز ادغام نمیشوند:** دو `source_profile_id` متفاوت یعنی irimc
|
||||
> آنها را دو پزشک میداند (ممکن است دو نفر واقعی باشند). گام
|
||||
> `app:doctors:repair --only=report-suspected-duplicates` خوشههای مشکوک (نام+تخصص+شهرِ
|
||||
> یکسان، profile id متفاوت) را فقط **گزارش** میکند؛ تصمیم ادغام انسانی است.
|
||||
|
||||
---
|
||||
|
||||
## Request
|
||||
@@ -66,9 +79,9 @@
|
||||
| فیلد | الزامی | توضیح |
|
||||
|---|:---:|---|
|
||||
| `name` | ✅ | نام کامل پزشک — پیشوند «دکتر» **هنگام ذخیره حذف** میشود (کنوانسیون: نام بدون عنوان؛ UI خودش «دکتر» را جلو میگذارد). ورودی میتواند با یا بدون «دکتر» باشد. |
|
||||
| `medical_system_code` | ✅ | کد نظام پزشکی (کلید idempotency) |
|
||||
| `medical_system_code` | ✅ | کد نظام پزشکی (کلید idempotency ثانویه) |
|
||||
| `source` | — | پیشفرض `irimc` |
|
||||
| `source_ref` | — | `profile_url` یا شناسهٔ مبدأ |
|
||||
| `source_ref` | — | `profile_url` نظام پزشکی؛ UUID داخلش استخراج و در `source_profile_id` ذخیره میشود و **کلید اصلی idempotency** است |
|
||||
| `gender` | — | `man` \| `woman` |
|
||||
| `degree` | — | `general` \| `expert` \| `specialist` \| `subspecialistplus` |
|
||||
| `info` | — | متن تخصص/توضیح |
|
||||
@@ -226,6 +239,8 @@ php bin/console app:doctors:repair --list # فهرست گامها
|
||||
| `degrees` | بازمحاسبهٔ درجه از روی عنوان خام `info` |
|
||||
| `specialty-parents` | افزودن تخصصهای والد به پزشکانی که فقط تخصص فرزند دارند |
|
||||
| `surrogate-role` | افزودن `ROLE_UNCLAIMED_DOCTOR` به کاربران جانشین قدیمی |
|
||||
| `source-profile-id` | پرکردن `source_profile_id` از `source_ref` (پیشنیاز dedup مبتنی بر profile id) |
|
||||
| `report-suspected-duplicates` | گزارش خوشههای مشکوک به تکراری (نام+تخصص+شهرِ یکسان، profile id متفاوت) — **بدون** ادغام خودکار |
|
||||
|
||||
سوییچها:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user