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,145 @@
|
||||
# معماری — تسک ۰۸
|
||||
|
||||
## ساختار فایل
|
||||
|
||||
```
|
||||
src/Pricing/
|
||||
├── Entity/
|
||||
│ ├── PriceList.php
|
||||
│ ├── PriceListItem.php
|
||||
│ ├── PriceSnapshot.php
|
||||
│ ├── PriceSnapshotLine.php
|
||||
│ └── DepositPolicy.php
|
||||
├── Service/
|
||||
│ ├── PricingEngine.php # ارکستراتور هفتمرحلهای
|
||||
│ ├── PriceResolver.php # قیمت پایه: لیست → تعرفه → سرویس
|
||||
│ ├── SnapshotWriter.php
|
||||
│ └── DepositCalculator.php
|
||||
├── Dto/{PriceQuote, PriceLine}.php
|
||||
├── Controller/{PriceListController, PricingController}.php
|
||||
└── Repository/…
|
||||
```
|
||||
|
||||
## `PricingEngine` — هفت مرحلهٔ مستند
|
||||
|
||||
```php
|
||||
public function quote(QuoteRequest $req): PriceQuote
|
||||
{
|
||||
$lines = [];
|
||||
|
||||
// ۱ قیمت پایهٔ سرویس (از لیست قیمت معتبر در تاریخ رزرو)
|
||||
$lines[] = PriceLine::base($this->resolver->servicePrice($req->service, $req->branch, $req->at));
|
||||
|
||||
// ۲ جمع قیمت آیتمهای انتخابی
|
||||
foreach ($req->options as $option) {
|
||||
$lines[] = PriceLine::option($option, $this->resolver->optionPrice($option, $req->branch, $req->at));
|
||||
}
|
||||
|
||||
// ۳ قوانین قیمت به ترتیب اولویت ← DiscountEngine موجود، بعداً تسک ۰۹
|
||||
$lines = $this->discounts->apply($lines, $req);
|
||||
|
||||
// ۴ کسر از اعتبار پکیج ← قلاب تسک ۱۱ (فعلاً no-op)
|
||||
$lines = $this->packages->consume($lines, $req);
|
||||
|
||||
// ۵ مالیات و سهم بیمه ← AppointmentInsuranceService موجود
|
||||
$lines = $this->insurance->apply($lines, $req);
|
||||
$lines = $this->tax->apply($lines, $req);
|
||||
|
||||
// ۶ بیعانه
|
||||
$deposit = $this->depositCalculator->forQuote($lines, $req);
|
||||
|
||||
// ۷ خروجی تفکیکشده (ذخیره فقط در confirm انجام میشود)
|
||||
return new PriceQuote($lines, $deposit);
|
||||
}
|
||||
```
|
||||
|
||||
هر مرحله یک سرویس مستقل با اینترفیس خودش. مرحلهٔ ۳ و ۴ از روز اول در زنجیره هستند حتی
|
||||
وقتی خالیاند — همان دلیل تسک ۰۵: امضای عمومی بعداً عوض نشود.
|
||||
|
||||
## `PriceResolver` — ترتیب اولویت
|
||||
|
||||
```
|
||||
۱. ServiceBranchOverride.price_rials (تسک ۰۴ — اختصاصیترین)
|
||||
۲. PriceListItem از PriceList فعالی که تاریخ رزرو را میپوشاند و شعبهاش مطابق است
|
||||
۳. PriceListItem از PriceList فعال محیط (بدون شعبه)
|
||||
۴. Tariff::findForServiceYear(سال شمسی تاریخ رزرو) ← موجود، دستنخورده
|
||||
۵. ServiceItem.price_rials ← آخرین fallback
|
||||
```
|
||||
|
||||
هیچوقت خطا یا صفر برنمیگرداند. سطر ۴ و ۵ تضمین میکنند همهٔ دادههای موجود بدون هیچ
|
||||
لیست قیمتی درست کار کنند.
|
||||
|
||||
**تاریخ مبنا:** تاریخ **رزرو** (`slot_start`)، نه تاریخ ثبت. مستند بند ۱۲: «از لیست قیمت
|
||||
معتبر در تاریخ رزرو». اگر بیمار امروز برای سه ماه بعد نوبت بگیرد، قیمت آن روز اعمال میشود.
|
||||
این تصمیم را در `docs/api/pricing.md` صریح بنویس — دو تفسیر دارد و پشتیبانی از هر دو
|
||||
غیرممکن است.
|
||||
|
||||
## `PriceSnapshot` — فاکتور منجمد
|
||||
|
||||
```php
|
||||
class PriceSnapshot
|
||||
{
|
||||
use TenantOwnedTrait;
|
||||
private Appointment $appointment;
|
||||
private int $baseRials;
|
||||
private int $optionsRials;
|
||||
private int $discountRials;
|
||||
private int $insuranceBaseRials;
|
||||
private int $insuranceSupplementaryRials;
|
||||
private int $taxRials;
|
||||
private int $finalRials;
|
||||
private int $depositRials;
|
||||
private array $appliedPolicyIds = []; // قانونهای اعمالشده — قانون پنجم مستند
|
||||
private int $createdAt;
|
||||
private Collection $lines; // PriceSnapshotLine
|
||||
}
|
||||
```
|
||||
|
||||
`appliedPolicyIds` از روز اول: مستند بند ۸ میگوید «هر نوبت فهرست قانونهایی که رویش
|
||||
اعمال شده را ذخیره میکند». تسک ۰۹ نسخهٔ قانونها را هم اضافه میکند؛ فعلاً شناسهٔ
|
||||
`DiscountRule` ها ثبت میشود.
|
||||
|
||||
`PriceSnapshotLine` ردیفهای تفکیکشده: نوع (`base`|`option`|`discount`|`insurance`|`tax`)،
|
||||
نام، مبلغ، ارجاع اختیاری به منبع (سرویس/آیتم/قانون).
|
||||
|
||||
## رابطه با `Invoice` موجود
|
||||
|
||||
`Invoice`/`InvoiceItem` (دامنهٔ `Billing`) **باقی میمانند** و کارشان صورتحساب مراجعهٔ
|
||||
انجامشده است. `PriceSnapshot` کار متفاوتی میکند: قیمت **لحظهٔ رزرو**.
|
||||
|
||||
| | `PriceSnapshot` | `Invoice` |
|
||||
|---|---|---|
|
||||
| کِی ساخته میشود | `confirm` نوبت | پایان مراجعه |
|
||||
| چه چیزی را ثبت میکند | آنچه قرار بود پرداخت شود | آنچه واقعاً انجام و صورتحساب شد |
|
||||
| تغییر میکند | هرگز | تا تسویه |
|
||||
|
||||
اگر بیمار سر نوبت خدمت اضافه بگیرد، `Invoice` فرق میکند و `PriceSnapshot` نه — و همین
|
||||
تفاوت، منبع گزارش «اختلاف پیشبینی و واقعیت» است.
|
||||
|
||||
این جدول را در `docs/architecture/insurance-billing-system.md` اضافه کن، وگرنه اولین
|
||||
کسی که هر دو را میبیند یکی را حذف میکند.
|
||||
|
||||
## سیاست بیعانه
|
||||
|
||||
```php
|
||||
class DepositPolicy
|
||||
{
|
||||
use TenantOwnedTrait;
|
||||
private ?ServiceItem $service = null; // null = پیشفرض محیط
|
||||
private string $mode; // none | fixed | percent
|
||||
private int $value = 0;
|
||||
private ?int $minRials = null;
|
||||
private ?int $maxRials = null;
|
||||
}
|
||||
```
|
||||
|
||||
`DepositCalculator` اختصاصیترین سیاست را میگیرد (سرویس بر محیط) و مقدار را به
|
||||
`Appointment.deposit_required/deposit_amount_rials` موجود مینویسد — ستونهای جدید لازم نیست.
|
||||
|
||||
## پنل ادمین
|
||||
|
||||
- `PriceListsPage.tsx` — لیست با بازهٔ شمسی و وضعیت (پیشنویس/فعال/منقضی)
|
||||
- `PriceListFormPage.tsx` — بازهٔ تاریخ با `PersianDatePicker`، شعبه با `SearchableSelect`،
|
||||
جدول سرویسها با `PriceInput`
|
||||
- در `AppointmentDetailPage.tsx` یک کارت «فاکتور» با ردیفهای snapshot
|
||||
- «کپی از لیست قیمت قبلی» — بدون آن، کلینیک با ۲۰۰ سرویس هرگز لیست جدید نمیسازد
|
||||
Reference in New Issue
Block a user