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,81 @@
# تسک ۰۸ — لیست قیمت بازه‌دار و snapshot فاکتور
**فاز:** ۱ (هسته) · **وابستگی:** ۰۴، ۰۷ · **زمان:** ۱۲-۱۴ ساعت
---
## هدف
مستند بند ۱۲: قیمت یک لایهٔ جداست با زندگی خودش (تاریخ اعتبار، مالیات، بیمه، بیعانه) و
قانون پنجم: **تغییر قیمت هرگز نوبت‌های ثبت‌شده را عوض نمی‌کند.**
## وضعیت فعلی
زنجیرهٔ قیمت امروز واقعاً وجود دارد و کار می‌کند:
```
ServiceItem.price_rials
→ Tariff (سال‌محور: findForServiceYear)
→ TenantServiceCoverage / TenantInsurance (بیمهٔ پایه و تکمیلی)
→ DiscountRule + DiscountEngine
→ Invoice / InvoiceItem
→ Payment
```
روی نوبت هم `visit_price_rials`, `deposit_required`, `deposit_amount_rials`,
`insurance_base_id`, `insurance_supplementary_id` هست.
**دو شکاف:**
1. `Tariff` فقط **سال** دارد، بازهٔ دقیق تاریخ ندارد. تغییر تعرفه وسط سال قابل بیان نیست.
2. `visit_price_rials` یک عدد است. فاکتور **تفکیک‌شده** روی نوبت ذخیره نمی‌شود، پس بعد از
تغییر قیمت یا تخفیف، نمی‌شود گفت آن ۲٬۴۰۰٬۰۰۰ ریال از چه تشکیل شده بود.
## دامنه
**هست:**
- `PriceList` (بازهٔ تاریخ + شعبه) و `PriceListItem`
- `PriceSnapshot` — فاکتور تفکیک‌شدهٔ لحظهٔ ثبت نوبت
- `PricingEngine` — زنجیرهٔ هفت‌مرحله‌ای مستند بند ۱۲
- سیاست بیعانه per سرویس/محیط
- اتصال به `BookingService::confirm()` (قلاب مرحلهٔ ۶ تسک ۰۷)
**نیست:** پکیج و دفتر اعتبار (تسک ۱۱)، قوانین قیمت پیشرفته (تسک ۰۹ — `DiscountRule`
موجود فعلاً کافی است).
## Endpoint ها
| متد | مسیر | توضیح |
|---|---|---|
| GET/POST | `/api/v1/price-lists` | لیست قیمت با بازهٔ تاریخ |
| GET/PATCH/DELETE | `/api/v1/price-list/{uuid}` | |
| PUT | `/api/v1/price-list/{uuid}/items` | قیمت سرویس‌ها و آیتم‌ها |
| POST | `/api/v1/price-list/{uuid}/activate` | فعال‌سازی (بررسی تداخل بازه) |
| POST | `/api/v1/pricing/quote` | محاسبهٔ قیمت بدون ثبت |
| GET | `/api/v1/appointment/{uuid}/price-snapshot` | فاکتور تفکیک‌شدهٔ نوبت |
## معیار پذیرش
- ✅ موفق: لیست قیمت «نیمهٔ دوم ۱۴۰۵» با بازهٔ ۱۴۰۵/۰۷/۰۱ تا ۱۴۰۵/۱۲/۲۹ فعال می‌شود؛
`POST /pricing/quote` برای تاریخ مهر قیمت جدید و برای شهریور قیمت قبلی می‌دهد.
- ✅ موفق: `confirm` نوبت → `price_snapshots` یک ردیف با تفکیک کامل دارد:
قیمت پایه، جمع آیتم‌ها، تخفیف‌های اعمال‌شده (با نام و مبلغ هر کدام)، سهم بیمهٔ پایه،
سهم تکمیلی، مالیات، مبلغ نهایی، بیعانه.
- ✅ موفق (**قانون پنجم**): بعد از ثبت نوبت، قیمت سرویس دو برابر می‌شود →
`GET /appointment/{uuid}/price-snapshot` **همان اعداد قبلی** را می‌دهد.
- ✅ موفق: قیمت override شعبه (تسک ۰۴) بر لیست قیمت محیط اولویت دارد.
- ❌ خطا: دو لیست قیمت فعال با بازهٔ هم‌پوشان برای یک شعبه → `422` هنگام `activate`.
- ❌ خطا: `quote` با سرویس محیط دیگر → `404`.
- ⚠️ مرزی: تاریخی که هیچ لیست قیمتی نمی‌پوشاند → fallback به `Tariff` سال، بعد به
`ServiceItem.price_rials`. هرگز صفر یا خطا.
- ⚠️ مرزی: تخفیف بیشتر از مبلغ → مبلغ نهایی صفر، نه منفی.
- ⚠️ مرزی: سقف جمع تخفیف‌ها (`max_total_discount_percent` per محیط) → اعمال شود.
- ⚠️ مرزی: بیعانه بیشتر از مبلغ نهایی → `422` هنگام تنظیم سیاست.
- ⚠️ مرزی: نوبت بدون سرویس (نوبت ویزیت ساده در حالت `slot`) → snapshot با
`visit_price_rials` موجود ساخته شود، نه خالی.
## خروجی
- `src/Pricing/`
- `assets/admin/pages/PriceListsPage.tsx` + `PriceListFormPage.tsx`
- `docs/api/pricing.md`
- به‌روزرسانی `docs/architecture/insurance-billing-system.md`