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>
This commit is contained in:
hamed
2026-07-11 14:07:18 +03:30
co-authored by Claude Opus 4.8
parent 0b49b03a1e
commit 51432c7bb9
21 changed files with 2460 additions and 1333 deletions
+146
View File
@@ -0,0 +1,146 @@
# رفع نام دوتایی «دکتر» در ایمپورت IRIMC + دستور پاک‌سازی کامل پزشکان
## پروژه
`clinicpro` (backend + پنل ادمین)
## زمینه
پس از ایمپورت پزشکان نظام پزشکی، نام در پنل ادمین اشتباه نمایش داده می‌شود: به‌جای
«دکتر صفورا حجازی نیا»، «دکتر دکتر صفورا حجازی نیا» و در کارت grid به‌خاطر ellipsis
بریده و «دکتر دکتر صفورا حجازی نی» دیده می‌شود.
**ریشه (تأییدشده):** نامِ خامِ نظام پزشکی خودش پیشوند «دکتر» دارد (`دکتر صفورا حجازی نیا`
در `doctors.json` و در DB، ۲۰ کاراکتر — داده درست ذخیره شده). اما کنوانسیون پنل این است
که نام **بدون** پیشوند ذخیره شود و خودِ UI «دکتر» را جلو می‌گذارد:
```tsx
// 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()` نام را همان‌طور که آمده ذخیره می‌کند:
```php
$name = trim((string) $data['name']);
// ...
$doctor->setName($name); // نام هنوز شامل «دکتر …» است
```
`PersianText::stripDoctorTitle()` از قبل هست و دقیقاً همین کار را می‌کند:
```php
public static function stripDoctorTitle(string $name): string
{
return trim(preg_replace('/^\s*دکتر\s+/u', '', self::normalize($name)) ?? $name);
}
```
## وظایف
### ۱. حذف پیشوند «دکتر» هنگام ایمپورت
در `DoctorImportService::doImport()`، نام را قبل از ذخیره normalize کن:
```php
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`:
```php
// 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:
```php
$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) و تطبیق نام همچنان درست است — رگرسیون نده.
- تست:
```bash
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 کند نام ذخیره‌شده پیشوند «دکتر» ندارد.
+22 -1
View File
@@ -61,7 +61,7 @@
| فیلد | الزامی | توضیح |
|---|:---:|---|
| `name` | ✅ | نام کامل پزشک |
| `name` | ✅ | نام کامل پزشک — پیشوند «دکتر» **هنگام ذخیره حذف** می‌شود (کنوانسیون: نام بدون عنوان؛ UI خودش «دکتر» را جلو می‌گذارد). ورودی می‌تواند با یا بدون «دکتر» باشد. |
| `medical_system_code` | ✅ | کد نظام پزشکی (کلید idempotency) |
| `source` | — | پیش‌فرض `irimc` |
| `source_ref` | — | `profile_url` یا شناسهٔ مبدأ |
@@ -188,3 +188,24 @@ X-Service-Token: <مقدار env CRAWLER_SERVICE_TOKEN>
php bin/console app:doctors:backfill-surrogate-role --dry-run # فقط گزارش
php bin/console app:doctors:backfill-surrogate-role # اعمال
```
### اصلاح نام رکوردهای قدیمی (حذف پیشوند «دکتر»)
رکوردهای IRIMC که پیش از این تغییر با پیشوند «دکتر» ذخیره شده بودند:
```bash
php bin/console app:doctors:fix-irimc-names --dry-run # فقط گزارش
php bin/console app:doctors:fix-irimc-names # اعمال (فقط source='irimc')
```
### پاک‌سازی کامل برای دیتابیس تست
حذف همهٔ پزشکان + داده‌های وابسته (FK-safe) برای شروع تمیز:
```bash
php bin/console app:doctors:purge # dry-run: فقط گزارش تعداد هر جدول
php bin/console app:doctors:purge --force # حذف واقعی + کاربران جانشین یتیم
```
> ⚠️ مخرب — `appointments`/`comments`/`rates` را هم پاک می‌کند. روی prod نیازمند
> `--i-know-this-is-prod` است و پیش‌فرض متوقف می‌شود.
+17 -1
View File
@@ -755,5 +755,21 @@
"753": "Community 753",
"754": "Community 754",
"755": "Community 755",
"756": "Community 756"
"756": "Community 756",
"757": "Community 757",
"758": "Community 758",
"759": "Community 759",
"760": "Community 760",
"761": "Community 761",
"762": "Community 762",
"763": "Community 763",
"764": "Community 764",
"765": "Community 765",
"766": "Community 766",
"767": "Community 767",
"768": "Community 768",
"769": "Community 769",
"770": "Community 770",
"771": "Community 771",
"772": "Community 772"
}
+180 -124
View File
@@ -1,16 +1,16 @@
# Graph Report - clinicpro (2026-07-11)
## Corpus Check
- 763 files · ~563,266 words
- 766 files · ~564,895 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 9567 nodes · 13272 edges · 757 communities (604 shown, 153 thin omitted)
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 293 edges (avg confidence: 0.8)
- 9595 nodes · 13312 edges · 773 communities (614 shown, 159 thin omitted)
- Extraction: 98% EXTRACTED · 2% INFERRED · 0% AMBIGUOUS · INFERRED: 294 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `95810569`
- Built from commit: `0b49b03a`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
@@ -756,6 +756,22 @@
- [[_COMMUNITY_Community 752|Community 752]]
- [[_COMMUNITY_Community 753|Community 753]]
- [[_COMMUNITY_Community 754|Community 754]]
- [[_COMMUNITY_Community 757|Community 757]]
- [[_COMMUNITY_Community 758|Community 758]]
- [[_COMMUNITY_Community 759|Community 759]]
- [[_COMMUNITY_Community 760|Community 760]]
- [[_COMMUNITY_Community 761|Community 761]]
- [[_COMMUNITY_Community 762|Community 762]]
- [[_COMMUNITY_Community 763|Community 763]]
- [[_COMMUNITY_Community 764|Community 764]]
- [[_COMMUNITY_Community 765|Community 765]]
- [[_COMMUNITY_Community 766|Community 766]]
- [[_COMMUNITY_Community 767|Community 767]]
- [[_COMMUNITY_Community 768|Community 768]]
- [[_COMMUNITY_Community 769|Community 769]]
- [[_COMMUNITY_Community 770|Community 770]]
- [[_COMMUNITY_Community 771|Community 771]]
- [[_COMMUNITY_Community 772|Community 772]]
## God Nodes (most connected - your core abstractions)
1. `BaseController` - 86 edges
@@ -774,21 +790,21 @@
assets/admin/components/ServiceTariffModal.tsx → assets/admin/lib/utils.ts
- `Pagination()` --calls--> `formatNumber()` [EXTRACTED]
assets/admin/components/ui/Pagination.tsx → assets/admin/lib/utils.ts
- `MyFinancialPage()` --calls--> `formatRial()` [EXTRACTED]
assets/admin/pages/MyFinancialPage.tsx → assets/admin/lib/utils.ts
- `SettlementsPage()` --calls--> `formatRial()` [EXTRACTED]
assets/admin/pages/SettlementsPage.tsx → assets/admin/lib/utils.ts
- `LogsPage()` --calls--> `formatDateTime()` [EXTRACTED]
assets/admin/pages/LogsPage.tsx → assets/admin/lib/utils.ts
- `SmsPage()` --calls--> `formatDateTime()` [EXTRACTED]
assets/admin/pages/SmsPage.tsx → assets/admin/lib/utils.ts
## Import Cycles
- None detected.
## Communities (757 total, 153 thin omitted)
## Communities (773 total, 159 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.05
Nodes (46): ALLOWED_ROLES, PrivateRoute(), PublicRoute(), RoleRoute(), queryClient, toastStyle, useSubscription(), avatarBg() (+38 more)
Nodes (39): ALLOWED_ROLES, PrivateRoute(), PublicRoute(), RoleRoute(), queryClient, toastStyle, DEGREE_OPTIONS, DoctorFormPage() (+31 more)
### Community 1 - "Community 1"
Cohesion: 0.03
@@ -811,16 +827,16 @@ Cohesion: 0.07
Nodes (3): UserProfile, self, User
### Community 6 - "Community 6"
Cohesion: 0.12
Nodes (12): SettlementController, SettlementRepository, WalletTransactionRepository, Settlement, JsonResponse, Request, User, ManagerRegistry (+4 more)
Cohesion: 0.07
Nodes (19): SettlementController, FinancialBreakdown, FinancialBreakdownRepository, SettlementRepository, WalletTransactionRepository, CommissionService, Settlement, JsonResponse (+11 more)
### Community 7 - "Community 7"
Cohesion: 0.06
Nodes (8): DoctorService, Clinic, DoctorServiceRepository, Collection, Doctor, self, User, ManagerRegistry
### Community 8 - "Community 8"
Cohesion: 0.07
Nodes (27): Contract, InsuranceOption, KIND_LABEL, AdminLayout(), Topbar(), DEGREE_OPTIONS, DoctorFormPage(), FormValues (+19 more)
Cohesion: 0.08
Nodes (27): useSubscription(), AdminLayout(), avatarBg(), buildSections(), HUES, Props, ROLE_LABELS, Section (+19 more)
### Community 9 - "Community 9"
Cohesion: 0.04
@@ -835,24 +851,24 @@ Cohesion: 0.50
Nodes (4): UserActiveContextRepository, ManagerRegistry, User, UserActiveContext
### Community 12 - "Community 12"
Cohesion: 0.04
Nodes (47): Clinic Doctor Invitation API, DELETE `/api/v1/admin/clinic/invitation/{invUuid}`, Errors, Errors, Errors, Errors, Errors, Errors (+39 more)
Cohesion: 0.05
Nodes (42): Clinic Doctor Invitation API, DELETE `/api/v1/admin/clinic/invitation/{invUuid}`, Errors, Errors, Errors, Errors, Errors, Errors (+34 more)
### Community 13 - "Community 13"
Cohesion: 0.05
Nodes (42): get, PaymentConfig, PaymentGatewayInfo, usePaymentConfig(), api, ApiError, ApiResponse, downloadFile() (+34 more)
Nodes (35): get, PaymentConfig, PaymentGatewayInfo, api, ApiError, ApiResponse, downloadFile(), getToken() (+27 more)
### Community 14 - "Community 14"
Cohesion: 0.10
Nodes (12): AppointmentSettingsController, DateOverride, Holiday, DateOverrideRepository, HolidayRepository, JsonResponse, Request, User (+4 more)
Cohesion: 0.15
Nodes (8): AppointmentSettingsController, Holiday, HolidayRepository, JsonResponse, Request, User, Doctor, ManagerRegistry
### Community 15 - "Community 15"
Cohesion: 0.25
Nodes (7): PaymentController, Appointment, JsonResponse, Payment, Request, Response, User
### Community 16 - "Community 16"
Cohesion: 0.09
Nodes (6): PatientSession, Appointment, Collection, PatientRecord, self, SessionService
Cohesion: 0.06
Nodes (9): PatientSession, SmsWallet, AppLog, AppointmentExpiryService, Appointment, Collection, PatientRecord, self (+1 more)
### Community 17 - "Community 17"
Cohesion: 0.05
@@ -863,20 +879,20 @@ Cohesion: 0.05
Nodes (38): API, API, API, API, API, Route, Route, Route (+30 more)
### Community 19 - "Community 19"
Cohesion: 0.06
Nodes (23): CityForm, citySchema, ImportError, InsuranceForm, insuranceSchema, LogoUploadField(), ProvinceForm, provinceSchema (+15 more)
Cohesion: 0.03
Nodes (57): usePaymentConfig(), CityForm, citySchema, ImportError, InsuranceForm, insuranceSchema, LogoUploadField(), ProvinceForm (+49 more)
### Community 20 - "Community 20"
Cohesion: 0.13
Nodes (4): AdminApiController, JsonResponse, Request, StreamedResponse
### Community 21 - "Community 21"
Cohesion: 0.05
Nodes (36): formatNumber(), ClinicDetailPage(), AdminCharts, AdminDashboard(), AdminRecent, AdminStats, APPT_CLS, APPT_COLOR (+28 more)
Cohesion: 0.03
Nodes (48): formatNumber(), ClinicAddress, ClinicDetailPage(), ClinicDoctorItem, ClinicInvitation, EditForm, editSchema, HUES_LIST (+40 more)
### Community 22 - "Community 22"
Cohesion: 0.15
Nodes (9): RatingController, Like, LikeRepository, JsonResponse, Request, User, Comment, ManagerRegistry (+1 more)
Cohesion: 0.11
Nodes (14): RatingController, Like, Rate, LikeRepository, RateRepository, JsonResponse, Request, User (+6 more)
### Community 23 - "Community 23"
Cohesion: 0.05
@@ -915,8 +931,8 @@ Cohesion: 0.06
Nodes (32): 10. Modal / Dialog, 11. Toast Notifications, 12. Empty States & Loading, 13. Page Header (هر صفحه), 14. تکنولوژی Stack, 15. Responsive Breakpoints, 16. Dark Mode (اختیاری — فاز دوم), 17. نمونه رنگ‌بندی صفحه داشبورد (+24 more)
### Community 33 - "Community 33"
Cohesion: 0.05
Nodes (32): CoverageRow, Draft, KIND, TenantInsurance, ServiceTariffModal(), TariffResponse, TariffRow, Claim (+24 more)
Cohesion: 0.07
Nodes (22): CoverageRow, Draft, KIND, TenantInsurance, ServiceTariffModal(), TariffResponse, TariffRow, Contract (+14 more)
### Community 34 - "Community 34"
Cohesion: 0.06
@@ -995,8 +1011,8 @@ Cohesion: 0.07
Nodes (26): الزامات UI, باگ‌فیکس صفحه نوبت‌ها, باگ ۱ — کرش تقویم, باگ ۲ — روز هفته در DateNavigator, باگ ۳ — پیام «slot نیست», باگ ۴ — نوبت جدید: نام اجباری + find-or-create patient, باگ ۵ — patient_mobile نشان می‌دهد موبایل پزشک, باگ ۶ — نوبت‌های رزرو شده در نمایش زمانبندی (+18 more)
### Community 53 - "Community 53"
Cohesion: 0.07
Nodes (17): AppLogRepository, PaymentLog, ClaimItemRepository, DoctorClaimRequestRepository, PaymentLogRepository, PreRegistrationRepository, SessionServiceRepository, SiteConfigRepository (+9 more)
Cohesion: 0.08
Nodes (18): AppLogRepository, ClaimItemRepository, DoctorClaimRequestRepository, DoctorInsuranceRepository, PreRegistrationRepository, SessionServiceRepository, SmsSettingsRepository, ServiceEntityRepository (+10 more)
### Community 54 - "Community 54"
Cohesion: 0.10
@@ -1015,8 +1031,8 @@ Cohesion: 0.08
Nodes (24): Blog API, DELETE `/api/v1/blog/{uuid}`, Errors, Errors, Errors, Errors, Errors, GET `/api/v1/blog/{slug}` (+16 more)
### Community 58 - "Community 58"
Cohesion: 0.28
Nodes (4): Blog, BlogRepository, ManagerRegistry, QueryBuilder
Cohesion: 0.15
Nodes (8): Blog, BlogController, BlogRepository, JsonResponse, Request, User, ManagerRegistry, QueryBuilder
### Community 59 - "Community 59"
Cohesion: 0.22
@@ -1036,20 +1052,16 @@ Nodes (25): Bulk import / export, DELETE `/api/v1/admin/specialty/{id}`, Errors,
### Community 63 - "Community 63"
Cohesion: 0.05
Nodes (52): FreeVisitPrice(), Pricing, cn(), formatDate(), formatDateTime(), formatRial(), iranMobileOptionalSchema, iranMobileSchema (+44 more)
Nodes (50): FreeVisitPrice(), Pricing, cn(), formatDate(), formatDateTime(), formatRial(), iranMobileOptionalSchema, iranMobileSchema (+42 more)
### Community 64 - "Community 64"
Cohesion: 0.19
Nodes (7): SmsWalletController, SmsSettingsRepository, SmsSettings, JsonResponse, Request, User, ManagerRegistry
Cohesion: 0.29
Nodes (4): SmsWalletController, JsonResponse, Request, User
### Community 65 - "Community 65"
Cohesion: 0.08
Nodes (24): 2. کاربر (User), 4. 🔵 `POST` verify code, 5. 🔵 `POST` send code, 6. 🔵 `POST` register, 7. 🔴 `DELETE` delete user, 8. 🟡 `PATCH` patch, 9. 🟢 `GET` list secretary, Request Body (+16 more)
### Community 66 - "Community 66"
Cohesion: 0.07
Nodes (5): ClinicStaff, SmsWallet, AppLog, AppointmentExpiryService, self
### Community 67 - "Community 67"
Cohesion: 0.11
Nodes (4): Invoice, Collection, InvoiceItem, self
@@ -1102,10 +1114,6 @@ Nodes (3): SubscriptionPlan, Collection, self
Cohesion: 0.09
Nodes (23): dependencies, @ckeditor/ckeditor5-build-classic, @ckeditor/ckeditor5-react, @fontsource/vazirmatn, @heroicons/react, @hookform/resolvers, jalaali-js, leaflet (+15 more)
### Community 82 - "Community 82"
Cohesion: 0.07
Nodes (8): CategoryImportTest, Connection, RepositoryClassMappingTest, KernelTestCase, DbLogger, CategoryImporter, DbLoggerTest, Stringable
### Community 83 - "Community 83"
Cohesion: 0.09
Nodes (21): Backend, CSS / UI, Frontend, روند اجرای هر قابلیت, قبل از شروع — تحلیل پرامپت و ساخت Todo, قوانین اجرا (اجباری — هیچ استثنایی ندارد), قوانین خاص این پروژه, مثال اجرا (+13 more)
@@ -1119,8 +1127,8 @@ Cohesion: 0.07
Nodes (30): devDependencies, @babel/core, @babel/preset-env, @babel/preset-react, @babel/preset-typescript, core-js, @csstools/postcss-oklab-function, @hotwired/stimulus (+22 more)
### Community 86 - "Community 86"
Cohesion: 0.05
Nodes (16): AppointmentExpiryServiceTest, LowTierFixesTest, SendCodeMobileRateLimitTest, ClaimsListPaginationTest, CaptchaFlowTest, ServiceItemStaffOwnershipTest, EntityManagerInterface, KernelBrowser (+8 more)
Cohesion: 0.04
Nodes (18): AppointmentExpiryServiceTest, LowTierFixesTest, SendCodeMobileRateLimitTest, ClaimsListPaginationTest, CaptchaFlowTest, ServiceItemDeleteCleanupTest, ServiceItemStaffOwnershipTest, TenantInsuranceCleanupTest (+10 more)
### Community 87 - "Community 87"
Cohesion: 0.10
@@ -1143,7 +1151,7 @@ Cohesion: 0.10
Nodes (20): DELETE `/api/v1/admin/users/{uuid}`, Errors, Errors, GET `/api/v1/admin/users`, GET `/api/v1/admin/users/stats`, GET `/api/v1/admin/users/{uuid}`, POST `/api/v1/admin/users/{uuid}/status`, PUT `/api/v1/admin/users/{uuid}` (+12 more)
### Community 92 - "Community 92"
Cohesion: 0.10
Cohesion: 0.09
Nodes (21): Bulk import / export, DELETE `/api/v1/admin/doctor-service/{id}`, Doctor Service API, Errors, Errors, Errors, Errors, GET `/api/v1/admin/doctor-services` (+13 more)
### Community 93 - "Community 93"
@@ -1208,7 +1216,7 @@ Nodes (6): AppointmentController, Appointment, Doctor, JsonResponse, Request, Us
### Community 108 - "Community 108"
Cohesion: 0.13
Nodes (11): BaseController, CategoryController, DoctorImportController, SiteContextController, JsonResponse, JsonResponse, Request, User (+3 more)
Nodes (10): BaseController, CategoryController, CategoryImportController, SiteContextController, JsonResponse, JsonResponse, Request, JsonResponse (+2 more)
### Community 109 - "Community 109"
Cohesion: 0.29
@@ -1307,8 +1315,8 @@ Cohesion: 0.12
Nodes (16): Endpoint ها, GET /api/v1/representation/filter/{id}, GET /api/v1/representation/filter/{representationId}, GET /api/v1/representation/my-appointments/{id}, GET /api/v1/representation/{uuid}, GET /api/v1/representation/yearly-income/{id}, GET /api/v1/representation/yearly-income/{representationId}, POST /api/v1/representations/{id}/bank-accounts (+8 more)
### Community 135 - "Community 135"
Cohesion: 0.12
Nodes (16): Day Index Convention, DELETE `/api/v1/booking-setting/{uuid}`, Errors, Errors, Errors, Errors, GET `/api/v1/appointment-settings/weekly-schedule/{uuid}`, PATCH `/api/v1/appointment-settings/weekly-schedule/{uuid}` (+8 more)
Cohesion: 0.09
Nodes (22): Appointment Settings API, Available Locations, Day Index Convention, DELETE `/api/v1/booking-setting/{uuid}`, Errors, Errors, Errors, Errors (+14 more)
### Community 136 - "Community 136"
Cohesion: 0.12
@@ -1403,12 +1411,12 @@ Cohesion: 0.12
Nodes (15): Endpoint ها, GET /api/v1/payment/{uuid}, POST /api/v1/payment, POST /api/v1/payment/callback/mellat, Strategy Pattern برای درگاه‌ها, Subscription Payment — POST /api/v1/subscription-payment, ⚠ امنیت: IP Whitelist برای Callback, ⚠ امنیت: جلوگیری از Open Redirect (+7 more)
### Community 160 - "Community 160"
Cohesion: 0.09
Nodes (21): Appointment Settings API, Available Locations, Date Overrides, DELETE `/api/v1/appointment-settings/date-override/{uuid}`, Errors, Errors, Errors, `GET /api/v1/appointment-settings/available-locations/{doctorUuid}` (+13 more)
Cohesion: 0.13
Nodes (15): Date Overrides, DELETE `/api/v1/appointment-settings/date-override/{uuid}`, Errors, Errors, GET `/api/v1/appointment-settings/date-override/list/{doctorUuid}`, GET `/api/v1/appointment-settings/date-override/{uuid}`, PATCH `/api/v1/appointment-settings/date-override/{uuid}`, POST `/api/v1/appointment-settings/date-override` (+7 more)
### Community 161 - "Community 161"
Cohesion: 0.16
Nodes (9): ClaimsListNPlusOneTest, ClaimItem, ClaimSubmitterInterface, ClaimService, ManualClaimSubmitter, Claim, Invoice, Claim (+1 more)
Cohesion: 0.24
Nodes (7): ClaimSubmitterInterface, ClaimService, ManualClaimSubmitter, Claim, Invoice, Claim, ClaimSubmissionResult
### Community 162 - "Community 162"
Cohesion: 0.13
@@ -1439,8 +1447,8 @@ Cohesion: 0.10
Nodes (20): api.ir (استعلام هویت — Shahkar / IbanMatch), اتصال به دیتابیس‌های مستقل (الزامی), اسرار (الزامی — قبل از اولین دیپلوی), امنیت و منابع, بررسی سلامت, دامنه‌ها و CORS, دیپلوی‌های بعدی, راهنمای دیپلوی ClinicPro (Coolify + Docker Compose) (+12 more)
### Community 169 - "Community 169"
Cohesion: 0.15
Nodes (5): EntityInsurancePricing, TenantInsuranceCleanupTest, EntityInsurancePricingRepository, TenantInsuranceCleanupService, ManagerRegistry
Cohesion: 0.13
Nodes (7): EntityInsurancePricing, EntityInsurancePricingRepository, TenantServiceCoverageRepository, TenantInsuranceCleanupService, ManagerRegistry, ManagerRegistry, TenantServiceCoverage
### Community 170 - "Community 170"
Cohesion: 0.13
@@ -1574,17 +1582,13 @@ Nodes (12): Clinic Services API, DELETE /api/v1/service-item/{uuid}, DELETE /api
Cohesion: 0.21
Nodes (6): AuthenticationException, ExceptionSubscriber, SecurityHeadersSubscriber, EventSubscriberInterface, ExceptionEvent, ResponseEvent
### Community 205 - "Community 205"
Cohesion: 0.06
Nodes (26): Command, BackfillSurrogateRoleCommand, CancelExpiredAppointmentsCommand, CreateAdminCommand, PruneLogsCommand, SeedCategoriesCommand, SeedDemoDataCommand, SeedSmsMessageTemplatesCommand (+18 more)
### Community 206 - "Community 206"
Cohesion: 0.08
Nodes (14): MellatGateway, MockGateway, SepGateway, SoapClient, PaymentGatewayInterface, PaymentInitResult, PaymentRefundResult, PaymentVerifyResult (+6 more)
Cohesion: 0.06
Nodes (17): GatewayFactory, MellatGateway, MockGateway, SepGateway, MellatGateway, SepGateway, SoapClient, PaymentGatewayInterface (+9 more)
### Community 207 - "Community 207"
Cohesion: 0.07
Nodes (30): ۲. مدل داده و موجودیت‌ها, ۲.۱ کاربر (User), ۲.۱۰ پرداخت (Payment Types), ۲.۱۰.۱ پرداخت نوبت, ۲.۱۰.۲ پرداخت اشتراک, ۲.۱۱ پروفایل بیمار (Profile), ۲.۱۲ وبلاگ (Blog), ۲.۱۳ نظرات، لایک و امتیازدهی (+22 more)
Cohesion: 0.15
Nodes (13): ۲. مدل داده و موجودیت‌ها, ۲.۱ کاربر (User), ۲.۱۰ پرداخت (Payment Types), ۲.۱۰.۱ پرداخت نوبت, ۲.۱۰.۲ پرداخت اشتراک, ۲.۱۱ پروفایل بیمار (Profile), ۲.۱۲ وبلاگ (Blog), ۲.۳ دکتر (Doctor) (+5 more)
### Community 208 - "Community 208"
Cohesion: 0.23
@@ -1715,8 +1719,8 @@ Cohesion: 0.28
Nodes (4): SubscriptionService, ClinicSubscription, Payment, SubscriptionPlan
### Community 245 - "Community 245"
Cohesion: 0.11
Nodes (15): emptyFeatures(), FEATURE_KEYS, FEATURE_LABELS, PeriodForm, periodSchema, PLAN_DISPLAY, PlanForm, planSchema (+7 more)
Cohesion: 0.15
Nodes (11): emptyFeatures(), FEATURE_KEYS, FEATURE_LABELS, PeriodForm, periodSchema, PLAN_DISPLAY, PlanForm, planSchema (+3 more)
### Community 246 - "Community 246"
Cohesion: 0.17
@@ -1755,12 +1759,12 @@ Cohesion: 0.18
Nodes (11): 1. 🔵 `POST` refresh token, 1. احراز هویت (Authentication), 2. 🟢 `GET` X-CSRF-Token, 3. 🟢 `GET` user info 🆕, Request Body, بخش دوم — مستند کامل API, خطاهای عمومی, فهرست مطالب (+3 more)
### Community 255 - "Community 255"
Cohesion: 0.11
Nodes (19): GET /api/v1/appointment-settings/slots — دریافت اسلات‌های خالی, GET /api/v1/categories/cities — شهرها, GET /api/v1/categories/states — استان‌ها, GET /api/v1/doctors/{id}, GET /api/v1/doctors/{id}/insurances, GET /api/v1/representations/{id}, GET /oauth/userinfo — اطلاعات کاربر (سازگار با دروپال), POST /api/v1/appointment-settings/holidays — ثبت تعطیلی (فقط ادمین) (+11 more)
Cohesion: 0.18
Nodes (11): GET /api/v1/doctors/{id}, GET /api/v1/doctors/{id}/insurances, GET /api/v1/representations/{id}, GET /oauth/userinfo — اطلاعات کاربر (سازگار با دروپال), POST /api/v1/representations/{id}/bank-accounts, POST /oauth/token — تجدید توکن (Refresh), POST /oauth/token — ورود به سیستم, Task-02: احراز هویت (Authentication) (+3 more)
### Community 256 - "Community 256"
Cohesion: 0.13
Nodes (14): `200 OK` (به‌روزرسانی شد), `200 OK` (رد به‌دلیل تصاحب‌شده), `201 Created` (ساخته شد), `422` (اعتبارسنجی), backfill نقش جانشین‌های قدیمی, Doctor Import (IRIMC) API, idempotency, Request (+6 more)
Cohesion: 0.12
Nodes (16): `200 OK` (به‌روزرسانی شد), `200 OK` (رد به‌دلیل تصاحب‌شده), `201 Created` (ساخته شد), `422` (اعتبارسنجی), backfill نقش جانشین‌های قدیمی, Doctor Import (IRIMC) API, idempotency, Request (+8 more)
### Community 258 - "Community 258"
Cohesion: 0.15
@@ -1827,16 +1831,20 @@ Cohesion: 0.18
Nodes (10): Endpoint های موجود که تغییر می‌کنند, GET /api/v1/admin/dashboard/charts?from=UNIX&to=UNIX, GET /api/v1/dashboard/clinic, GET /api/v1/dashboard/doctor, تسک ۱۶: داشبورد هوشمند — چارت + فیلتر زمانی, توضیح, زمان تخمینی, فیلتر بازه زمانی (+2 more)
### Community 274 - "Community 274"
Cohesion: 0.16
Cohesion: 0.17
Nodes (5): Authentication, ClinicPro — API Documentation Index, Error Code Reference, Modules, Standard Response Envelope
### Community 275 - "Community 275"
Cohesion: 0.19
Nodes (5): MellatGatewayTest, ErrorCodesTest, KavehNegarProviderTest, TestCase, KavehNegarProvider
### Community 276 - "Community 276"
Cohesion: 0.20
Nodes (9): Architecture Audit — ClinicPro Symfony 7 Migration, Architecture Score (بعد از اصلاحات), Architecture Violations, Executive Summary, Final Verdict (بعد از اصلاحات), Missing Requirements (کامل), اصلاحات اعمال‌شده (بعد از Audit), دلیل تصمیم (+1 more)
### Community 277 - "Community 277"
Cohesion: 0.08
Nodes (23): GET /api/v1/sms/balance — موجودی حساب پیامک, POST /api/v1/sms/queue — افزودن به صف, بخش اول — مستند محصول (PRD), ثبت‌نام دکتر — از طریق نماینده, ثبت‌نام دکتر — از طریق کلینیک, ثبت‌نام دکتر — مستقل, فهرست کلی, کلینیک پرو — مستند جامع فنی و API (+15 more)
Cohesion: 0.20
Nodes (9): بخش اول — مستند محصول (PRD), فهرست کلی, کلینیک پرو — مستند جامع فنی و API, ۵. فهرست مشکلات شناسایی‌شده و اصلاحات لازم, ۶. پیوست, ۶.۱ دیاگرام وضعیت نوبت, ۶.۲ جریان کمیسیون, ۶.۳ بررسی محدودیت منشی (+1 more)
### Community 279 - "Community 279"
Cohesion: 0.20
@@ -1927,8 +1935,8 @@ Cohesion: 0.42
Nodes (3): PreRegistrationController, JsonResponse, Request
### Community 301 - "Community 301"
Cohesion: 0.05
Nodes (42): PaginatedResponse, STATUS_FILTERS, AddForm, addSchema, ClinicsPage(), HUES_LIST, FILTERS, Breakdown (+34 more)
Cohesion: 0.04
Nodes (47): PaginatedResponse, STATUS_FILTERS, AddForm, addSchema, ClinicsPage(), HUES_LIST, FILTERS, Breakdown (+39 more)
### Community 302 - "Community 302"
Cohesion: 0.12
@@ -1940,7 +1948,7 @@ Nodes (16): آماده‌سازی پروژه ClinicPro برای دیپلوی ر
### Community 304 - "Community 304"
Cohesion: 0.22
Nodes (9): 29. 🟢 `GET` clinic list 🆕, 30. 🟢 `GET` get 🆕, 36. 🟢 `GET` get my rate, 6. کلینیک (Clinic), هدرهای اضافی, پارامترهای Query, پاسخ‌ها, پاسخ‌ها (+1 more)
Nodes (9): 30. 🟢 `GET` get 🆕, 33. 🔵 `POST` image_clinic, 36. 🟢 `GET` get my rate, 6. کلینیک (Clinic), هدرهای اضافی, هدرهای اضافی, پاسخ‌ها, پاسخ‌ها (+1 more)
### Community 306 - "Community 306"
Cohesion: 0.07
@@ -2063,8 +2071,8 @@ Cohesion: 0.39
Nodes (5): JsonContains, FunctionNode, Node, Parser, SqlWalker
### Community 340 - "Community 340"
Cohesion: 0.05
Nodes (43): Contract, CoverageRow, fieldLabel, FormData, PAYMENT_LABELS, Rule, schema, sectionTitle (+35 more)
Cohesion: 0.04
Nodes (38): Claim, ClaimItem, DebtRow, InsuranceOption, KIND_LABEL, STATUS_FILTERS, STATUS_META, DoctorClaim (+30 more)
### Community 341 - "Community 341"
Cohesion: 0.12
@@ -2183,8 +2191,8 @@ Cohesion: 0.29
Nodes (7): Authentication, Authorization, Input Validation, Logging Security, Rate Limiting, Secrets Management, ۷. تحلیل امنیت
### Community 371 - "Community 371"
Cohesion: 0.15
Nodes (6): UniqueConstraintsTest, FinancialBreakdown, FinancialBreakdownRepository, FinancialBreakdownIntegrityTest, ManagerRegistry, Payment
Cohesion: 0.13
Nodes (6): DateOverrideOwnershipTest, UniqueConstraintsTest, DateOverride, DateOverrideRepository, Doctor, ManagerRegistry
### Community 372 - "Community 372"
Cohesion: 0.11
@@ -2279,8 +2287,8 @@ Cohesion: 0.33
Nodes (6): Refactoring Plan, فاز ۰ — مستندسازی (۳ تا ۵ روز، قبل از هر کدنویسی), فاز ۱ — زیرساخت پایه (task-01), فاز ۲ — پیاده‌سازی ماژول‌ها (به ترتیب dependency), فاز ۳ — بهینه‌سازی (بعد از پیاده‌سازی), فاز ۴ — آماده‌سازی تولید
### Community 397 - "Community 397"
Cohesion: 0.36
Nodes (4): ClaimAmountBoundsTest, Claim, Doctor, User
Cohesion: 0.21
Nodes (6): ClaimAmountBoundsTest, ClaimsListNPlusOneTest, ClaimItem, Claim, Doctor, User
### Community 398 - "Community 398"
Cohesion: 0.22
@@ -2295,8 +2303,8 @@ Cohesion: 0.11
Nodes (17): بازطراحی معماری پرداخت — سرویس‌محور، امن، توسعه‌پذیر (Backend), تست دستی (ddev، در حالت `payment_test_mode=1`), خروجی نهایی (طبق spec — در گزارش اجرا ارائه شود), زمینه, فایل‌های مرتبط, مشکل / هدف, نکات مهم, وضعیت فعلی (+9 more)
### Community 407 - "Community 407"
Cohesion: 0.27
Nodes (8): MessageBusInterface, MockObject, SmsServiceLookupOnlyTest, SmsLogRepository, SmsMessageTemplateRepository, SmsService, SmsTextResolver, KavehNegarProvider
Cohesion: 0.26
Nodes (7): MessageBusInterface, MockObject, SmsServiceLookupOnlyTest, SmsLogRepository, SmsMessageTemplateRepository, SmsTextResolver, KavehNegarProvider
### Community 414 - "Community 414"
Cohesion: 0.10
@@ -2327,8 +2335,8 @@ Cohesion: 0.35
Nodes (5): PatientController, JsonResponse, PatientSession, Request, User
### Community 435 - "Community 435"
Cohesion: 0.33
Nodes (4): BlogController, JsonResponse, Request, User
Cohesion: 0.16
Nodes (10): Command, CancelExpiredAppointmentsCommand, PruneLogsCommand, PurgeDoctorsCommand, InputInterface, OutputInterface, InputInterface, OutputInterface (+2 more)
### Community 439 - "Community 439"
Cohesion: 0.17
@@ -2338,10 +2346,6 @@ Nodes (11): زمینه, صفحه Twig دعوت پزشک + کوتاه‌کردن
Cohesion: 0.11
Nodes (18): [F10] راهنمای کهنه در `CLAUDE.md`: endpoint `categorys/{bundle}` منتقل شده, [F11] داشبورد دکتر `GET /api/v1/dashboard/doctor` همیشه 500 (فیلد ناموجود در DQL) — ✅ رفع شد, [F1] phpstan: مقایسهٔ همیشه‌درست در محاسبهٔ estimated SMS — ✅ رفع شد, [F2] تست‌های PHPUnit به API خارجی Kavenegar درخواست واقعی می‌زنند, [F3] دیتابیس تست seed نشده — فقط کاربر ادمین وجود دارد, [F4] اسکریپت seeder `create_test_users.php` وجود ندارد, [F5] ادمین با JWT معتبر به `/api/doc` (Swagger UI) دسترسی ندارد (401), [F6] ناسازگاری کدهای خطا بین دامنه‌ها (+10 more)
### Community 451 - "Community 451"
Cohesion: 0.08
Nodes (16): ClinicAddress, ClinicDoctorItem, ClinicInvitation, EditForm, editSchema, HUES_LIST, INV_STATUS_MAP, IRAN_CENTER (+8 more)
### Community 452 - "Community 452"
Cohesion: 0.15
Nodes (3): DoctorClaimRequest, Doctor, User
@@ -2435,8 +2439,8 @@ Cohesion: 0.36
Nodes (5): StaffController, ClinicStaff, JsonResponse, Request, User
### Community 481 - "Community 481"
Cohesion: 0.38
Nodes (5): Rate, RateRepository, Doctor, ManagerRegistry, User
Cohesion: 0.17
Nodes (11): رفع نام دوتایی «دکتر» در ایمپورت IRIMC + دستور پاک‌سازی کامل پزشکان, زمینه, فایل‌های مرتبط, مشکل / هدف, نکات مهم, وضعیت فعلی, وظایف, پروژه (+3 more)
### Community 482 - "Community 482"
Cohesion: 0.43
@@ -2462,10 +2466,6 @@ Nodes (3): DoctorServiceController, JsonResponse, Request
Cohesion: 0.12
Nodes (15): `Modal.tsx` (بدون Portal), `PersianCalendar.tsx` (buttonها بدون `type`) — نمونه‌ها, باگ ۱ — علت, باگ ۲ — علت, رفع دو باگ Modal و تقویم شمسی در پنل ادمین, زمینه, فایل‌های مرتبط, مشکل / هدف (+7 more)
### Community 492 - "Community 492"
Cohesion: 0.18
Nodes (4): KavehNegarProvider, SmsService, SendSmsMessage, SmsProviderInterface
### Community 493 - "Community 493"
Cohesion: 0.20
Nodes (9): Backend Audit Backlog — ClinicPro, ☐ CRITICAL, ✅ DONE (committed on backend-audit), ☐ EPICS (design debt — cross-repo, defer; do NOT quick-fix), ☐ HIGH, ☐ LOW, ☐ MEDIUM, Progress (this audit session) (+1 more)
@@ -2594,10 +2594,6 @@ Nodes (12): buildKavenegarPattern(), KavenegarGuide(), SmsPage(), STATUS_LOG_MET
Cohesion: 0.50
Nodes (4): Errors, POST `/oauth/token/refresh`, Request Body, Response `200`
### Community 530 - "Community 530"
Cohesion: 0.23
Nodes (4): GatewayFactory, MellatGateway, MellatGatewayTest, SepGateway
### Community 531 - "Community 531"
Cohesion: 0.42
Nodes (4): DoctorClaimRequest, DoctorClaimService, Doctor, User
@@ -2627,8 +2623,8 @@ Cohesion: 0.22
Nodes (3): AppException, SlotTakenException, RuntimeException
### Community 543 - "Community 543"
Cohesion: 0.47
Nodes (3): CommissionService, Payment, Representation
Cohesion: 0.25
Nodes (8): ثبت‌نام دکتر — از طریق نماینده, ثبت‌نام دکتر — از طریق کلینیک, ثبت‌نام دکتر — مستقل, ۱. معرفی محصول, ۱.۱ نقش‌های سیستم, ۱.۲ فلوهای عملیاتی اصلی, ۱.۳ پلن‌های اشتراک, ۱.۴ سیستم پیامک
### Community 544 - "Community 544"
Cohesion: 0.15
@@ -2698,10 +2694,6 @@ Nodes (4): SmsMessageTemplateRepository, SmsTextResolver, SmsMessageTemplate, Ma
Cohesion: 0.50
Nodes (4): GET /api/v1/sms/wallet/balance, GET /api/v1/sms/wallet/logs, POST /api/v1/sms/wallet/charge, SMS Wallet
### Community 561 - "Community 561"
Cohesion: 0.25
Nodes (3): CorsRegexEnvProcessorTest, ErrorCodesTest, TestCase
### Community 563 - "Community 563"
Cohesion: 0.67
Nodes (3): submit(), Claim, ClaimSubmissionResult
@@ -2739,8 +2731,8 @@ Cohesion: 0.40
Nodes (5): Errors, Notes, POST `/api/v1/notification-mobile/request-otp`, Request Body, Response `200`
### Community 576 - "Community 576"
Cohesion: 0.43
Nodes (3): CategoryImportController, JsonResponse, Request
Cohesion: 0.25
Nodes (8): ۲.۲ انواع دسته‌بندی (Category Types), ۲.۲.۱ تگ (Tag), ۲.۲.۲ استان (State), ۲.۲.۳ شهر (City), ۲.۲.۴ بیمه پایه (Basic Insurance), ۲.۲.۵ بیمه مکمل (Supplementary Insurance), ۲.۲.۶ تخصص دکتر (Doctor Specialty), ۲.۲.۷ خدمات دکتر (Doctor Services)
### Community 577 - "Community 577"
Cohesion: 0.43
@@ -2783,16 +2775,16 @@ Cohesion: 0.50
Nodes (3): Entity: Payment, ساختار فایل‌ها, معماری — تسک ۱۵: ماژول پرداخت
### Community 595 - "Community 595"
Cohesion: 0.43
Nodes (3): SmsLogRepository, SmsLog, ManagerRegistry
Cohesion: 0.36
Nodes (4): SmsLogRepository, SmsLog, SmsService, ManagerRegistry
### Community 596 - "Community 596"
Cohesion: 0.29
Nodes (3): FinancialBreakdown, Payment, User
### Community 597 - "Community 597"
Cohesion: 0.20
Nodes (10): Configuration, Errors, GET `/api/v1/admin/sms/templates`, GET `/api/v1/sms/template/{uuid}`, POST `/api/v1/admin/sms/template/{uuid}/approve`, Request Body (`application/json`), Response `200`, Response `200` (+2 more)
Cohesion: 0.29
Nodes (7): Configuration, Errors, GET `/api/v1/admin/sms/templates`, GET `/api/v1/sms/template/{uuid}`, Response `200`, Response `200`, SMS API
### Community 599 - "Community 599"
Cohesion: 0.67
@@ -2822,10 +2814,6 @@ Nodes (5): RepresentationController, JsonResponse, Representation, Request, User
Cohesion: 0.67
Nodes (3): بک‌اند, فرانت‌اند, وضعیت فعلی کد (مهم — قبل از تغییر بخوان)
### Community 618 - "Community 618"
Cohesion: 0.16
Nodes (5): ServiceItemDeleteCleanupTest, ServiceCoverageNPlusOneTest, TenantServiceCoverageRepository, ManagerRegistry, TenantServiceCoverage
### Community 625 - "Community 625"
Cohesion: 0.40
Nodes (5): 31. 🟡 `PATCH` patch, Request Body, مثال Request, هدرهای اضافی, پاسخ‌ها
@@ -2835,8 +2823,8 @@ Cohesion: 0.23
Nodes (7): Money, BillingCalculator, InvoiceService, CoverageRule, ShareBreakdown, Invoice, PatientSession
### Community 631 - "Community 631"
Cohesion: 0.48
Nodes (3): DoctorInsuranceRepository, DoctorInsurance, ManagerRegistry
Cohesion: 0.32
Nodes (3): SmsService, SendSmsMessage, SmsProviderInterface
### Community 632 - "Community 632"
Cohesion: 0.40
@@ -3134,28 +3122,96 @@ Nodes (3): 43. 🟢 `GET` get, هدرهای اضافی, پاسخ‌ها
Cohesion: 0.36
Nodes (4): DoctorImportResult, DoctorImportService, Collection, User
### Community 748 - "Community 748"
Cohesion: 0.38
Nodes (3): BackfillSurrogateRoleCommand, InputInterface, OutputInterface
### Community 750 - "Community 750"
Cohesion: 0.38
Nodes (3): CreateAdminCommand, InputInterface, OutputInterface
### Community 751 - "Community 751"
Cohesion: 0.25
Nodes (5): HealthController, EntityManagerInterface, InputInterface, OutputInterface, JsonResponse
### Community 753 - "Community 753"
Cohesion: 0.38
Nodes (3): FixIrimcNamesCommand, InputInterface, OutputInterface
### Community 757 - "Community 757"
Cohesion: 0.38
Nodes (3): SystemOwnerCommand, InputInterface, OutputInterface
### Community 758 - "Community 758"
Cohesion: 0.43
Nodes (4): DoctorImportController, JsonResponse, Request, User
### Community 759 - "Community 759"
Cohesion: 0.38
Nodes (3): RepositoryClassMappingTest, KernelTestCase, DbLoggerTest
### Community 761 - "Community 761"
Cohesion: 0.47
Nodes (3): SeedCategoriesCommand, InputInterface, OutputInterface
### Community 762 - "Community 762"
Cohesion: 0.47
Nodes (3): SeedSmsMessageTemplatesCommand, InputInterface, OutputInterface
### Community 763 - "Community 763"
Cohesion: 0.33
Nodes (6): GET /api/v1/sms/balance — موجودی حساب پیامک, POST /api/v1/sms/queue — افزودن به صف, ۴. سیستم حساب و صف پیامک, ۴.۱ حساب پیامک (SMS Account), ۴.۲ صف پیامک (SMS Queue), ۴.۳ API پیامک
### Community 764 - "Community 764"
Cohesion: 0.53
Nodes (3): PaymentLog, PaymentLogRepository, ManagerRegistry
### Community 765 - "Community 765"
Cohesion: 0.40
Nodes (5): Errors, Path Parameters, POST `/api/v1/doctor/invitation/{invUuid}/respond`, Request Body, Response `200`
### Community 766 - "Community 766"
Cohesion: 0.40
Nodes (5): ۲.۸ تنظیمات نوبت (Appointment Settings), ۲.۸.۱ برنامه هفتگی (Weekly Schedule), ۲.۸.۲ تعطیلات (Holidays), ۲.۸.۳ لغو تعطیل (Date Override), ۲.۸.۴ الگوریتم محاسبه اسلات‌های خالی
### Community 768 - "Community 768"
Cohesion: 0.50
Nodes (4): GET /api/v1/appointment-settings/slots — دریافت اسلات‌های خالی, POST /api/v1/appointment-settings/holidays — ثبت تعطیلی (فقط ادمین), POST /api/v1/appointment-settings/overrides — ثبت Override توسط دکتر, Task-09: API تنظیمات نوبت
### Community 769 - "Community 769"
Cohesion: 0.50
Nodes (4): GET /api/v1/categories/cities — شهرها, GET /api/v1/categories/states — استان‌ها, Task-08: API دسته‌بندی‌ها, سایر Endpoint های دسته‌بندی — الزامی
### Community 770 - "Community 770"
Cohesion: 0.50
Nodes (4): ۲.۱۳ نظرات، لایک و امتیازدهی, ۲.۱۳.۱ نظر (Comment), ۲.۱۳.۲ لایک (Like), ۲.۱۳.۳ امتیاز (Rate)
### Community 771 - "Community 771"
Cohesion: 0.67
Nodes (3): 33. 🔵 `POST` image_clinic, هدرهای اضافی, پاسخ‌ها
Nodes (3): POST `/api/v1/admin/sms/template/{uuid}/approve`, Request Body (`application/json`), Response `200`
### Community 772 - "Community 772"
Cohesion: 0.67
Nodes (3): 29. 🟢 `GET` clinic list 🆕, پارامترهای Query, پاسخ‌ها
## Knowledge Gaps
- **4135 isolated node(s):** `ALLOWED_ROLES`, `Pricing`, `ImageCropModalProps`, `TenantInsurance`, `CoverageRow` (+4130 more)
- **4146 isolated node(s):** `ALLOWED_ROLES`, `Pricing`, `ImageCropModalProps`, `TenantInsurance`, `CoverageRow` (+4141 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **153 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
- **159 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `Altcha` connect `Community 367` to `Community 0`, `Community 485`?**
_High betweenness centrality (0.066) - this node is a cross-community bridge._
- **Why does `BaseController` connect `Community 108` to `Community 6`, `Community 138`, `Community 139`, `Community 14`, `Community 654`, `Community 655`, `Community 15`, `Community 20`, `Community 22`, `Community 26`, `Community 164`, `Community 295`, `Community 424`, `Community 300`, `Community 433`, `Community 435`, `Community 308`, `Community 59`, `Community 318`, `Community 576`, `Community 577`, `Community 64`, `Community 75`, `Community 77`, `Community 607`, `Community 480`, `Community 739`, `Community 230`, `Community 104`, `Community 745`, `Community 490`, `Community 107`, `Community 109`, `Community 499`, `Community 252`, `Community 121`, `Community 122`, `Community 380`?**
_High betweenness centrality (0.037) - this node is a cross-community bridge._
_High betweenness centrality (0.065) - this node is a cross-community bridge._
- **Why does `BaseController` connect `Community 108` to `Community 6`, `Community 138`, `Community 139`, `Community 14`, `Community 654`, `Community 655`, `Community 15`, `Community 20`, `Community 22`, `Community 26`, `Community 164`, `Community 295`, `Community 424`, `Community 300`, `Community 433`, `Community 308`, `Community 58`, `Community 59`, `Community 318`, `Community 64`, `Community 577`, `Community 75`, `Community 77`, `Community 607`, `Community 480`, `Community 739`, `Community 230`, `Community 104`, `Community 745`, `Community 490`, `Community 107`, `Community 109`, `Community 499`, `Community 252`, `Community 758`, `Community 121`, `Community 122`, `Community 380`?**
_High betweenness centrality (0.038) - this node is a cross-community bridge._
- **Why does `Version20260705070546` connect `Community 646` to `Community 421`?**
_High betweenness centrality (0.024) - this node is a cross-community bridge._
- **What connects `ALLOWED_ROLES`, `Pricing`, `ImageCropModalProps` to the rest of the system?**
_4135 weakly-connected nodes found - possible documentation gaps or missing edges._
_4146 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05069124423963134 - nodes in this community are weakly interconnected._
_Cohesion score 0.05075187969924812 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?**
_Cohesion score 0.028985507246376812 - nodes in this community are weakly interconnected._
- **Should `Community 2` be split into smaller, more focused modules?**
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"nodes": [{"id": "users_hamed_pj_my_pj_clinic_pro_clinicpro_src_shared_util_persiantext_php", "label": "PersianText.php", "file_type": "code", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L1"}, {"id": "util_persiantext_persiantext", "label": "PersianText", "file_type": "code", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L12"}, {"id": "util_persiantext_persiantext_normalize", "label": ".normalize()", "file_type": "code", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L14"}, {"id": "util_persiantext_persiantext_samename", "label": ".sameName()", "file_type": "code", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L40"}, {"id": "util_persiantext_persiantext_stripdoctortitle", "label": ".stripDoctorTitle()", "file_type": "code", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L46"}], "edges": [{"source": "users_hamed_pj_my_pj_clinic_pro_clinicpro_src_shared_util_persiantext_php", "target": "util_persiantext_persiantext", "relation": "contains", "confidence": "EXTRACTED", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L12", "weight": 1.0}, {"source": "util_persiantext_persiantext", "target": "util_persiantext_persiantext_normalize", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L14", "weight": 1.0}, {"source": "util_persiantext_persiantext", "target": "util_persiantext_persiantext_samename", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L40", "weight": 1.0}, {"source": "util_persiantext_persiantext", "target": "util_persiantext_persiantext_stripdoctortitle", "relation": "method", "confidence": "EXTRACTED", "source_file": "src/Shared/Util/PersianText.php", "source_location": "L46", "weight": 1.0}], "raw_calls": [{"caller_nid": "util_persiantext_persiantext_normalize", "callee": "class_exists", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L16", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_normalize", "callee": "\\Normalizer", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L17", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_normalize", "callee": "strtr", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L20", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_normalize", "callee": "strtr", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L31", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_normalize", "callee": "array_combine", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L31", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_normalize", "callee": "trim", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L36", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_normalize", "callee": "preg_replace", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L36", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_samename", "callee": "self", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L42", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_samename", "callee": "self", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L42", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_stripdoctortitle", "callee": "self", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L48", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_stripdoctortitle", "callee": "trim", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L50", "receiver": null}, {"caller_nid": "util_persiantext_persiantext_stripdoctortitle", "callee": "preg_replace", "is_member_call": false, "source_file": "/Users/hamed/pj/my_pj/clinic_pro/clinicpro/src/Shared/Util/PersianText.php", "source_location": "L50", "receiver": null}]}
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1887 -1192
View File
File diff suppressed because it is too large Load Diff
+25 -10
View File
@@ -3875,8 +3875,8 @@
"semantic_hash": ""
},
"docs/api/doctor-import.md": {
"mtime": 1783757329.074825,
"ast_hash": "cadef9a87e1f9249ffe4a78a3b394a45",
"mtime": 1783766160.064325,
"ast_hash": "28bb80aa78e7650d0b3e7be75bfc12d0",
"semantic_hash": ""
},
"assets/admin/pages/DoctorClaimsPage.tsx": {
@@ -3930,13 +3930,13 @@
"semantic_hash": ""
},
"src/Doctor/Service/DoctorImportService.php": {
"mtime": 1783757329.091918,
"ast_hash": "1e3ad74119be06e751c97438152c10bf",
"mtime": 1783765876.696864,
"ast_hash": "56bacf3ad261680191151c347f8cd411",
"semantic_hash": ""
},
"src/Shared/Util/PersianText.php": {
"mtime": 1783757329.0920727,
"ast_hash": "5f7b411848c6a2969e6e299ed2a770a4",
"mtime": 1783765891.9874525,
"ast_hash": "477051e58ba2a5a5e9cbf25f3a6992d6",
"semantic_hash": ""
},
"tests/Doctor/DoctorClaimTest.php": {
@@ -3945,13 +3945,13 @@
"semantic_hash": ""
},
"tests/Doctor/DoctorImportTest.php": {
"mtime": 1783757329.0924423,
"ast_hash": "77366f70913004b4b94367c2f47ef00e",
"mtime": 1783765976.698628,
"ast_hash": "1d99f84ff5b37dacd93000fa8562f994",
"semantic_hash": ""
},
"tests/Shared/PersianTextTest.php": {
"mtime": 1783757329.092578,
"ast_hash": "7edac40aa2ee51f7fa7d5fbefc9a49f9",
"mtime": 1783765905.1030002,
"ast_hash": "c852d71e5f7ab87a0dc9250f9e848bd6",
"semantic_hash": ""
},
".claude/prompt/irimc-import-complete.md": {
@@ -3978,5 +3978,20 @@
"mtime": 1783751951.8968952,
"ast_hash": "692f883978aaad1f269b5357ba10a5f6",
"semantic_hash": ""
},
"src/Doctor/Command/FixIrimcNamesCommand.php": {
"mtime": 1783766017.2526865,
"ast_hash": "fb0cad757bf1405c6919159a374e9154",
"semantic_hash": ""
},
"src/Doctor/Command/PurgeDoctorsCommand.php": {
"mtime": 1783766065.697639,
"ast_hash": "8ba5c9742c92bbf2a0d01d97ee903dd3",
"semantic_hash": ""
},
".claude/prompt/fix-doctor-name-and-purge.md": {
"mtime": 1783765695.1482882,
"ast_hash": "b03ab1640dc46aa51114fbc3eba51870",
"semantic_hash": ""
}
}
@@ -0,0 +1,70 @@
<?php
namespace App\Doctor\Command;
use App\Doctor\Entity\Doctor;
use App\Shared\Util\PersianText;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* اصلاح یک‌بارمصرف نامِ پزشکانِ ایمپورت‌شدهٔ IRIMC که با پیشوند «دکتر» ذخیره شده‌اند.
* فقط source='irimc' را دست می‌زند؛ پزشکان manual/seed را تغییر نمی‌دهد.
*
* php bin/console app:doctors:fix-irimc-names --dry-run
* php bin/console app:doctors:fix-irimc-names
*/
#[AsCommand(
name: 'app:doctors:fix-irimc-names',
description: 'Strip the leading «دکتر» title from existing IRIMC doctor names (idempotent, supports --dry-run)',
)]
class FixIrimcNamesCommand extends Command
{
public function __construct(private readonly EntityManagerInterface $em)
{
parent::__construct();
}
protected function configure(): void
{
$this->addOption('dry-run', null, InputOption::VALUE_NONE, 'Report only, change nothing');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$dryRun = (bool) $input->getOption('dry-run');
/** @var Doctor[] $doctors */
$doctors = $this->em->getRepository(Doctor::class)->createQueryBuilder('d')
->where('d.source = :src')
->setParameter('src', 'irimc')
->getQuery()
->getResult();
$fixed = 0;
foreach ($doctors as $doctor) {
$clean = PersianText::stripDoctorTitle((string) $doctor->getName());
if ($clean !== '' && $clean !== $doctor->getName()) {
$io->text(sprintf('%s «%s» → «%s»', $dryRun ? '[dry-run]' : '[fix]', $doctor->getName(), $clean));
if (!$dryRun) {
$doctor->setName($clean);
}
$fixed++;
}
}
if (!$dryRun && $fixed > 0) {
$this->em->flush();
}
$io->success(sprintf('%d نام %s (از %d پزشک IRIMC).', $fixed, $dryRun ? 'قابل اصلاح' : 'اصلاح شد', count($doctors)));
return Command::SUCCESS;
}
}
@@ -0,0 +1,89 @@
<?php
namespace App\Doctor\Command;
use Doctrine\DBAL\Connection;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* پاک‌سازی کامل همهٔ پزشکان و داده‌های وابسته برای ساختِ یک دیتابیس تمیزِ تست.
*
* مخرب است: بدون --force فقط تعداد رکوردهای هر جدول را گزارش می‌دهد (dry-run).
* روی prod نیازمند --i-know-this-is-prod است.
*
* php bin/console app:doctors:purge # فقط گزارش
* php bin/console app:doctors:purge --force # پاک‌سازی
*/
#[AsCommand(
name: 'app:doctors:purge',
description: 'Delete ALL doctors and doctor-related data for a clean test DB (dry-run by default; --force to apply)',
)]
class PurgeDoctorsCommand extends Command
{
/** ترتیب: فرزندان اول، سپس doctors. FK_CHECKS خاموش می‌شود پس ترتیب فقط برای خوانایی است. */
private const TABLES = [
'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',
];
public function __construct(private readonly Connection $conn)
{
parent::__construct();
}
protected function configure(): void
{
$this->addOption('force', null, InputOption::VALUE_NONE, 'Actually delete (otherwise dry-run report)');
$this->addOption('i-know-this-is-prod', null, InputOption::VALUE_NONE, 'Required to run against APP_ENV=prod');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$force = (bool) $input->getOption('force');
if (($_ENV['APP_ENV'] ?? 'dev') === 'prod' && !$input->getOption('i-know-this-is-prod')) {
$io->error('روی prod بدون --i-know-this-is-prod اجرا نمی‌شود. این عمل داده‌های واقعی (نوبت/نظر/پرداخت) را حذف می‌کند.');
return Command::FAILURE;
}
$io->section($force ? 'پاک‌سازی پزشکان و داده‌های وابسته' : 'گزارش (dry-run — چیزی حذف نمی‌شود)');
$rows = [];
foreach (self::TABLES as $t) {
$rows[] = [$t, (int) $this->conn->fetchOne("SELECT COUNT(*) FROM {$t}")];
}
$io->table(['جدول', 'رکورد'], $rows);
if (!$force) {
$io->warning('برای حذف واقعی، دوباره با --force اجرا کن.');
return Command::SUCCESS;
}
$this->conn->executeStatement('SET FOREIGN_KEY_CHECKS=0');
try {
foreach (self::TABLES as $t) {
$n = $this->conn->executeStatement("DELETE FROM {$t}");
$io->text(sprintf('%s: %d حذف شد', $t, $n));
}
// کاربران جانشینِ ایمپورت (imp_..., غیرفعال) که اکنون یتیم‌اند
$surrogates = $this->conn->executeStatement(
"DELETE FROM users WHERE mobile_number LIKE 'imp\\_%' AND status = 0"
);
$io->text(sprintf('کاربران جانشین: %d حذف شد', $surrogates));
} finally {
$this->conn->executeStatement('SET FOREIGN_KEY_CHECKS=1');
}
$io->success('دیتابیس پزشکان پاک شد.');
return Command::SUCCESS;
}
}
+3 -1
View File
@@ -46,7 +46,9 @@ class DoctorImportService
private function doImport(array $data, User $importedBy): DoctorImportResult
{
return $this->em->wrapInTransaction(function () use ($data, $importedBy): DoctorImportResult {
$name = trim((string) $data['name']);
// نامِ نظام پزشکی پیشوند «دکتر» دارد؛ کنوانسیون پنل نام بدون پیشوند است
// (UI خودش «دکتر» را جلو می‌گذارد). حذف پیشوند + normalize فارسی.
$name = \App\Shared\Util\PersianText::stripDoctorTitle((string) $data['name']);
$code = trim((string) ($data['medical_system_code'] ?? $data['medicalSystemCode']));
$source = trim((string) ($data['source'] ?? 'irimc')) ?: 'irimc';
+3 -1
View File
@@ -45,6 +45,8 @@ final class PersianText
/** حذف عنوان «دکتر» از ابتدای نام (برای مقایسهٔ نام پروفایل با نام ثبت احوال). */
public static function stripDoctorTitle(string $name): string
{
return trim(preg_replace('/^\s*دکتر\s+/u', '', self::normalize($name)) ?? $name);
$normalized = self::normalize($name);
// پیشوندهای متوالی «دکتر دکتر …» را هم کامل حذف می‌کند.
return trim(preg_replace('/^(?:دکتر\s+)+/u', '', $normalized) ?? $normalized);
}
}
+5 -2
View File
@@ -15,7 +15,7 @@ class DoctorImportTest extends ApiTestCase
private function importPayload(string $code): array
{
return [
'name' => 'دکتر تست ایمپورت',
'name' => 'دکتر صفورا حجازی نیا',
'medical_system_code' => $code,
'source_ref' => 'https://membersearch.irimc.org/member/profile?id=test',
'gender' => 'man',
@@ -46,6 +46,8 @@ class DoctorImportTest extends ApiTestCase
$this->assertSame('unclaimed', $doctor->getOwnerStatus());
$this->assertSame('irimc', $doctor->getSource());
$this->assertFalse($doctor->isActiveDoctorAppointment());
// نام بدون پیشوند «دکتر» ذخیره می‌شود (UI خودش «دکتر» را جلو می‌گذارد)
$this->assertSame('صفورا حجازی نیا', $doctor->getName());
$surrogate = $doctor->getUser();
$this->assertStringStartsWith('imp_', $surrogate->getMobileNumber());
@@ -74,7 +76,8 @@ class DoctorImportTest extends ApiTestCase
$this->em->clear();
$doctor = $this->em->getRepository(Doctor::class)->findOneBy(['uuid' => $first['data']['uuid']]);
$this->assertSame('دکتر تست ویرایش‌شده', $doctor->getName());
// نیم‌فاصله در normalize به فاصله تبدیل می‌شود
$this->assertSame('تست ویرایش شده', $doctor->getName());
}
public function testClaimedDoctorIsNeverOverwritten(): void
+4
View File
@@ -28,6 +28,10 @@ class PersianTextTest extends TestCase
$this->assertSame('فرخنده حسینی', PersianText::stripDoctorTitle('دکتر فرخنده حسینی'));
$this->assertSame('فرخنده حسینی', PersianText::stripDoctorTitle(' دکتر فرخنده حسینی '));
$this->assertSame('فرخنده حسینی', PersianText::stripDoctorTitle('فرخنده حسینی'));
$this->assertSame('صفورا حجازی نیا', PersianText::stripDoctorTitle('دکتر صفورا حجازی نیا'));
// پیشوند متوالی و کافِ عربی
$this->assertSame('صفورا حجازی نیا', PersianText::stripDoctorTitle('دکتر دکتر صفورا حجازی نیا'));
$this->assertSame('علی اکبری', PersianText::stripDoctorTitle('دكتر علي اكبري'));
}
public function testDifferentNamesStayDifferent(): void