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:
@@ -0,0 +1,89 @@
|
||||
# تسک ۰۴ — کاتالوگ خدمات نسخهٔ ۲: گروه آیتم، دو نوع زمان، ناسازگاری
|
||||
|
||||
**فاز:** ۱ (هسته) · **وابستگی:** ۰۱ · **زمان:** ۱۴-۱۶ ساعت
|
||||
|
||||
---
|
||||
|
||||
## هدف
|
||||
|
||||
مستند بند ۵ میگوید انتخاب آیتم خودش قانون دارد و این قوانین **نباید** به موتور قوانین
|
||||
سپرده شوند: «حتماً یک سطح انرژی، فقط یکی»، «بین ۱ تا ۸ دندان»، «بیکینی با فولبادی
|
||||
جمع نمیشود». و مهمتر: هر آیتم دو زمان دارد — «زمان تنها» و «زمان اضافه».
|
||||
|
||||
## وضعیت فعلی
|
||||
|
||||
```php
|
||||
// src/ClinicService/Entity/ServiceItem.php
|
||||
private ?int $durationMinutes = null; // یک عدد، تخت
|
||||
private int $priceRials = 0;
|
||||
private bool $bookable = false;
|
||||
```
|
||||
|
||||
و در `AppointmentController::serviceSlots()`:
|
||||
|
||||
```php
|
||||
$totalMinutes += $duration; // ← جمع ساده؛ همان فرمولی که مستند ردش میکند
|
||||
```
|
||||
|
||||
نتیجه: بیمار که «صورت + بیکینی» میخواهد، ۱۵+۱۵=۳۰ دقیقه ظرفیت میگیرد در حالی که
|
||||
واقعیت ۱۵+۸=۲۳ دقیقه است. هفت دقیقه ضرب در روزی ۲۰ نوبت = یک ساعت ظرفیت هدررفته در روز.
|
||||
|
||||
همچنین `ServiceSection` تکسطحی است و دستهبندی درختی مستند را ندارد.
|
||||
|
||||
## دامنه
|
||||
|
||||
**هست:**
|
||||
- `ServiceCategory` درختی (جدا از `ServiceSection` موجود که «بخش کلینیک» است)
|
||||
- `ItemGroup` با `min_select` / `max_select`
|
||||
- روی `ServiceItem`: `solo_duration_minutes` و `additional_duration_minutes`
|
||||
- `ServiceItemRelation` برای `incompatible_with` و `requires`
|
||||
- `ServiceBranchOverride` برای قیمت و مدت اختصاصی شعبه
|
||||
- `session_count` روی سرویس (تکجلسه یا دورهای — پروتکل کاملش تسک ۱۲)
|
||||
- `ServiceSelectionValidator` — اعتبارسنجی انتخاب کاربر پیش از هر محاسبه
|
||||
- `DurationCalculator` — محاسبهٔ درست مدت با دو نوع زمان
|
||||
|
||||
**نیست:** بخشهای نوبت و نیازمندی منبع (تسک ۰۵)، اعمال روی جستجوی وقت (تسک ۰۶).
|
||||
|
||||
## Endpoint ها
|
||||
|
||||
| متد | مسیر | توضیح |
|
||||
|---|---|---|
|
||||
| GET | `/api/v1/service-categories/tree` | درخت دستهبندی |
|
||||
| POST/PATCH/DELETE | `/api/v1/service-category[/{uuid}]` | |
|
||||
| GET/POST | `/api/v1/service-item/{uuid}/groups` | گروههای آیتم یک سرویس |
|
||||
| PATCH/DELETE | `/api/v1/item-group/{uuid}` | |
|
||||
| PUT | `/api/v1/item-group/{uuid}/items` | جایگزینی کامل آیتمهای گروه |
|
||||
| PUT | `/api/v1/service-item/{uuid}/relations` | ناسازگاری و پیشنیاز |
|
||||
| PUT | `/api/v1/service-item/{uuid}/branch-overrides` | قیمت/مدت per شعبه |
|
||||
| POST | `/api/v1/service-selection/validate` | اعتبارسنجی انتخاب + مدت و قیمت محاسبهشده |
|
||||
|
||||
`POST /service-selection/validate` مهمترین endpoint این تسک است: سایت عمومی و پنل هر دو
|
||||
پیش از رفتن به مرحلهٔ انتخاب زمان، آن را صدا میزنند.
|
||||
|
||||
## معیار پذیرش
|
||||
|
||||
- ✅ موفق: سرویس «لیزر» با گروه «نواحی» (`min=1, max=8`) و آیتمهای صورت (تنها ۱۵، اضافه ۸)
|
||||
و بیکینی (تنها ۱۲، اضافه ۸). انتخاب هر دو →
|
||||
`POST /service-selection/validate` برمیگرداند `total_duration_minutes = 23`
|
||||
(اولین آیتم زمان تنها، بقیه زمان اضافه) و `valid = true`.
|
||||
- ✅ موفق: انتخاب فقط بیکینی → `total_duration_minutes = 12`.
|
||||
- ✅ موفق: شعبهٔ مرکزی برای همین سرویس `price_rials` بالاتر دارد →
|
||||
با `branch_uuid` مرکزی، قیمت override اعمال میشود.
|
||||
- ❌ خطا: انتخاب صفر آیتم از گروهی با `min_select=1` → `valid=false` با
|
||||
`errors[{group_uuid, code: 'min_select', message: 'انتخاب حداقل یک مورد از «نواحی» الزامی است'}]`.
|
||||
- ❌ خطا: انتخاب ۹ آیتم از گروهی با `max_select=8` → `valid=false` با کد `max_select`.
|
||||
- ❌ خطا: انتخاب دو آیتم ناسازگار → `valid=false` با کد `incompatible` و نام هر دو آیتم.
|
||||
- ❌ خطا: انتخاب آیتمی که پیشنیازش انتخاب نشده → `valid=false` با کد `missing_prerequisite`.
|
||||
- ❌ خطا: uuid آیتم از محیط دیگر → `404` (نه ۴۲۲ — نباید وجودش لو برود).
|
||||
- ⚠️ مرزی: `max_select = null` یعنی نامحدود.
|
||||
- ⚠️ مرزی: گروه با `min_select = 0` یعنی اختیاری.
|
||||
- ⚠️ مرزی: آیتم بدون `additional_duration_minutes` → از `solo_duration_minutes` استفاده شود
|
||||
(سازگاری با دادههای موجود که فقط یک `duration_minutes` دارند).
|
||||
- ⚠️ مرزی: حلقهٔ پیشنیاز (الف پیشنیاز ب، ب پیشنیاز الف) → `422` هنگام ثبت رابطه.
|
||||
|
||||
## خروجی
|
||||
|
||||
- توسعهٔ `src/ClinicService/` (بدون شکستن endpoint های موجود)
|
||||
- `assets/admin/pages/ServiceDetailPage.tsx` توسعه: تب «گروهها و آیتمها»
|
||||
- `docs/api/clinic-services.md` بهروزرسانی
|
||||
- migration + backfill: `duration_minutes` موجود → `solo_duration_minutes`
|
||||
Reference in New Issue
Block a user