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

**فاز:** ۴ (بهینه‌سازی) · **وابستگی:** ۰۷ · **زمان:** ۸-۱۰ ساعت

---

## هدف

دو چیز از مستند:

1. **بند ۱۶** — فهرست رویدادهایی که سیستم منتشر می‌کند تا سیستم‌های دیگر (پیامک،
   حسابداری، گزارش) به آن‌ها گوش بدهند.
2. **بند ۱۷، ریسک سوم** — «کلینیک بخش‌های نوبت را اشتباه تعریف کند → ظرفیت غلط حساب
   می‌شود». راه‌حل مستند: **گزارش بهره‌وری منابع برای پیدا کردن اشکال.**

گزارش بهره‌وری تنها ابزاری است که به کلینیک می‌گوید تعریف بخش‌هایش درست است یا نه.
بدون آن، تسک ۰۵ یک ابزار قدرتمند بدون بازخورد است.

## وضعیت فعلی

- `AppointmentEvent` وجود دارد و تاریخچهٔ تغییر وضعیت نوبت را ثبت می‌کند
- `symfony/messenger` + `symfony/redis-messenger` + `symfony/scheduler` در استک هستند
- پیامک از راه `Sms` domain و `messenger:consume async` کار می‌کند
- تسک‌های ۰۷ تا ۱۳ هر کدام یک `dispatch` گذاشته‌اند بدون یک قرارداد واحد

## دامنه

**هست:**
- قرارداد واحد رویداد دامنه: نام، payload (فقط uuid)، زمان انتشار (بعد از commit)
- ثبت همهٔ رویدادهای بند ۱۶ مستند
- `domain_events` — جدول outbox برای تضمین انتشار
- گزارش بهره‌وری منابع: ساعت آزاد / اشغال / انتظار / کار فعال per منبع per بازه
- گزارش «مدت پیش‌بینی‌شده در برابر مدت واقعی» برای تشخیص تعریف غلط بخش‌ها

**نیست:** پیش‌بینی عدم حضور، پیشنهاد هوشمند وقت (فاز ۴ مستند، خارج از دامنه).

## Endpoint ها

| متد | مسیر | توضیح |
|---|---|---|
| GET | `/api/v1/reports/resource-utilization` | بهره‌وری منابع در بازه |
| GET | `/api/v1/reports/plan-accuracy` | مقایسهٔ مدت پیش‌بینی و واقعی per سرویس |
| GET | `/api/v1/domain-events` | (ادمین) رویدادهای منتشرشده — عیب‌یابی |

## فهرست رویدادها (مستند بند ۱۶)

```
HoldCreated              AppointmentBooked
AppointmentCancelled     AppointmentRescheduled
PatientNoShow            AppointmentCompleted
ResourceBlocked          ResourceReleased
CourseStarted            CourseSessionCompleted
CourseCompleted          PackagePurchased
CreditConsumed           CreditRefunded
```

## معیار پذیرش

- ✅ موفق: ثبت نوبت → یک ردیف در `domain_events` با نام `AppointmentBooked` و
  payload شامل `appointment_uuid`؛ و `GET /domain-events` آن را نشان می‌دهد.
- ✅ موفق: رویداد **بعد از** commit منتشر می‌شود. تست: تراکنشی که rollback می‌شود
  هیچ رویدادی منتشر نمی‌کند.
- ✅ موفق: گزارش بهره‌وری برای اپراتور مریم در یک هفته →
  `{ available_minutes: 2400, occupied_minutes: 1800, active_minutes: 1200, utilization: 0.75, active_ratio: 0.50 }`.
- ✅ موفق (**تشخیص تعریف غلط بخش‌ها**): سرویسی که `total_minutes` پیش‌بینی‌اش ۶۰ است ولی
  میانگین مدت واقعی مراجعاتش ۹۰ دقیقه → `GET /reports/plan-accuracy` آن را با
  `deviation_percent: +50` و `severity: 'high'` برمی‌گرداند.
- ✅ موفق: منبعی با `active_ratio` زیر ۰.۳ در گزارش با نشان «ظرفیت هدررفته» می‌آید —
  یعنی بخش‌های `passive` یا انتظار زیادی به آن نسبت داده شده.
- ❌ خطا: گزارش با بازهٔ بزرگ‌تر از ۹۰ روز → `422`.
- ❌ خطا: منشی روی `GET /domain-events` → `403` (فقط `ROLE_ADMIN`).
- ⚠️ مرزی: منبع بدون هیچ تقویم → `available_minutes: 0` و `utilization: null` (نه صفر —
  تقسیم بر صفر معنایی متفاوت دارد).
- ⚠️ مرزی: بخش‌های `passive` در `occupied_minutes` می‌آیند ولی در `active_minutes` نه.
- ⚠️ مرزی: `setup/cleanup` در `occupied_minutes` می‌آید (منبع واقعاً اشغال بوده).
- ⚠️ مرزی: رویداد تکراری (پیام دوباره از messenger) → مصرف‌کننده idempotent، نه رویداد.

## خروجی

- `src/Shared/Event/` — قرارداد رویداد + outbox
- `src/Report/` — دو گزارش
- `assets/admin/pages/ResourceUtilizationPage.tsx` + `PlanAccuracyPage.tsx`
- `docs/api/reports.md` + `docs/architecture/domain-events.md`
