# چک‌لیست — تسک ۱۱ (پکیج و دفتر اعتبار جلسات)

**وضعیت کلی:** ⏳ شروع نشده · **آخرین بازبینی:** —

قواعد: [_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 .` | ⏳ | |
| ۶.۱۲ | موارد به‌تعویق با دلیل و تسک مقصد | ⏳ | سیاست بازگشت اعتبار → تسک ۱۳ |
