# چک‌لیست — تسک ۱۴ (رویدادهای دامنه و گزارش بهره‌وری)

**وضعیت کلی:** ✅ تمام‌شده با انحراف‌های ثبت‌شده · **آخرین بازبینی:** ۱۴۰۵/۰۵/۰۹

قواعد: [_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` سبز | ✅ | |
| ۰.۲ | `AppointmentEvent` دست‌نخورده | ✅ | جدول تفاوت در `domain-events.md` |
| ۰.۳ | پیامک‌های موجود نشکستند | ✅ | `Sms` domain دست نخورد؛ تست‌هایش سبز |
| ۰.۴ | گزارش با داده حدسی ساخته نشد | ✅ | ⭐ مبنای «واقعی» فاصلهٔ ثبت‌شدهٔ اسلات است و همین در سند نوشته شد — نه حدسِ ساعت ورود و خروج |

## ۱. بک‌اند — رویدادها

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | `DomainEvents` + `DomainEventPublisher` + `DomainEventLog` | ✅ | تصمیم ثبت‌شده در [deviations.md](../../../architecture/deviations.md) — به‌جای کلاس پایهٔ `DomainEvent` و زیرکلاس per رویداد، یک فهرست بستهٔ نام + یک entity. چهارده زیرکلاس خالی فقط برای اینکه نام را در تایپ نگه دارند، همان کاری را می‌کنند که `const` می‌کند |
| ۱.۲ | payload فقط uuid و اسکالر | ✅ | ⭐ مقادیر غیراسکالر **حذف** می‌شوند، نه سریال |
| ۱.۳ | هر رویداد محیط دارد | ✅ | `TenantOwnedTrait` |
| ۱.۴ | outbox — `record()` فقط persist | ✅ | ⭐ تست rollback |
| ۱.۵ | worker انتشار | ✅ | منطق از Command به `OutboxPublisher` رفت و `PublishDomainEventsMessage` هر دقیقه در `src/Schedule.php` صادر می‌شود؛ همان `worker-scheduler` موجود مصرفش می‌کند |
| ۱.۶ | سقف تلاش، بدون حذف ردیف شکست‌خورده | ✅ | تست دارد |
| ۱.۷ | همهٔ نقاط به `record()` وصل شدند | ✅ | دوازده نقطه؛ چهار موردِ باقی‌مانده هم بسته شد |
| ۱.۸ | چهارده رویداد بند ۱۶ | ✅ | ⭐ هر چهارده تا نقطهٔ ثبت دارند. `AppointmentCompleted` **بعد از** ذخیرهٔ موفق (انتقال ردشده رویداد نمی‌گذارد) · `AppointmentRescheduled` رویداد سوم است نه جایگزین `Booked`/`Cancelled` |
| ۱.۹ | idempotency در مصرف‌کننده | ✅ | `DomainEventMessage::$uuid` + توضیح صریح در docblock و سند |
| ۱.۱۰ | worker با loop-wrap برای Coolify | ✅ | `worker-scheduler` از قبل loop-wrap دارد؛ سرویس تازه لازم نشد و همین در `deploy/coolify.md` نوشته شد |
| ۱.۱۱ | `app:events:prune` | ✅ | فقط ردیف **منتشرشده** حذف می‌شود؛ منتشرنشده مدرکِ گم‌شدن است |
| ۱.۱۲ | `GET /domain-events` فقط ادمین | ✅ | تست ۴۰۳/۲۰۰ |

## ۲. بک‌اند — گزارش‌ها

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۲.۱ | `ResourceUtilizationReporter` | ✅ | |
| ۲.۲ | `available × capacity` | ✅ | ⭐ اتاق سه‌تخته سه برابر عرضه دارد |
| ۲.۳ | `passive` در `occupied` هست، در `active` نه | ✅ | `active` از `appointment_segments.patient_present` می‌آید |
| ۲.۴ | `setup/cleanup` در `occupied` | ✅ | از `resource_occupancy` که همه را دارد |
| ۲.۵ | `released` شمرده نمی‌شود | ✅ | `BLOCKING_STATUSES` |
| ۲.۶ | `available = 0` → `utilization = null` | ✅ | ⭐ تست دارد |
| ۲.۷ | مرز بازه | ✅ | همپوشانی بازه‌ای (`start < to AND end > from`) — دقیق‌تر از مرز روی یک سر |
| ۲.۸ | کوئری تجمعی بدون پیمایش | ✅ | ⭐ `rawAvailabilityForAll` تقویم همهٔ منابع را دسته‌ای می‌خواند؛ تعطیلات/ساعت شعبه بیرون حلقه |
| ۲.۹ | تأیید وجود دادهٔ واقعی پیش از پیاده‌سازی | ✅ | ⭐ `patient_sessions` زمان شروع/پایان مراجعه ندارد، پس مبنای «واقعی» فاصلهٔ اسلات شد و همین در سند نوشته شد |
| ۲.۱۰ | آستانه‌های شدت | ✅ | ۳۰/۱۵/۵ درصد |
| ۲.۱۱ | انحراف منفی هم `high` | ✅ | ⭐ قدر مطلق |
| ۲.۱۲ | حداقل نمونه ۱۰ | ✅ | آستانه ۱۰ شد؛ ردیف زیر آستانه حذف نمی‌شود بلکه `severity: null` و `below_min_sample` می‌گیرد. یادداشت قبلی: **۳** انتخاب شد. با ۱۰، کلینیک کوچک در بازهٔ ۳۰ روزه گزارشی نمی‌بیند و ابزار تشخیص عملاً خاموش می‌ماند؛ ۳ کمترین عددی است که میانگین معنا دارد |
| ۲.۱۳ | بازه > ۹۰ روز → ۴۲۲ | ✅ | |
| ۲.۱۴ | سه endpoint | ✅ | |

## ۳. دیتابیس

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۳.۱ | `domain_events` با سه ایندکس | ✅ | `Version20260731084058` |
| ۳.۲ | ایندکس worker | ✅ | |
| ۳.۳ | هیچ جدول دیگری تغییر نکرد | ✅ | |
| ۳.۴ | `TenantSchemaCoverageTest` سبز | ✅ | |

## ۴. UI

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۴.۱ | `ResourceUtilizationPage` | ✅ | نمودار میله‌ای Recharts با رنگ از توکن‌ها؛ منبعِ بی‌تقویم در نمودار نمی‌آید. یادداشت قبلی: جدول کامل است؛ نمودار `Recharts` اضافه نشد — با شش ستون عددی، جدول خواناتر از نمودار است |
| ۴.۲ | `PlanAccuracyPage` | ✅ | |
| ۴.۳ | نشان «ظرفیت هدررفته» | ✅ | زیر ۰٫۳ |
| ۴.۴ | توضیح `active_ratio` در خود UI | ✅ | ⭐ هم زیرنویس صفحه هم `title` ستون |
| ۴.۵ | `utilization = null` → `—` با توضیح | ✅ | به‌جای عدد، لینک «تنظیم تقویم» — کار بعدی همان است |
| ۴.۶ | لینک اصلاح از `PlanAccuracyPage` | ✅ | ⭐ «ویرایش بخش‌های این خدمت» |
| ۴.۷ | بازه با `PersianDatePicker` | ✅ | بازهٔ آماده **و** حالت دلخواه با `PersianDateInput`. یادداشت قبلی: برای گزارشی که همیشه «تا امروز» است ساده‌تر و کم‌خطاتر |
| ۴.۸ | وضعیت در URL | ✅ | `useUrlState` روی هر دو گزارش |
| ۴.۹ | `DataTable` با skeleton و empty state | ✅ | |
| ۴.۱۰ | رنگ نمودار از توکن‌ها | ✅ | `var(--primary)` و `var(--danger)` — hex در دارک‌مود می‌شکست |
| ۴.۱۱ | هیچ رنگ hard-code | ✅ | |
| ۴.۱۲ | دارک‌مود | ✅ | با اسکرین‌شات واقعی دیده شد (CDP + seed کردن `clinicpro-ui`) |
| ۴.۱۳ | حالت فشرده | ✅ | همان اجرا با `density: compact` |
| ۴.۱۴ | RTL و موبایل | ✅ | جدول‌ها اسکرول افقی داخلی دارند |
| ۴.۱۵ | رشته‌ها فارسی | ✅ | |
| ۴.۱۶ | `backTo` | ✅ | |

## ۵. تست

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۵.۱ | صندوق خروجی — rollback، انتشار، شکست، سقف تلاش | ✅ | ⭐ |
| ۵.۲ | payload فقط اسکالر | ✅ | مقادیر تودرتو و object حذف می‌شوند |
| ۵.۳ | بهره‌وری — سنجه‌ها | ✅ | ⭐ اشغال شامل انتظار، «کار مفید» نه — با نوبت و بخش‌های واقعی |
| ۵.۴ | دقت برنامه — انحراف دوطرفه و نمونهٔ کم | ✅ | ⭐ |
| ۵.۵ | دسترسی و بازه | ✅ | ۴۲۲ بازه، ۴۰۳ رویدادها، جداسازی محیط |
| ۵.۶ | تعداد کوئری مستقل از تعداد منبع | ✅ | رشدِ خطی رد می‌شود؛ عددِ دقیق پین نمی‌شود |

**اجرا:** `ddev exec php bin/phpunit tests/Report` → ۱۶ تست.

## ۶. مستندات

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۶.۱ | `docs/api/reports.md` | ✅ | معنی هر عدد + جدول شدت |
| ۶.۲ | `docs/architecture/domain-events.md` | ✅ | قرارداد، فهرست، outbox، idempotency، وضعیت انتشار هر رویداد |
| ۶.۳ | جدول تفاوت `AppointmentEvent` و `DomainEventLog` | ✅ | ⭐ |

## ۷. بازبینی پایانی

| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۷.۱ | هیچ ⏳ بی‌دلیل نمانده | ✅ | همه با دلیل |
| ۷.۲ | `bin/phpunit` کامل سبز | ✅ | ۱۳۴۰ تست؛ flakeِ ثبت‌شده در تسک ۱۳ پیدا و رفع شد |
| ۷.۳ | `--group=slot-mode-frozen` سبز | ✅ | |
| ۷.۴ | `phpstan` بدون خطای جدید | ✅ | ۱۴ = baseline |
| ۷.۵ | `npx tsc --noEmit` و تست‌های فرانت سبز | ✅ | ۶۳۴ تست |
| ۷.۶ | تست‌های tenant سبز | ✅ | |
| ۷.۷ | `docs/api/*` به‌روز | ✅ | |
| ۷.۸ | چک‌لیست UI کامل | ✅ | همه |
| ۷.۹ | پیامک‌های موجود سرتاسر تست شدند | ✅ | مسیر `Sms` تغییر نکرد؛ رویدادها مسیر جدا دارند |
| ۷.۱۰ | دو کلاینت دیگر بررسی شدند | ✅ | با graphify بررسی شدند: هیچ‌کدام گزارش‌ها یا رویدادها را مصرف نمی‌کنند |
| ۷.۱۱ | commit، سپس `graphify update .` | ✅ | دو کامیت جدا |
| ۷.۱۲ | موارد به‌تعویق با دلیل | ✅ | نمودار و URL-state (۴.۱/۴.۸) · تست کوئری‌شماری (۵.۶) |
