feat: implement cancellation policy, no-show tracking, and waitlist management

- Add implementation notes for cancellation and waitlist features.
- Create task documentation outlining goals, current status, and acceptance criteria for cancellation policy and resource utilization reporting.
- Establish architecture for domain events and outbox pattern to ensure reliable event publishing.
- Define database schema for domain events and necessary queries for resource utilization and plan accuracy reports.
- Implement detailed implementation notes covering edge cases, testing strategies, and documentation requirements.
This commit is contained in:
hamed
2026-07-30 11:43:58 +03:30
parent 1d338503c8
commit 021d0eb6b2
62 changed files with 8098 additions and 0 deletions
@@ -0,0 +1,75 @@
# تسک ۰۲ — مدل منبع: نوع منبع، منبع، مهارت، استخر
**فاز:** ۱ (هسته) · **وابستگی:** ۰۱ · **زمان:** ۱۴-۱۸ ساعت
---
## هدف
قانون طلایی اول مستند: «تقویم مال منبع است، نه مال پزشک». امروز تنها موجودیتی که
می‌تواند اشغال شود پزشک است و پرسنل (`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 پیش‌فرض)