# چک‌لیست — تسک ۱۱ (پکیج و دفتر اعتبار جلسات) **وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:** — قواعد: [_shared/definition-of-done.md](../_shared/definition-of-done.md) · [red-lines.md](../_shared/red-lines.md) · [ui-conventions.md](../_shared/ui-conventions.md) --- ## ۰. خط سرخ | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۰.۱ | `--group=slot-mode-frozen` سبز | ⏳ | | | ۰.۲ | **هیچ ستون `remaining`/`used_count`/`balance` در هیچ جدولی** | ⏳ | ⭐⭐ `LedgerSchemaTest` اجبار می‌کند | | ۰.۳ | دفتر append-only — هیچ `remove`/`update` روی ردیف‌ها | ⏳ | | | ۰.۴ | `WalletTransaction` و منطق کیف پول دست‌نخورده | ⏳ | مفهوم متفاوت | ## ۱. بک‌اند | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۱.۱ | `Package` · `PackageService` · `PatientPackage` · `SessionCreditLedger` | ⏳ | | | ۱.۲ | `CreditLedgerService` — **تنها** نویسندهٔ دفتر | ⏳ | | | ۱.۳ | `balance()` = `SUM(delta)`، بدون هیچ مقدار ذخیره‌شده | ⏳ | ⭐ | | ۱.۴ | پنج `kind` تعریف شد | ⏳ | | | ۱.۵ | `quote` **هرگز** مصرف نمی‌کند؛ فقط `confirm` | ⏳ | ⭐⭐ رفرش صفحه = از دست رفتن جلسه | | ۱.۶ | `PriceQuote` پرچم `packageWillBeConsumed` دارد | ⏳ | | | ۱.۷ | مانده صفر → `false`، **نه استثنا** | ⏳ | ⭐ بیمار نقدی بپردازد | | ۱.۸ | قفل بدبینانه `PESSIMISTIC_WRITE` روی ردیف پکیج | ⏳ | با جدول مقایسه با تسک ۰۷ | | ۱.۹ | `catch UniqueConstraintViolationException` روی `consume` → idempotent | ⏳ | | | ۱.۱۰ | FIFO — قدیمی‌ترین پکیج منقضی‌نشده | ⏳ | LIFO یعنی پول بیمار سوخته | | ۱.۱۱ | `valid_to` هنگام **خرید** محاسبه و ذخیره می‌شود | ⏳ | | | ۱.۱۲ | لغو → ردیف `refund`، نه حذف `consume` | ⏳ | | | ۱.۱۳ | `TODO` با ارجاع به تسک ۱۳ برای سیاست بازگشت اعتبار | ⏳ | نه پرچم نیم‌کاره | | ۱.۱۴ | `adjust` فقط با نقش مدیر و با `reason` اجباری | ⏳ | | | ۱.۱۵ | `app:package:expire` روزانه — ردیف `expiry` با `delta = -balance` | ⏳ | | | ۱.۱۶ | قلاب مرحلهٔ ۴ `PricingEngine` وصل شد | ⏳ | | | ۱.۱۷ | هشت endpoint | ⏳ | | | ۱.۱۸ | `TenantOwnershipChecker` روی هر uuid از request | ⏳ | | ## ۲. دیتابیس | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۲.۱ | چهار جدول | ⏳ | | | ۲.۲ | `price_rials` و `price_paid_rials` از نوع **BIGINT** | ⏳ | پکیج بزرگ | | ۲.۳ | `UNIQUE(appointment_id, kind)` روی دفتر | ⏳ | ⭐ جلوگیری از مصرف دوباره | | ۲.۴ | `session_count`/`price_paid_rials` روی `patient_packages` **snapshot** اند | ⏳ | قانون پنجم | | ۲.۵ | `ON DELETE RESTRICT` روی سرویسِ پکیج فروخته‌شده | ⏳ | | | ۲.۶ | `package_services` در `AGGREGATE_CHILDREN` | ⏳ | | | ۲.۷ | دفتر **جفت tenant** دارد (نه `ENTITIES` مثل کیف پول) | ⏳ | ⭐ دلیل مکتوب | | ۲.۸ | `TenantSchemaCoverageTest` سبز | ⏳ | | ## ۳. UI | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۳.۱ | `PackagesPage` — تعریف با `PriceInput` و انتخاب سرویس | ⏳ | | | ۳.۲ | کارت «پکیج‌ها» در `PatientDetailPage` با مانده و انقضا | ⏳ | | | ۳.۳ | `PatientPackageLedgerPage` — جدول دفتر | ⏳ | | | ۳.۴ | ستون «مانده تجمعی» **محاسبه‌شده در UI**، نه ستون DB | ⏳ | ⭐ به کاربر ثابت می‌کند عدد از کجاست | | ۳.۵ | ستون‌های دفتر: تاریخ، نوع، تغییر، مانده تجمعی، دلیل، ثبت‌کننده، نوبت | ⏳ | | | ۳.۶ | پیام «اعتبار پکیج تمام شده؛ این نوبت نقدی محاسبه می‌شود» | ⏳ | | | ۳.۷ | `DataTable` با skeleton و empty state | ⏳ | | | ۳.۸ | سرویس‌ها با `SearchableSelect` | ⏳ | | | ۳.۹ | `backTo`/`BackButton` روی زیرصفحه‌ها | ⏳ | | | ۳.۱۰ | هیچ رنگ/شعاع hard-code | ⏳ | | | ۳.۱۱ | دارک‌مود و حالت فشرده | ⏳ | | | ۳.۱۲ | RTL و موبایل | ⏳ | | | ۳.۱۳ | مبالغ با `formatRial` · تاریخ با `formatDate` | ⏳ | | | ۳.۱۴ | وضعیت لیست در URL با `useUrlState` | ⏳ | | | ۳.۱۵ | همهٔ رشته‌ها فارسی | ⏳ | | | ۳.۱۶ | دکمهٔ `adjust` فقط برای نقش مدیر نمایش داده می‌شود | ⏳ | `FeatureGate`/بررسی نقش | ## ۴. تست | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۴.۱ | `CreditLedgerTest` — `SUM(delta)` در همهٔ سناریوها، append-only | ⏳ | ⭐ | | ۴.۲ | `LedgerSchemaTest` — هیچ ستون مانده در schema | ⏳ | ⭐⭐ | | ۴.۳ | `QuoteDoesNotConsumeTest` — ده `quote` → مانده بی‌تغییر | ⏳ | ⭐⭐ | | ۴.۴ | `ConcurrentConsumeTest` — مانده منفی نمی‌شود | ⏳ | | | ۴.۵ | `IdempotentConsumeTest` — `confirm` دوبار → یک ردیف | ⏳ | | | ۴.۶ | `FifoTest` | ⏳ | | | ۴.۷ | `ExpiryTest` — ردیف `expiry` و حذف از finder | ⏳ | | | ۴.۸ | `AdjustmentAuthTest` — منشی ۴۰۳، مدیر بی‌دلیل ۴۲۲ | ⏳ | | | ۴.۹ | `PackageTenantTest` — پکیج محیط دیگر ۴۰۴ | ⏳ | | | ۴.۱۰ | `PricingIntegrationTest` — ردیف `package` منفی + invariant تسک ۰۸ حفظ شد | ⏳ | ⭐ | ## ۵. مستندات | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۵.۱ | `docs/api/package.md` | ⏳ | | | ۵.۲ | جدول مقایسهٔ قفل بدبینانه (این تسک) با سطل زمانی (تسک ۰۷) | ⏳ | ⭐ وگرنه «یکدست‌سازی» می‌شود | | ۵.۳ | `docs/architecture/tenancy.md` — تفاوت دفتر اعتبار با کیف پول | ⏳ | ⭐ اشتباه گرفتنشان = نشتی مالی | ## ۶. بازبینی پایانی | # | مورد | وضعیت | یادداشت | |---|---|---|---| | ۶.۱ | هیچ 🔄 و ⏳ بی‌دلیل نمانده | ⏳ | | | ۶.۲ | `bin/phpunit` کامل سبز | ⏳ | | | ۶.۳ | `--group=slot-mode-frozen` سبز | ⏳ | | | ۶.۴ | `PatientWalletTenantTest` موجود سبز ماند | ⏳ | | | ۶.۵ | `phpstan` بدون خطای جدید | ⏳ | | | ۶.۶ | `npx tsc --noEmit` و `yarn test` سبز | ⏳ | | | ۶.۷ | تست‌های tenant سبز | ⏳ | | | ۶.۸ | `docs/api/*` به‌روز | ⏳ | | | ۶.۹ | چک‌لیست UI کامل | ⏳ | | | ۶.۱۰ | دو کلاینت دیگر بررسی شدند | ⏳ | مبلغ صفر در رزرو درست نمایش داده می‌شود؟ | | ۶.۱۱ | commit، سپس `graphify update .` | ⏳ | | | ۶.۱۲ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | سیاست بازگشت اعتبار → تسک ۱۳ |