docs/architecture/resource-first-model.md describes the shape: the three entities, why an option is a ServiceItem rather than a fourth table, the four-level resolution chain, the two conditions on the eligibility filter and what each of them prevented, and why containment is a graph beside the display tree rather than the tree itself. docs/api/resource.md gains both offering endpoints with the response captured from a real call, including a row where the price comes from the branch and one where it comes from the resource — the two cases the *_source fields exist for. docs/api/appointment.md documents resource_uuid, the doctor inference, and the nullable resource/service_option in the response. The checklists for tasks 9 to 14 keep their rows but open with a banner saying the task was removed, when, by whose decision, and which commit to revert. They are history now; deleting them would erase the record of work that shipped and was then withdrawn. Verified end to end: 1304 tests, slot-mode-frozen green, phpstan at 14, tsc clean, 648 panel tests, and app:seed-scenarios --reset builds all three environments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 KiB
11 KiB
چکلیست — تسک ۱۴ (رویدادهای دامنه و گزارش بهرهوری)
⛔ این تسک از محصول حذف شد
تصمیم مالک محصول، ۱۴۰۵/۰۵/۱۰: مدل نوبتدهی به منبع/سرویس/گزینه محدود شد و هر چیز خارج از آن حذف شد. کد، جدولها، endpointها، تستها و صفحات پنل این تسک در کامیت «Remove the policy, package, course, cancellation and event subsystems» برداشته شدند.
ریسکش پیش از اجرا دو بار مطرح و دو بار تأیید شد. ردیفهای زیر تاریخچهاند، نه کار جاری؛ برای برگرداندن به همان کامیت رجوع کنید.
مدل جایگزین:
docs/architecture/resource-first-model.mdو چکلیست تسک ۱۵.
وضعیت کلی: ✅ تمامشده با انحرافهای ثبتشده · آخرین بازبینی: ۱۴۰۵/۰۵/۰۹
قواعد: _shared/definition-of-done.md · red-lines.md · ui-conventions.md
۰. خط سرخ
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۰.۱ | --group=slot-mode-frozen سبز |
✅ | |
| ۰.۲ | AppointmentEvent دستنخورده |
✅ | جدول تفاوت در domain-events.md |
| ۰.۳ | پیامکهای موجود نشکستند | ✅ | Sms domain دست نخورد؛ تستهایش سبز |
| ۰.۴ | گزارش با داده حدسی ساخته نشد | ✅ | ⭐ مبنای «واقعی» فاصلهٔ ثبتشدهٔ اسلات است و همین در سند نوشته شد — نه حدسِ ساعت ورود و خروج |
۱. بکاند — رویدادها
| # | مورد | وضعیت | یادداشت |
|---|---|---|---|
| ۱.۱ | DomainEvents + DomainEventPublisher + DomainEventLog |
✅ | تصمیم ثبتشده در 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 (۴.۱/۴.۸) · تست کوئریشماری (۵.۶) |