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,133 @@
|
||||
# نکات پیادهسازی — تسک ۰۴
|
||||
|
||||
## ۱. `ServiceItem` را تغییر نام نده
|
||||
|
||||
در این جدولها و کدها به آن ارجاع هست:
|
||||
|
||||
```
|
||||
appointment_service_items · service_item_staff · service_item_consumables
|
||||
service_item_audit_logs · tariffs.service_item_id · session_services
|
||||
appointments.service_item_id
|
||||
```
|
||||
|
||||
و در `nobat724_front/services/response.js` و `clinic-pro-tauri/src/service/response.js`
|
||||
کلید `service_item_uuid` در بدنهٔ رزرو میرود. تغییر نام یعنی شکستن سه ریپو بدون یک
|
||||
خطای build. کلاس جدید `ServiceOption` بساز و در `docs/api/clinic-services.md` جدول
|
||||
واژگان (فایل architecture) را عیناً بنویس.
|
||||
|
||||
## ۲. `/service-selection/validate` هم عمومی است هم پنلی
|
||||
|
||||
سایت عمومی بدون توکن آن را صدا میزند (بیمار هنوز وارد نشده). پس:
|
||||
|
||||
- در `security.yaml` مسیرش را whitelist کن
|
||||
- بدون کاربر احراز شده، `TenantFilter` خاموش است → **گارد دستی اجباری است**:
|
||||
محیط از `doctor_uuid` + `clinic_uuid` درخواست حل میشود و همهٔ uuid ها با
|
||||
`TenantOwnershipChecker::belongsToPair()` سنجیده میشوند
|
||||
- نرخمحدودسازی: این endpoint یک enumerate کنندهٔ کاتالوگ است. `symfony/rate-limiter`
|
||||
روی IP، مثل بقیهٔ endpoint های عمومی
|
||||
|
||||
این دقیقاً همان اشتباهی است که یک بار در `GET /api/v1/appointment-service-slots` رخ داد و
|
||||
در فاز ۸ tenancy رفع شد. تکرارش نکن.
|
||||
|
||||
## ۳. قطعیت محاسبهٔ مدت
|
||||
|
||||
دو انتخاب یکسان با ترتیب متفاوت باید **همیشه** یک عدد بدهند. تست:
|
||||
|
||||
```php
|
||||
$a = $calc->totalMinutes($service, [$face, $bikini]);
|
||||
$b = $calc->totalMinutes($service, [$bikini, $face]);
|
||||
self::assertSame($a, $b);
|
||||
```
|
||||
|
||||
اگر این تست نباشد، اولین بهینهسازی که ترتیب آرایه را عوض کند، قیمتها را تغییر میدهد و
|
||||
هیچکس نمیفهمد چرا.
|
||||
|
||||
## ۴. تصمیم: مرتبسازی نزولی بر اساس «زمان تنها»
|
||||
|
||||
مستند نگفته کدام آیتم «اولی» است. سه گزینه بررسی شد:
|
||||
|
||||
| گزینه | مشکل |
|
||||
|---|---|
|
||||
| ترتیب انتخاب کاربر | غیرقطعی — همان انتخاب دو مدت میدهد |
|
||||
| `sort_order` تعریفشده | کلینیک باید برای هر ترکیب فکر کند؛ عملاً پر نمیشود |
|
||||
| **بیشترین «زمان تنها»** ✅ | قطعی، بدون ورودی اضافه، و از نظر کسبوکار درست: کار بزرگتر آمادهسازی را میبلعد |
|
||||
|
||||
انتخاب سوم. دلیلش را در کد بهصورت کامنت بنویس، وگرنه اولین بازبینیکننده آن را
|
||||
«مرتبسازی بیدلیل» میبیند و حذفش میکند.
|
||||
|
||||
## ۵. `additional_minutes = null` یعنی محافظهکار
|
||||
|
||||
```php
|
||||
$rest->getAdditionalMinutes() ?? $rest->getSoloMinutes()
|
||||
```
|
||||
|
||||
نه صفر. اگر null را صفر بگیری، سرویسهای موجود که این ستون را ندارند یکشبه مدتشان
|
||||
نصف میشود و ظرفیت الکی باز میشود — یعنی نوبت روی نوبت.
|
||||
|
||||
## ۶. ناسازگاری متقارن، پیشنیاز جهتدار
|
||||
|
||||
```php
|
||||
// ناسازگاری — یک ردیف کافی است، هر دو جهت پرسوجو میشوند
|
||||
$conflicts = $relationRepo->createQueryBuilder('r')
|
||||
->where('r.type = :incompatible')
|
||||
->andWhere('r.source IN (:sel) AND r.target IN (:sel)')
|
||||
->setParameter('sel', $selectedIds)
|
||||
->getQuery()->getResult();
|
||||
```
|
||||
|
||||
پیشنیاز جهتدار است و حلقه ممنوع. `assertNoCycle()` با DFS هنگام **ثبت** اجرا شود، نه
|
||||
هنگام اعتبارسنجی انتخاب — بررسی حلقه در مسیر داغ رزرو، هزینهٔ بیدلیل است.
|
||||
|
||||
## ۷. عمق درخت و حذف دسته
|
||||
|
||||
- سقف عمق ۴ (`depth <= 3` با ریشهٔ صفر)
|
||||
- حذف دستهای که فرزند یا سرویس دارد → `422`
|
||||
- جابهجایی دسته → `path` همهٔ نوادگان با یک `UPDATE … SET path = REPLACE(path, :old, :new)`
|
||||
بهروز شود، در یک تراکنش
|
||||
|
||||
## ۸. edge case ها
|
||||
|
||||
| حالت | رفتار درست |
|
||||
|---|---|
|
||||
| سرویس بدون هیچ گروه | معتبر — رفتار امروزی، مدت = `duration_minutes` |
|
||||
| گروه بدون هیچ آیتم فعال و `min_select=1` | انتخاب همیشه نامعتبر میشود → هشدار در پنل هنگام ذخیره |
|
||||
| `min_select > max_select` | `422` |
|
||||
| `max_select` بزرگتر از تعداد آیتمهای فعال | مجاز؛ عملاً یعنی نامحدود |
|
||||
| `additional_minutes > solo_minutes` | `422` |
|
||||
| آیتم غیرفعال در انتخاب | `422` با کد `inactive_option` |
|
||||
| override شعبه با `bookable=0` | سرویس در آن شعبه در لیست رزرو نیاید |
|
||||
| دو آیتم ناسازگار در دو گروه مختلف | همچنان ناسازگار — رابطه بین آیتمهاست، نه گروهها |
|
||||
| انتخاب آیتم از سرویس دیگر | `422` `option_not_in_service` (بعد از بررسی tenant) |
|
||||
|
||||
## ۹. تست
|
||||
|
||||
```
|
||||
tests/ClinicService/DurationCalculatorTest.php
|
||||
- یک آیتم → solo
|
||||
- دو آیتم یک گروه → solo(بزرگتر) + additional(کوچکتر)
|
||||
- دو گروه → هر گروه solo خودش
|
||||
- additional=null → از solo استفاده شود
|
||||
- قطعیت: جابهجایی ترتیب ورودی، همان عدد
|
||||
tests/ClinicService/ServiceSelectionValidatorTest.php
|
||||
- min_select نقض → کد min_select
|
||||
- max_select نقض → کد max_select
|
||||
- ناسازگار → کد incompatible با نام هر دو
|
||||
- پیشنیاز غایب → کد missing_prerequisite
|
||||
- چند خطا همزمان → همه با هم برگردند
|
||||
- uuid محیط دیگر → 404 و هیچ اطلاعاتی در بدنه
|
||||
tests/ClinicService/ServicePriceResolverTest.php
|
||||
- override شعبه بر تعرفه اولویت دارد
|
||||
- override جزئی (فقط قیمت) مدت را دست نمیزند
|
||||
tests/ClinicService/ServiceCategoryTreeTest.php
|
||||
- عمق ۵ → 422 · حذف دستهٔ دارای فرزند → 422 · جابهجایی path نوادگان
|
||||
tests/ClinicService/BackwardCompatibilityTest.php
|
||||
- سرویس بدون گروه: appointment-service-slots دقیقاً همان خروجی قبلی
|
||||
```
|
||||
|
||||
آخرین تست مهمترین است: **این تسک نباید رفتار نوبتدهی سرویسی فعلی را تغییر دهد.**
|
||||
|
||||
## ۱۰. مستندات
|
||||
|
||||
`docs/api/clinic-services.md` بهروزرسانی با جدول واژگان + endpoint های جدید.
|
||||
یادآوری: قرارداد `POST /service-selection/validate` را `nobat724_front` مصرف میکند و
|
||||
شکستنش در build خطا نمیدهد.
|
||||
Reference in New Issue
Block a user