# تسک ۰۲ — مدل منبع: نوع منبع، منبع، مهارت، استخر **فاز:** ۱ (هسته) · **وابستگی:** ۰۱ · **زمان:** ۱۴-۱۸ ساعت --- ## هدف قانون طلایی اول مستند: «تقویم مال منبع است، نه مال پزشک». امروز تنها موجودیتی که می‌تواند اشغال شود پزشک است و پرسنل (`ClinicStaff`) فقط یک برچسب روی سرویس و نوبت است. این تسک لایهٔ منبع را می‌سازد: هر چیزی که ممکن است اشغال باشد — پزشک، اپراتور، دستیار، دستگاه، اتاق، تخت، یونیت. ## وضعیت فعلی ```php // src/Staff/Entity/ClinicStaff.php — تنها «منبع» امروز private string $fullName; private ?string $jobTitle; // متن آزاد، بدون معنای ساختاری private bool $active; private ?User $user; // برای ورود به پنل ``` بدون تقویم، بدون ظرفیت، بدون مهارت. `ServiceItem.staffMembers` یک ManyToMany به همین است و `Appointment.staff_id` یک ارجاع تکی — هیچ‌کدام تداخل زمانی را بررسی نمی‌کنند. ## دامنه **هست:** `ResourceType`، `Resource`، `Skill`، `ResourceSkill`، `ResourcePool`، `ResourcePoolMember`، ویژگی‌های آزاد (`attributes` JSON)، ظرفیت هم‌زمان، زمان آماده‌سازی/تمیزکاری per منبع، CRUD پنل، و **پل زدن `ClinicStaff` و `Doctor` و `Room` به `Resource`**. **نیست:** تقویم و مرخصی (تسک ۰۳)، نیازمندی منبع per بخش (تسک ۰۵)، محاسبهٔ اشغال (تسک ۰۶/۰۷). ## Endpoint ها | متد | مسیر | توضیح | |---|---|---| | GET/POST | `/api/v1/resource-types` | نوع منبع (کلینیک خودش تعریف می‌کند) | | PATCH/DELETE | `/api/v1/resource-type/{uuid}` | | | GET | `/api/v1/resources` | لیست با فیلتر `branch_uuid`, `type_uuid`, `active` | | POST | `/api/v1/resource` | ساخت منبع | | GET/PATCH/DELETE | `/api/v1/resource/{uuid}` | | | GET/POST | `/api/v1/skills` | مهارت‌ها | | PATCH/DELETE | `/api/v1/skill/{uuid}` | | | PUT | `/api/v1/resource/{uuid}/skills` | جایگزینی کامل مهارت‌های منبع | | GET/POST | `/api/v1/resource-pools` | استخر منابع قابل جایگزینی | | PATCH/DELETE | `/api/v1/resource-pool/{uuid}` | | | PUT | `/api/v1/resource-pool/{uuid}/members` | جایگزینی کامل اعضا | ## معیار پذیرش - ✅ موفق: کلینیک نوع منبع «دستگاه لیزر» می‌سازد، سه منبع از آن نوع در شعبهٔ مرکزی ثبت می‌کند، یک استخر «لیزرهای آلکساندرایت» می‌سازد و هر سه را عضو می‌کند → `GET /api/v1/resource-pool/{uuid}` هر سه را با `branch_uuid` برمی‌گرداند. - ✅ موفق: مهارت «لیزر آلکساندرایت» ساخته و به دو اپراتور داده می‌شود → `GET /api/v1/resources?skill_uuid=…` فقط همان دو را برمی‌گرداند. - ✅ موفق: بعد از اجرای `app:resource:backfill`، هر `ClinicStaff` فعال یک `Resource` با `type=staff` و هر `Doctor` دارای برنامه یک `Resource` با `type=doctor` دارد. - ❌ خطا: منبع با `branch_uuid` متعلق به محیط دیگر → `404`. - ❌ خطا: عضو کردن منبعی از شعبهٔ A در استخری که منابعش در شعبهٔ B هستند → `422` «همهٔ اعضای استخر باید در یک شعبه باشند». - ⚠️ مرزی: `capacity = 3` روی اتاق تزریق → یک ردیف، نه سه. `GET` مقدار ۳ را برمی‌گرداند. - ⚠️ مرزی: `setup_minutes = 0, cleanup_minutes = 10` → معتبر. - ⚠️ مرزی: حذف مهارتی که به منبعی داده شده → `422`؛ باید اول از منابع برداشته شود. - ⚠️ مرزی: `attributes` با کلید ناشناخته → پذیرفته می‌شود (عمداً آزاد)، ولی مقدار غیر اسکالر → `422`. ## خروجی - `src/Resource/` کامل با تست - صفحات پنل: `ResourcesPage`, `ResourceFormPage`, `ResourceTypesPage`, `SkillsPage`, `ResourcePoolsPage` - `docs/api/resource.md` - `app:resource:backfill` (dry-run پیش‌فرض)