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,131 @@
|
||||
# نکات پیادهسازی — تسک ۰۵
|
||||
|
||||
## ۱. برنامه یک تابع خالص است
|
||||
|
||||
`AppointmentPlanBuilder::build()` نباید چیزی بنویسد، چیزی cache کند، یا به `time()` نگاه کند.
|
||||
ورودی یکسان → خروجی یکسان. دلیلش تسک ۰۶ است: موتور جستجو یک برنامه میسازد و آن را
|
||||
برای ۹۰ روز × دهها نقطهٔ شروع استفاده میکند. اگر ساختن برنامه عوارض جانبی داشته باشد،
|
||||
جستجو یا کند میشود یا نتیجهٔ ناپایدار میدهد.
|
||||
|
||||
تنها I/O مجاز: خواندن الگوها و منابع کاندید (مرحلهٔ ۶).
|
||||
|
||||
## ۲. `offset_minutes` نسبی است، نه مطلق
|
||||
|
||||
بخشها با فاصله از **شروع نوبت** ذخیره میشوند، نه timestamp:
|
||||
|
||||
```
|
||||
بخش ۱ — offset 0, duration 5
|
||||
بخش ۲ — offset 5, duration 30
|
||||
بخش ۳ — offset 35, duration 20
|
||||
بخش ۴ — offset 55, duration 5
|
||||
```
|
||||
|
||||
تسک ۰۶ همین برنامه را روی هر نقطهٔ شروع کاندید «میلغزاند». اگر offset مطلق بود، برای هر
|
||||
نقطهٔ شروع باید برنامه از نو ساخته میشد.
|
||||
|
||||
## ۳. `setup/cleanup` منبع کجا اعمال میشود
|
||||
|
||||
**نه در برنامه، در اشغال.** برنامه میگوید «اپراتور از دقیقهٔ ۳۵ تا ۵۵ لازم است». اشغال
|
||||
واقعی همان منبع، با `setup=5, cleanup=10`، از دقیقهٔ ۳۰ تا ۶۵ است.
|
||||
|
||||
پس `PlannedRequirement` دو مقدار میدهد:
|
||||
|
||||
```php
|
||||
public readonly int $startOffset; // ۳۵ — چیزی که به بیمار نشان داده میشود
|
||||
public readonly int $endOffset; // ۵۵
|
||||
public readonly int $occupancyStartOffset; // ۳۰ — چیزی که تسک ۰۶/۰۷ استفاده میکند
|
||||
public readonly int $occupancyEndOffset; // ۶۵
|
||||
```
|
||||
|
||||
محاسبهشان اینجا انجام میشود چون `setup/cleanup` per منبع کاندید متفاوت است و باید
|
||||
بعد از مرحلهٔ ۶ (پیدا کردن کاندیدها) حساب شود — با بیشینهٔ کاندیدها، تا برنامه محافظهکار
|
||||
بماند و تسک ۰۶ بعد از انتخاب منبع دقیقش کند.
|
||||
|
||||
## ۴. ادغام: `count` بیشینه، نه جمع
|
||||
|
||||
وقتی دو بخش همنوع ادغام میشوند و هر دو «۱ اپراتور» میخواهند، نتیجه **۱** اپراتور است،
|
||||
نه ۲. همان اپراتور هر دو کار را میکند.
|
||||
|
||||
جمع فقط وقتی درست است که دو کار واقعاً همزمان انجام شوند — که در بخشهای ادغامشده
|
||||
(که ذاتاً یک کار شدهاند) صدق نمیکند.
|
||||
|
||||
## ۵. قید جنسیت — سکوت ممنوع
|
||||
|
||||
```php
|
||||
if (($req->constraints['same_gender_as_patient'] ?? false) && $patient?->getGender() === null) {
|
||||
throw new AppException(
|
||||
ErrorCodes::ERR_VALIDATION_001,
|
||||
'برای این سرویس ثبت جنسیت بیمار الزامی است',
|
||||
422
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
نادیده گرفتنِ قید وقتی داده نیست، بدترین حالت است: در کلینیک زیبایی ایران این یک الزام
|
||||
جدی است (مستند بند ۶) و نقض خاموشش یعنی بیمار سر قرار با اپراتور نامناسب روبهرو میشود.
|
||||
|
||||
## ۶. `occupancy` سه حالت — تفاوت عملی
|
||||
|
||||
| حالت | در تسک ۰۶ | در تسک ۰۷ |
|
||||
|---|---|---|
|
||||
| `exclusive` | منبع باید کاملاً آزاد باشد | یک ردیف اشغال با `units = capacity` |
|
||||
| `shared` | `COUNT(اشغالهای فعال) < capacity` | یک ردیف با `units = 1` |
|
||||
| `passive` | مثل `exclusive` | ردیف اشغال با پرچم `passive` — گزارش بهرهوری آن را «کارِ فعال» حساب نمیکند |
|
||||
|
||||
اینجا فقط ستون و اعتبارسنجی است؛ معنیشان در ۰۶ و ۰۷ پیاده میشود. ولی تفاوت را همین
|
||||
حالا در `docs/api/appointment-plan.md` بنویس.
|
||||
|
||||
## ۷. سقفها — حفاظت از تسک ۰۶
|
||||
|
||||
| سقف | مقدار | چرا |
|
||||
|---|---|---|
|
||||
| تعداد بخش یک برنامه | ۲۰ | هر بخش یعنی یک بررسی تداخل per نقطهٔ شروع |
|
||||
| مجموع مدت برنامه | ۴۸۰ دقیقه | برنامهٔ طولانیتر عملاً هیچ روزی جا نمیشود |
|
||||
| تعداد نیازمندی هر بخش | ۱۰ | |
|
||||
| تعداد آیتم انتخابی | ۲۰ | (تسک ۰۴ هم `max_select` دارد، این سقف کلی است) |
|
||||
|
||||
نقض → `422` با پیام روشن، نه تلاش برای محاسبه. مستند بند ۱۷ ریسک «کند شدن جستجو با
|
||||
زیاد شدن منابع» را اولین ریسک میداند؛ سقفها ارزانترین دفاعاند.
|
||||
|
||||
## ۸. edge case ها
|
||||
|
||||
| حالت | رفتار درست |
|
||||
|---|---|
|
||||
| سرویس بدون الگو | یک بخش مجازی با منبع `type=doctor` — رفتار امروزی |
|
||||
| بخش با `requirements = []` | معتبر؛ زمان میگیرد، منبع نمیگیرد |
|
||||
| همهٔ بخشها `fixed_minutes` و آیتمها مدت دارند | مدت آیتمها **نادیده** نمیرود: اگر هیچ بخش سهمی نباشد و آیتم مدت داشته باشد → `422` هنگام ذخیرهٔ الگو |
|
||||
| `duration_share` جمعش ۹۹ | `422` هنگام ذخیره |
|
||||
| دو بخش با یک `sequence` | ترتیب بر اساس `id` پایدار شود، نه خطا |
|
||||
| بخش آیتمی که آیتمش انتخاب نشده | در برنامه نمیآید |
|
||||
| `specific_resource` غیرفعال | `candidates=[]` → خطای انسانی |
|
||||
| استخر خالی | همان |
|
||||
| بیمار مهمان (بدون پرونده) و قید جنسیت | جنسیت از فرم رزرو (`patient_gender` روی `Appointment` هست) گرفته شود |
|
||||
|
||||
## ۹. تست
|
||||
|
||||
```
|
||||
tests/Appointment/Plan/SegmentAssemblerTest.php
|
||||
- جمعآوری بخش سرویس + بخش آیتم
|
||||
- ادغام دو بخش همنوع mergeable → یکی، count بیشینه نه جمع
|
||||
- بخش غیر-mergeable همنوع → جدا میماند
|
||||
tests/Appointment/Plan/SegmentDurationResolverTest.php
|
||||
- fixed ثابت میماند وقتی آیتمها زیاد شوند
|
||||
- سهمی با مدت محاسبهشدهٔ تسک ۰۴ مقیاس میگیرد
|
||||
- جمع مدت بخشها = total_minutes
|
||||
tests/Appointment/Plan/AppointmentPlanBuilderTest.php
|
||||
- سناریوی کامل مستند: چهار بخش، offset های ۰/۵/۳۵/۵۵، total=60
|
||||
- سرویس بدون الگو → یک بخش با منبع doctor (سازگاری)
|
||||
- قطعیت: دو بار build با ورودی یکسان → خروجی یکسان
|
||||
tests/Appointment/Plan/RequirementResolverTest.php
|
||||
- مهارت موجود → کاندید
|
||||
- هیچ کاندید → NoEligibleResourceException با پیام شامل نقش و مهارت
|
||||
- قید جنسیت با بیمار بدون جنسیت → 422
|
||||
- منبع محیط دیگر هرگز کاندید نمیشود
|
||||
tests/Appointment/Plan/PlanLimitsTest.php
|
||||
- ۲۱ بخش → 422 · ۴۸۱ دقیقه → 422
|
||||
```
|
||||
|
||||
## ۱۰. مستندات
|
||||
|
||||
`docs/api/appointment-plan.md` بساز — شامل جدول سه حالت اشغال، مثال کامل خروجی
|
||||
`preview`، و توضیح تفاوت `offset` نمایشی با `occupancy_offset`.
|
||||
Reference in New Issue
Block a user