# چک‌لیست — تسک ۰۲ (منبع، نوع منبع، مهارت، استخر) **وضعیت کلی:** ✅ تکمیل‌شده (۲ ردیف 🔄 بازبینی چشمی · ۱ ردیف ⏳ وابسته به تسک ۰۷) · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۸ قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) · [red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md) --- ## ۰. خط سرخ | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۰.۱ | `--group=slot-mode-frozen` سبز | ✅ | ۳ تست / ۸ assertion سبز | | ۰.۲ | `Doctor` به `Resource` تبدیل **نشد** — فقط لینک شد | ✅ | فقط پل؛ `appointments.doctor_id` دست‌نخورده | | ۰.۳ | `ClinicStaff` و `Room` هویت مستقل حفظ کردند | ✅ | هر سه هویت مستقل ماندند | | ۰.۴ | `service_item_staff` و `ServiceItem.staffMembers` دست‌نخورده | ✅ | صفر تغییر | | ۰.۵ | `WeeklySchedule.meta.buffer_minutes` دست‌نخورده | ✅ | `setup/cleanup` per منبع است، آن یکی per پزشک | ## ۱. بک‌اند | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۱.۱ | `ResourceType` · `ClinicResource` · `Skill` · `ResourceSkill` | ✅ | | | ۱.۲ | `ResourcePool` · `ResourcePoolMember` | ✅ | | | ۱.۳ | نام کلاس `ClinicResource` (نه `Resource`) و جدول `clinic_resources` | ✅ | | | ۱.۴ | `ResourceLinker` — تنها نقطهٔ نگاشت پزشک/پرسنل/اتاق ↔ منبع | ✅ | + identity map برای نوع‌های flush-نشده | | ۱.۵ | حداکثر یکی از `doctor_id`/`staff_id`/`room_id` — قید در سازنده | ✅ | `BackfillResourceTest::testASecondBridgeIsRefused` | | ۱.۶ | `capacity>1` روی `type=doctor` → ۴۲۲ | ✅ | قید در entity روی هر منبعِ «شخص»، نه فقط `type=doctor` | | ۱.۷ | `normalizeAttributes` — اسکالر، کلید `[a-z_]{1,40}`، سقف ۲۰ | ✅ | | | ۱.۸ | `ResourcePoolService` — اعضا هم‌شعبه و هم‌نوع، وگرنه ۴۲۲ | ✅ | اعتبارسنجی پیش از حذف — اتمی | | ۱.۹ | `findEligible()` با `HAVING COUNT(DISTINCT skill) = n` | ✅ | `ResourceEligibilityTest` هر دو حالت را می‌سنجد | | ۱.۱۰ | `StaffService` موجود `ResourceLinker::syncActive()` صدا می‌زند | ✅ | `StaffController::toggle` صدا می‌زند؛ تست سطح endpoint دارد | | ۱.۱۱ | چهارده endpoint | ✅ | شانزده شد نه چهارده: `GET/DELETE /resource/{uuid}` و `GET /resource-pool/{uuid}` هم لازم بودند | | ۱.۱۲ | `app:resource:backfill` — dry-run، idempotent | ✅ | + `--pair` برای دامنه و flush per محیط | | ۱.۱۳ | `TenantOwnershipChecker` روی هر uuid از request | ✅ | `ResourceContext` تک‌نقطهٔ ۴۰۴ | ## ۲. دیتابیس | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۲.۱ | شش جدول ساخته شد | ✅ | `Version20260730132948` | | ۲.۲ | سه UNIQUE تهی‌پذیر روی `doctor_id`/`staff_id`/`room_id` | ✅ | دو تای اول به `(doctor_id, address_id)` و `(staff_id, address_id)` تصحیح شد — پزشک در چند شعبهٔ یک محیط کار می‌کند | | ۲.۳ | `idx_resource_skills_skill (skill_id, level)` | ✅ | کوئری داغ تسک ۰۶ | | ۲.۴ | `entity_type, entity_id` ستون اول ایندکس‌های لیست | ✅ | | | ۲.۵ | `resource_skills` و `resource_pool_members` در `AGGREGATE_CHILDREN` | ✅ | ریشه‌هاشان جفت دارند، پس ارث‌بری واقعی است | | ۲.۶ | `TenantSchemaCoverageTest` سبز | ✅ | | ## ۳. UI | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۳.۱ | `ResourcesPage` · `ResourceFormPage` · `ResourceTypesPage` · `SkillsPage` · `ResourcePoolsPage` | ✅ | `ResourceFormPage` به‌صورت مودال شد نه صفحه: فرم کوتاه است و صفحهٔ جدا یک ناوبری اضافه بدون سود می‌داد | | ۳.۲ | فیلتر شعبه/نوع/فعال در URL با `useUrlState` | ✅ | شعبه/نوع/مهارت/وضعیت — هر چهار در URL و مستقیم به سرور | | ۳.۳ | شعبه و نوع با `SearchableSelect` | ✅ | هیچ `