Files
clinicpro/.claude/prompt/fix-doctor-name-and-purge.md
hamedandClaude Opus 4.8 51432c7bb9 fix(doctor): strip «دکتر» prefix on IRIMC import + name-fix & purge commands
Root cause of "دکتر دکتر …" (and ellipsis-truncated "…نی") in admin: IRIMC
names already contain the «دکتر» title, while the panel renders «دکتر {name}».
Convention is to store the bare name.

- DoctorImportService: normalize name via PersianText::stripDoctorTitle
  (also fixes ي/ی, ك/ک, half-space)
- PersianText::stripDoctorTitle now strips consecutive «دکتر دکتر …» prefixes
- app:doctors:fix-irimc-names: one-off backfill for existing source='irimc'
  rows (dry-run supported) — fixed 340 rows
- app:doctors:purge: FK-safe full wipe of doctors + all dependent tables +
  orphan surrogate users, for a clean test DB (dry-run default, --force to
  apply, prod-guarded)
- tests: PersianTextTest cases for the title stripping; DoctorImportTest
  asserts stored name has no «دکتر» prefix
- docs/api/doctor-import.md: name convention + the two new commands

Verified: import "دکتر صفورا حجازی نیا" → stored "صفورا حجازی نیا" → panel
shows single «دکتر صفورا حجازی نیا».

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 14:07:18 +03:30

8.9 KiB

رفع نام دوتایی «دکتر» در ایمپورت IRIMC + دستور پاک‌سازی کامل پزشکان

پروژه

clinicpro (backend + پنل ادمین)

زمینه

پس از ایمپورت پزشکان نظام پزشکی، نام در پنل ادمین اشتباه نمایش داده می‌شود: به‌جای «دکتر صفورا حجازی نیا»، «دکتر دکتر صفورا حجازی نیا» و در کارت grid به‌خاطر ellipsis بریده و «دکتر دکتر صفورا حجازی نی» دیده می‌شود.

ریشه (تأییدشده): نامِ خامِ نظام پزشکی خودش پیشوند «دکتر» دارد (دکتر صفورا حجازی نیا در doctors.json و در DB، ۲۰ کاراکتر — داده درست ذخیره شده). اما کنوانسیون پنل این است که نام بدون پیشوند ذخیره شود و خودِ UI «دکتر» را جلو می‌گذارد:

// assets/admin/pages/DoctorsPage.tsx:326 (جدول) و :412-413 (کارت grid با ellipsis)
<b>دکتر {doc.name}</b>

پس وقتی doc.name = "دکتر صفورا حجازی نیا" باشد، خروجی «دکتر دکتر …» می‌شود و در کارت (whiteSpace:nowrap; overflow:hidden; textOverflow:ellipsis) طولانی‌تر شده و «…نیا» بریده می‌شود. یک ریشه، هر دو نشانه.

علاوه بر این، کاربر می‌خواهد همهٔ پزشکان و داده‌های وابسته به پزشک پاک شوند تا یک دیتابیس تمیز برای تست داشته باشیم (این کار با FK حذف مستقیم شکست می‌خورد — قبلاً خطای FK_4384ADBC87F4FB17 روی doctor_provinces دیدیم).

مشکل / هدف

۱. ایمپورت IRIMC پیشوند «دکتر/دكتر» را از نام حذف کند تا با کنوانسیون پنل یکدست شود. ۲. ۳۴۰ رکورد IRIMC موجود (که با پیشوند ذخیره شده‌اند) اصلاح شوند. ۳. یک دستور کنسول امن برای پاک‌سازی کامل پزشکان + همهٔ داده‌های وابسته (FK-safe).

فایل‌های مرتبط

فایل نقش
src/Doctor/Service/DoctorImportService.php ساخت/به‌روزرسانی پزشک؛ اینجا نام normalize شود
src/Shared/Util/PersianText.php stripDoctorTitle() موجود — «دکتر» ابتدای نام را حذف می‌کند
assets/admin/pages/DoctorsPage.tsx خط ۳۲۶ و ۴۱۲-۴۱۳ — نمایش دکتر {doc.name} (تغییر لازم ندارد، فقط داده اصلاح شود)
جداول FK به doctors (۱۶ عدد) weekly_schedules, date_overrides, comments, clinic_doctor_invitations, holidays, doctor_specialties, appointments, doctor_cities, doctor_expertise, rates, doctor_provinces, doctor_insurances, clinic_doctors, doctor_claim_requests, doctor_addresses, doctor_secretaries

وضعیت فعلی

DoctorImportService::doImport() نام را همان‌طور که آمده ذخیره می‌کند:

$name = trim((string) $data['name']);
// ...
$doctor->setName($name);   // نام هنوز شامل «دکتر …» است

PersianText::stripDoctorTitle() از قبل هست و دقیقاً همین کار را می‌کند:

public static function stripDoctorTitle(string $name): string
{
    return trim(preg_replace('/^\s*دکتر\s+/u', '', self::normalize($name)) ?? $name);
}

وظایف

۱. حذف پیشوند «دکتر» هنگام ایمپورت

در DoctorImportService::doImport()، نام را قبل از ذخیره normalize کن:

use App\Shared\Util\PersianText;

$name = PersianText::stripDoctorTitle((string) ($data['name'] ?? ''));
if ($name === '') { /* همان اعتبارسنجی موجود در کنترلر — کنترلر نام خام را چک می‌کند */ }
  • توجه: کنترلر (DoctorImportController) نام خام را برای اعتبارسنجی !== '' چک می‌کند؛ strip فقط داخل سرویس برای مقدار ذخیره‌شده انجام شود تا اعتبارسنجی نشکند.
  • stripDoctorTitle علاوه بر حذف پیشوند، normalize هم می‌کند (ي→ی، ك→ک، نیم‌فاصله) که برای یکدستی نام مفید است.
  • edge: نام‌هایی که «دکتر» ندارند بدون تغییر می‌مانند؛ نام‌های دو-پیشوندی نادر («دکتر دکتر …») هم چون preg فقط یک بار از ابتدا حذف می‌کند، در صورت وجود باید بررسی شود (regex را در صورت نیاز به ^(?:\s*دکتر\s+)+ تغییر بده تا همهٔ پیشوندهای متوالی برود).

۲. اصلاح رکوردهای IRIMC موجود

یک دستور یک‌بارمصرف (هم‌سبک BackfillSurrogateRoleCommand) به نام app:doctors:fix-irimc-names:

// SELECT پزشکان source='irimc' که name با 'دکتر ' شروع می‌شود؛
// name = stripDoctorTitle(name)؛ با --dry-run فقط گزارش.
  • فقط source='irimc' را دست بزن (پزشکان manual/seed را تغییر نده).
  • --dry-run داشته باشد؛ در خروجی تعداد اصلاح‌شده را بده.
  • در همان دستور، اگر name کاربرِ جانشین (realName) هم پیشوند دارد اختیاری است؛ اولویت با doctors.name.

۳. دستور پاک‌سازی کامل پزشکان (دیتابیس تمیز تست)

دستور app:doctors:purge در src/Doctor/Command/PurgeDoctorsCommand.php:

  • حفاظت: فقط با --force اجرا شود؛ بدون آن فقط تعداد رکوردهای هر جدول را گزارش کند (dry-run پیش‌فرض). چون مخرب است، پیام تأیید واضح بدهد.
  • ترتیب FK-safe: داخل یک تراکنش، اول جداول فرزند سپس doctors، سپس کاربران جانشین. ساده‌ترین و مطمئن‌ترین راه در MariaDB:
$conn = $this->em->getConnection();
$conn->executeStatement('SET FOREIGN_KEY_CHECKS=0');
foreach ([
    'doctor_claim_requests','doctor_secretaries','doctor_addresses','doctor_insurances',
    'doctor_provinces','doctor_cities','doctor_specialties','doctor_expertise',
    'clinic_doctors','clinic_doctor_invitations','weekly_schedules','date_overrides',
    'holidays','comments','rates','appointments','doctors',
] as $t) {
    $n = $conn->executeStatement("DELETE FROM {$t}");   // یا TRUNCATE پس از خالی‌شدن FK
    $io->text("{$t}: {$n}");
}
$conn->executeStatement('SET FOREIGN_KEY_CHECKS=1');
  • کاربران جانشین: پس از حذف پزشکان، کاربرانِ ایمپورت را هم پاک کن (وگرنه یتیم می‌مانند): DELETE FROM users WHERE mobile_number LIKE 'imp\\_%' AND status=0.
  • هشدار داده‌های مشترک: appointments, comments, rates به بیمار/پرداخت هم وصل‌اند؛ چون این دیتابیس فقط برای تستِ ایمپورت است حذف کامل قابل‌قبول است، ولی در دستور صریح هشدار بده که این عمل روی prod اجرا نشود (بررسی APP_ENV !== 'prod' یا نیاز به فلگ اضافهٔ --i-know برای prod).
  • بعد از اجرا: SET FOREIGN_KEY_CHECKS=1 حتی در صورت خطا (finally) اجرا شود.

نکات مهم

  • بعد از وظیفهٔ ۱، فقط ایمپورت‌های جدید نام تمیز می‌گیرند؛ وظیفهٔ ۲ برای ۳۴۰ رکورد فعلی لازم است.
  • تغییری در DoctorsPage.tsx لازم نیست — با نام تمیز، دکتر {doc.name} درست رندر می‌شود و کارت grid دیگر بریده نمی‌شود.
  • جریان claim (DoctorClaimService::verifyIdentity) از stripDoctorTitle روی doctor.getName() استفاده می‌کند؛ با نام تمیزِ ذخیره‌شده، این strip بی‌اثر (no-op) و تطبیق نام همچنان درست است — رگرسیون نده.
  • تست:
    ddev exec php bin/console app:doctors:fix-irimc-names --dry-run
    ddev exec php bin/console app:doctors:purge            # dry-run
    ddev exec php bin/console app:doctors:purge --force    # پاک‌سازی
    # سپس یک ایمپورت تست و بررسی نام در /admin/doctors (باید «دکتر صفورا حجازی نیا» تک‌پیشوند باشد)
    
  • بعد از تغییر سرویس/کنترلر ایمپورت، docs/api/doctor-import.md را با «نام بدون پیشوند دکتر ذخیره می‌شود» به‌روز کن.
  • تست integration موجود DoctorImportTest را به‌روز کن: assert کند نام ذخیره‌شده پیشوند «دکتر» ندارد.