feat(admin): price lists and the appointment invoice card
Task 08's pricing chain was reachable only through the API, so a clinic could not define a price list or see what a booked appointment was actually charged. Price lists - Draft / active / expired are shown as three states because they mean three different things operationally: a draft has no effect on today's price at all - Activation is a separate action rather than a checkbox in the form, matching the backend rule that creating a list must not change anything - "Copy" seeds a new list from an existing one starting the day the old one ends, since most lists are last quarter's with a few numbers moved - "All branches" is an explicit option, not an empty field Invoice card - Renders the recorded chain down to the final amount, hiding zero rows so the card stays readable - A missing invoice renders as a normal state, not an error: an appointment that was never confirmed has no invoice - Says outright that the numbers are from the appointment's own date and later tariff changes do not move them — otherwise someone who edited a price yesterday reads today's older number as a bug Also corrects task 08's checklist: its test section carried a copy-pasted "no UI was built" note against rows whose tests have existed since the task shipped. Replaced with the real test names and the two that genuinely are not covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# چکلیست — تسک ۰۸ (لیست قیمت بازهدار و snapshot فاکتور)
|
||||
|
||||
**وضعیت کلی:** ✅ بکاند و مستندات تکمیل (UI ⏳) · **آخرین بازبینی:** —
|
||||
**وضعیت کلی:** ✅ تمامشده — بکاند، مستندات و UI · **آخرین بازبینی:** —
|
||||
|
||||
قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) ·
|
||||
[red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md)
|
||||
@@ -55,33 +55,38 @@
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۳.۱ | `PriceListsPage` · `PriceListFormPage` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۲ | وضعیت شمسی: پیشنویس/فعال/منقضی با `StatusBadge` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۳ | بازهٔ تاریخ با `PersianDatePicker` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۴ | قیمتها با `PriceInput` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۵ | شعبه با `SearchableSelect` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۶ | **«کپی از لیست قیمت قبلی»** | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۷ | کارت «فاکتور» در `AppointmentDetailPage` با ردیفهای snapshot | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۸ | متن «قیمت بر اساس تاریخ نوبت محاسبه شده است» | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۹ | هیچ رنگ/شعاع hard-code | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۱۰ | دارکمود و حالت فشرده | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۱۱ | RTL و موبایل | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۱۲ | مبالغ با `formatRial` · تاریخها با `formatDate` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۱۳ | وضعیت لیست در URL با `useUrlState` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۱۴ | همهٔ رشتهها فارسی | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۳.۱ | `PriceListsPage` | ✅ | لیست + مودال ویرایش با ردیفهای قیمت |
|
||||
| ۳.۲ | وضعیت شمسی: پیشنویس/فعال/منقضی | ✅ | سه حالت با معنای عملیاتی متفاوت؛ فعالسازی عمل جداست، نه تیک داخل فرم |
|
||||
| ۳.۳ | بازهٔ تاریخ با `PersianDateInput` | ✅ | تبدیل ISO↔Unix در همان صفحه |
|
||||
| ۳.۴ | قیمتها با `PriceInput` | ✅ | |
|
||||
| ۳.۵ | شعبه با `SearchableSelect` | ✅ | «همهٔ شعبهها» گزینهٔ صریح است، نه خالیگذاشتن |
|
||||
| ۳.۶ | «کپی از لیست قیمت قبلی» | ✅ | ⭐ بازه از پایان لیست قبلی شروع میشود |
|
||||
| ۳.۷ | کارت فاکتور در `AppointmentDetailPage` | ✅ | `AppointmentInvoiceCard` — ردیفهای صفر پنهان میشوند |
|
||||
| ۳.۸ | متن «قیمت بر اساس تاریخ نوبت محاسبه شده» | ✅ | ⭐ وگرنه کاربری که دیروز تعرفه را عوض کرده فکر میکند سیستم خراب است |
|
||||
| ۳.۹ | هیچ رنگ/شعاع hard-code | ✅ | |
|
||||
| ۳.۱۰ | دارکمود و حالت فشرده | ⚠️ | فقط توکنها؛ بازبینی چشمی انجام نشد |
|
||||
| ۳.۱۱ | RTL و موبایل | ✅ | جدول لیستها اسکرول افقی داخلی دارد |
|
||||
| ۳.۱۲ | مبالغ با `formatRial` · تاریخ با `formatDate` | ✅ | |
|
||||
| ۳.۱۳ | وضعیت لیست در URL | ✅ | `useUrlState` |
|
||||
| ۳.۱۴ | همهٔ رشتهها فارسی | ✅ | |
|
||||
| ۳.۱۵ | تست فرانت کارت فاکتور | ✅ | چهار تست، شامل «نبودِ فاکتور خطا نیست» |
|
||||
|
||||
## ۴. تست
|
||||
|
||||
| # | مورد | وضعیت | یادداشت |
|
||||
|---|---|---|---|
|
||||
| ۴.۱ | `PriceResolverTest` — ترتیب پنجگانه + fallback | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۲ | `PricingEngineTest` — تخفیف پشتسرهم، سقف، منفی → صفر | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۳ | **invariant**: جمع ردیفها = مبلغ نهایی، در همهٔ سناریوها | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۴ | `PriceSnapshotImmutabilityTest` — قانون پنجم | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۵ | `PriceListActivationTest` — تداخل همسطح ۴۲۲، شعبه/محیط بیتداخل | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۶ | `DepositCalculatorTest` — درصدی با min/max، اولویت سرویس | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۷ | `QuoteTenantTest` — سرویس محیط دیگر ۴۰۴ | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۸ | نوبت بدون سرویس (حالت `slot`) → snapshot با `visit_price_rials` | ⏳ | UI این تسک ساخته نشد — اندپوینتها کامل و از API مصرفشدنیاند. مقصد: پاس UI مالی |
|
||||
| ۴.۱ | ترتیب لایههای قیمت + fallback | ✅ | `testFullChainAppliesInOrder` · `testFallsBackToTheServicePrice` · `testBranchOverrideBeatsThePriceList` |
|
||||
| ۴.۲ | تخفیف پشتسرهم، سقف، منفی → صفر | ✅ | `testDiscountLargerThanTheAmountFloorsAtZero` · `testTotalDiscountCapIsApplied` |
|
||||
| ۴.۳ | invariant جمع ردیفها = مبلغ نهایی | ⚠️ | زنجیره در `testFullChainAppliesInOrder` عددبهعدد سنجیده میشود؛ invariant بهصورت property-based روی سناریوهای تصادفی نوشته نشد |
|
||||
| ۴.۴ | تغییرناپذیری فاکتور (قانون پنجم) | ✅ | ⭐ `testBookedAppointmentKeepsItsOriginalInvoiceAfterAPriceChange` |
|
||||
| ۴.۵ | فعالسازی و تداخل بازه | ✅ | `testOverlappingActiveListsAreRejected` · `testBranchListWinsOverTheGeneralList` · `testDraftListHasNoEffectUntilActivated` |
|
||||
| ۴.۶ | محاسبهٔ بیعانه | ⚠️ | درصدی و مبلغی هر دو در `PricingEngine` هست و در زنجیرهٔ کامل تست میشود؛ تست اختصاصی با min/max ندارد |
|
||||
| ۴.۷ | سرویس محیط دیگر ۴۰۴ | ✅ | `testForeignServiceIsNotFound` |
|
||||
| ۴.۸ | نوبت بدون سرویس → فاکتور با `visit_price_rials` | ⚠️ | مسیرش هست (`recordFlatVisit`)؛ تست اختصاصی ندارد |
|
||||
| ۴.۹ | قیمت منفی رد میشود | ✅ | `testNegativePriceIsRejected` |
|
||||
| ۴.۱۰ | تست فرانت کارت فاکتور | ✅ | چهار تست |
|
||||
|
||||
**اجرا:** `ddev exec php bin/phpunit tests/Pricing` → ۱۲ تست.
|
||||
|
||||
## ۵. مستندات
|
||||
|
||||
|
||||
Reference in New Issue
Block a user