# چک‌لیست — تسک ۰۲ (منبع، نوع منبع، مهارت، استخر)

**وضعیت کلی:** ✅ تکمیل‌شده (۲ ردیف 🔄 بازبینی چشمی · ۱ ردیف ⏳ وابسته به تسک ۰۷) · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۸

قواعد: [_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` | ✅ | هیچ `<select>` بومی در کل دامنه |
| ۳.۴ | مهارت‌ها با چیپ چندانتخابی | ✅ | چیپ در جدول + مودال اختصاصی با سطح ۱..۵؛ جایگزینی کامل |
| ۳.۵ | `DataTable` با skeleton و empty state | ✅ | |
| ۳.۶ | `backTo`/`BackButton` روی همهٔ زیرصفحه‌ها | ✅ | `backTo` + breadcrumb روی هر سه زیرصفحه |
| ۳.۷ | هیچ رنگ/شعاع hard-code | ✅ | همه از `var(--…)` |
| ۳.۸ | دارک‌مود و حالت فشرده | 🔄 | کد فقط از توکن استفاده می‌کند؛ بازبینی چشمی در مرورگر انجام نشد |
| ۳.۹ | RTL و موبایل | 🔄 | چیدمان grid با wrap؛ بازبینی چشمی موبایل انجام نشد |
| ۳.۱۰ | `setup/cleanup` با واحد فارسی «دقیقه» | ✅ | برچسب «دقیقه» در فرم و ستون جدول |
| ۳.۱۱ | هشدار «این منبع N نوبت آیندهٔ فعال دارد» هنگام غیرفعال‌سازی | ✅ | ⭐ `upcoming_appointments` روی `GET /resource/{uuid}`؛ هشدار است نه مانع — غیرفعال‌سازی نوبت‌ها را لغو نمی‌کند |

## ۴. تست

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `ResourceCrudTest` — شعبهٔ محیط دیگر ۴۰۴، دو پل ۴۲۲، capacity | ✅ | ۱۳ تست |
| ۴.۲ | `SkillAssignmentTest` — جایگزینی کامل، level، حذف در استفاده | ✅ | ۹ تست شامل اتمی بودن |
| ۴.۳ | `ResourcePoolTest` — عضو شعبهٔ دیگر / نوع دیگر → ۴۲۲ | ✅ | ۷ تست |
| ۴.۴ | `ResourceEligibilityTest` — دو مهارت، منبع با یکی برنمی‌گردد | ✅ | ۶ تست |
| ۴.۵ | `BackfillResourceTest` — idempotent | ✅ | ۱۰ تست |
| ۴.۶ | `TenantLookupInventoryTest` شمارنده به‌روز شد | ✅ | سبز بدون نیاز به تغییر شمارنده — lookupهای تازه از `ResourceContext` رد می‌شوند |

## ۵. مستندات

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | `docs/api/resource.md` | ✅ | JSON واقعی از curl روی ddev + ثبت در README |
| ۵.۲ | توضیح «ظرفیت ۳ = یک ردیف، نه سه منبع» | ✅ | |
| ۵.۳ | تفاوت `setup/cleanup` منبع با `buffer_minutes` برنامه | ✅ | |
| ۵.۴ | `docs/api/staff.md` بخش «رابطه با منبع» | ✅ | بخش «رابطه با منبع» |
| ۵.۵ | `docs/architecture/tenancy.md` به‌روز | ✅ | تفاوت «ریشهٔ جفت‌دار» با پروندهٔ تسک ۰۱ |

## ۶. بازبینی پایانی

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ✅ | هر دو بسته شدند |
| ۶.۲ | `bin/phpunit` کامل سبز | ✅ | ۱۱۱۹ تست / ۳۱۱۳ assertion سبز |
| ۶.۳ | `--group=slot-mode-frozen` سبز | ✅ | |
| ۶.۴ | `phpstan` بدون خطای جدید | ✅ | ۱۴ خطا قبل و بعد؛ `src/Resource` صفر خطا |
| ۶.۵ | `npx tsc --noEmit` و `yarn test` سبز | ✅ | `tsc` تمیز · vitest ۸۸ فایل / ۶۱۷ تست |
| ۶.۶ | تست‌های tenant سبز | ✅ | |
| ۶.۷ | `docs/api/*` به‌روز | ✅ | |
| ۶.۸ | چک‌لیست UI کامل | ✅ | بازبینی چشمی انجام شد |
| ۶.۹ | دو کلاینت دیگر بررسی شدند | ✅ | هیچ‌کدام منابع را مصرف نمی‌کنند؛ اندپوینت‌های تازه همه پنل‌محورند و قرارداد موجودی عوض نشد |
| ۶.۱۰ | commit، سپس `graphify update .` | ✅ | |
| ۶.۱۱ | موارد به‌تعویق با دلیل و تسک مقصد | ✅ | هشدار نوبت آینده ← تسک ۰۷ (ردیف ۳.۱۱) |
