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,102 @@
# تسک ۰۵ — بخش‌های نوبت و سازندهٔ برنامه
**فاز:** ۱ (هسته) · **وابستگی:** ۰۲، ۰۴ · **زمان:** ۱۶-۲۰ ساعت
---
## هدف
مهم‌ترین بخش مستند (بند ۷). یک نوبت یک تکه زمان پیوسته نیست:
| بخش | مدت | اتاق | اپراتور | دستگاه |
|---|---|---|---|---|
| مالیدن کرم بی‌حسی | ۵ | اشغال | اشغال | آزاد |
| انتظار اثر کرم | ۳۰ | اشغال | **آزاد** | آزاد |
| خود لیزر | ۲۰ | اشغال | اشغال | اشغال |
| مراقبت بعد | ۵ | اشغال | اشغال | آزاد |
با مدل امروز اپراتور ۶۰ دقیقه قفل می‌شود در حالی که ۳۰ دقیقه کار می‌کند. نصف ظرفیت
هدر می‌رود.
## وضعیت فعلی
```php
// src/Appointment/Entity/Appointment.php
private int $slotStart; // یک بازهٔ پیوسته
private int $slotEnd;
```
هیچ مفهومی از بخش، و هیچ نیازمندی منبعی وجود ندارد. تنها منبعی که تداخلش بررسی می‌شود
پزشک است (`AppointmentRepository::isSlotTaken`).
## دامنه
**هست:**
- `SegmentTemplate` — الگوی بخش‌های یک سرویس (و بخش‌های اضافهٔ هر `ServiceOption`)
- `SegmentRequirement` — نیازمندی منبع هر بخش: نقش، تعداد، شرط مهارت، قید، نوع اشغال
- `AppointmentPlanBuilder` — از (سرویس، آیتم‌ها، بیمار، شعبه) یک **برنامهٔ نوبت** می‌سازد
- ادغام بخش‌های هم‌نوع، محاسبهٔ مدت هر بخش، چیدمان ترتیبی
- `GET /api/v1/appointment-plan/preview` برای دیدن برنامه پیش از جستجوی وقت
**نیست:** پیدا کردن منابع آزاد و زمان (تسک ۰۶)، ثبت اشغال (تسک ۰۷)،
اعمال قوانین روی برنامه (تسک ۰۹ — نقطهٔ اتصالش اینجا آماده می‌شود).
## Endpoint ها
| متد | مسیر | توضیح |
|---|---|---|
| GET/PUT | `/api/v1/service-item/{uuid}/segments` | الگوی بخش‌های سرویس |
| PUT | `/api/v1/segment-template/{uuid}/requirements` | نیازمندی‌های منبع یک بخش |
| POST | `/api/v1/appointment-plan/preview` | ساخت و برگرداندن برنامهٔ نوبت (بدون ثبت) |
## خروجی `POST /appointment-plan/preview`
```json
{
"success": true,
"data": {
"total_minutes": 60,
"segments": [
{ "sequence": 1, "name": "بی‌حسی موضعی", "offset_minutes": 0, "duration_minutes": 5,
"patient_present": true,
"requirements": [
{ "role": "room", "count": 1, "occupancy": "exclusive", "candidates": 3 },
{ "role": "operator", "count": 1, "occupancy": "exclusive", "candidates": 2 }
] },
{ "sequence": 2, "name": "انتظار", "offset_minutes": 5, "duration_minutes": 30,
"patient_present": true, "mergeable": true,
"requirements": [ { "role": "room", "count": 1, "occupancy": "exclusive", "candidates": 3 } ] }
]
}
}
```
`candidates` تعداد منابع واجد شرایط است — اگر صفر باشد، برنامه ساخته نمی‌شود و خطای
انسانی برمی‌گردد: «هیچ اپراتور خانمی با مهارت لیزر در این شعبه نیست» (مستند بند ۱۰).
## معیار پذیرش
- ✅ موفق: سرویس «لیزر» با چهار بخش بالا تعریف می‌شود؛ `preview` برنامه‌ای با
`total_minutes = 60` و چهار بخش با `offset_minutes` صحیح (۰، ۵، ۳۵، ۵۵) برمی‌گرداند.
- ✅ موفق: انتخاب دو ناحیه (صورت + بیکینی) → بخش «آماده‌سازی» **یک بار** می‌آید
(`mergeable=true` هم‌نوع‌ها ادغام می‌شوند) ولی بخش «لیزر» مدتش با `DurationCalculator`
تسک ۰۴ محاسبه شده است.
- ✅ موفق: سرویسی که هیچ `SegmentTemplate` ندارد → برنامه‌ای با **یک بخش** برابر کل مدت
و نیازمندی پیش‌فرض (منبع `type=doctor`). این همان رفتار امروز است.
- ❌ خطا: نیازمندی با مهارتی که هیچ منبعی در آن شعبه ندارد →
`422` با `ERR_NO_ELIGIBLE_RESOURCE` و پیام فارسی شامل نقش و مهارت.
- ❌ خطا: بخش با `duration_minutes <= 0` و بدون منبع مدت پویا → `422`.
- ⚠️ مرزی: بخش با `requirements = []` (مثلاً «انتظار در خانه») → معتبر؛ زمان می‌گیرد،
هیچ منبعی نمی‌گیرد.
- ⚠️ مرزی: قید `same_gender_as_patient` وقتی جنسیت بیمار نامشخص است → نیازمندی نادیده
گرفته **نمی‌شود**؛ `422` با پیام «برای این سرویس ثبت جنسیت بیمار الزامی است».
- ⚠️ مرزی: `setup/cleanup` منبع در `preview` **نمایش داده نمی‌شود** ولی در
`occupancy_offset` هر نیازمندی می‌آید تا تسک ۰۶ همان را استفاده کند.
- ⚠️ مرزی: مجموع مدت بخش‌ها بیشتر از ۸ ساعت → `422` (حفاظت از جستجوی وقت).
## خروجی
- `src/Appointment/Plan/` — entity ها، `AppointmentPlanBuilder`، DTO ها
- `assets/admin/pages/ServiceSegmentsPage.tsx` + پیش‌نمایش برنامه
- `docs/api/appointment-plan.md`
- الگوهای آماده: `app:segment:seed-templates --preset=beauty|dental|physio` (مستند بند ۱۷)