Add dental module phase 4 and phase 5 documentation, including treatment estimates, clinical operations, and preset content

- Introduced phase 4 documentation detailing treatment estimates, data models, state machines, API endpoints, and new dashboard metrics.
- Added phase 5 documentation covering consumables, lab operations, periodontal charts, sterilization cycles, clinical images, and a checklist for professional review.
- Created a preset content document outlining default dental service packages and protocols for clinics.
This commit is contained in:
hamed
2026-08-20 16:21:08 +03:30
parent 601d211d6f
commit 2f030edef1
12 changed files with 1794 additions and 3 deletions
@@ -0,0 +1,200 @@
# فاز ۳ — داشبورد دندانپزشکی
> پیش‌نیاز: فاز ۱ و ۲.
> خروجی قابل تست: مدیر و پزشک و پذیرش، شاخص‌های دندانپزشکی را در همان داشبورد فعلی خودشان می‌بینند.
---
## ۱. هدف
شاخص‌های مخصوص حوزهٔ فعالیت به داشبورد نقشی اضافه شوند، بدون دست‌زدن به منطق داشبورد عمومی و بدون کند کردن داشبورد حوزه‌های دیگر.
---
## ۲. الگو
همان الگوی `TreatmentWorkflow` که در `docs/adr/0005` ثبت شده.
```
src/Dashboard/Metric/DomainMetricProvider.php اینترفیس، با تگ app.domain_metric_provider
src/Dashboard/Metric/DomainMetricRegistry.php انتخاب بر اساس کد حوزه
src/Dashboard/Metric/MetricRequest.php بازه، نقش، محیط، فیلترها
src/Dashboard/Metric/MetricSet.php خروجی استاندارد
src/Dental/Metric/DentalMetricProvider.php پیاده‌سازی دندانپزشکی
```
اینترفیس:
```php
interface DomainMetricProvider
{
public function supports(?string $practiceDomainCode): bool;
/** @return list<string> کلید شاخص‌هایی که این نقش می‌بیند */
public function keysFor(string $role): array;
public function collect(MetricRequest $request): MetricSet;
}
```
محیطی که حوزه‌اش `null` است یا provider ندارد، هیچ بخش تازه‌ای نمی‌گیرد.
برخلاف `TreatmentWorkflowRegistry` اینجا پیاده‌سازی پیش‌فرض لازم نیست؛ نبودن provider یعنی بخش دندانی رندر نمی‌شود.
دلیل: شاخص خالی بدتر از نبودن بخش است.
هر متد `collect` باید تعداد کوئری ثابت داشته باشد، مستقل از تعداد شاخص.
یعنی شاخص‌های هم‌منبع در یک کوئری جمع شوند.
---
## ۳. رجیستری شاخص‌ها
هر شاخص یک کلید ثابت دارد.
فرانت هیچ فرمولی محاسبه نمی‌کند و فقط مصرف‌کنندهٔ عدد است.
دلیل: اگر تعریف «تولید» در دو جا نوشته شود، دیر یا زود دو عدد متفاوت نشان داده می‌شود.
### شاخص‌های قابل محاسبه در فاز ۳
| کلید | تعریف | منبع |
|---|---|---|
| `production` | جمع مبلغ ناخالص ویزیت‌های انجام‌شده در بازه | `patient_sessions` |
| `collection` | جمع پرداخت‌های ثبت‌شده در بازه | `payments` و `session_payments` |
| `collection_rate` | وصولی تقسیم بر تولید | مشتق |
| `avg_per_visit` | تولید تقسیم بر تعداد ویزیت | مشتق |
| `production_per_doctor` | تولید به تفکیک پزشک | `patient_sessions` |
| `service_mix` | سهم هر گروه کاتالوگ از تولید | `session_services` و `service_catalog_categories` |
| `chair_utilization` | دقایق رزروشدهٔ منابع نوع اتاق تقسیم بر دقایق ظرفیت | `resource_occupancy` و `resource_calendars` |
| `no_show_rate` | نوبت‌های حاضرنشده تقسیم بر کل نوبت‌ها | `appointments` |
| `cancellation_rate` | نوبت‌های لغوشده تقسیم بر کل | `appointments` |
| `new_patients` | بیماران با اولین ویزیت در بازه | `patient_sessions` |
| `ar_outstanding` | جمع بدهی معوق بیماران | `patient_sessions` |
| `treatments_by_group` | تعداد خدمت انجام‌شده به تفکیک گروه دندانی | `session_services` |
| `teeth_treated` | تعداد دندان‌های درمان‌شدهٔ یکتا در بازه | `session_services` |
### شاخص‌هایی که در این فاز وجود ندارند
| کلید | چرا |
|---|---|
| `case_acceptance_rate` | ورودی‌اش برآورد درمان است که فاز ۴ ساخته می‌شود |
| `unscheduled_treatment` | همان |
| `redo_rate` | نیازمند حالت درمان مجدد روی ردیف برآورد |
| `lab_cost_ratio` | فاز ۵ |
| `consumable_cost_ratio` | فاز ۵ |
| `recall_response_rate` | نیازمند سازوکار ریکال که پروژه هنوز ندارد |
این فهرست عمداً در سند مانده تا کسی فکر نکند فراموش شده‌اند.
### دو تعریف که نباید قاطی شوند
**تولید** جمع مبلغ کاری است که انجام شده.
**وصولی** جمع پولی است که رسیده.
این دو از دو جدول متفاوت می‌آیند و هیچ‌کدام نباید از دیگری استنتاج شود.
فاصلهٔ بینشان همان چیزی است که نرخ وصول را معنادار می‌کند.
---
## ۴. نماها
| نقش | شاخص‌های صفحهٔ اصلی |
|---|---|
| مدیر کلینیک یا مطب | `collection`, `collection_rate`, `chair_utilization`, `new_patients`, `ar_outstanding`, `service_mix` |
| پزشک | تولید شخصی، `avg_per_visit` خودش، `treatments_by_group` خودش، `teeth_treated` |
| پذیرش | اشغال یونیت امروز، `no_show_rate`, `cancellation_rate`, صف نوبت امروز |
حداکثر شش کارت در صفحهٔ اصلی هر نقش.
بقیه پشت drill-down.
دلیل: داشبورد با بیست کارت خوانده نمی‌شود و کاربر به‌جای تصمیم، اسکرول می‌کند.
فیلتر مشترک: بازهٔ تاریخ جلالی، پزشک، گروه خدمت، یونیت.
---
## ۵. API
اندپوینت‌های نقشی موجود دست نمی‌خورند.
پاسخشان یک کلید تازه می‌گیرد:
```
GET /api/v1/dashboard/clinic
→ { ..., domain_metrics: { code: "dental", metrics: { ... } } | null }
```
اگر محیط حوزه ندارد یا provider ندارد، مقدار `null` است.
یک اندپوینت تازه برای بازه و روند:
```
GET /api/v1/dashboard/domain-metrics?from&to&doctorUuid?&resourceUuid?&groupUuid?
→ { code, metrics: { <key>: { value, previous_value, change_percent } } }
GET /api/v1/dashboard/domain-metrics/trend?metric=production&from&to&interval=day|week|month
→ { points: [ { date, value } ] }
```
`date` میلادی برمی‌گردد و تبدیل جلالی در فرانت انجام می‌شود.
دلیل: رشتهٔ جلالی در پاسخ، مرتب‌سازی و بازه‌گیری را در فرانت می‌شکند.
بازهٔ پیش‌فرض سی روز.
حداکثر بازهٔ مجاز یک سال، وگرنه خطای ۴۲۲.
دلیل: بدون سقف، یک درخواست می‌تواند کل جدول ویزیت را اسکن کند.
`docs/api/dashboard.md` همان جلسه به‌روز می‌شود.
---
## ۶. پنل ادمین
فایل: `assets/admin/pages/DashboardPage.tsx`
- بخش «شاخص‌های دندانپزشکی» بعد از کارت‌های عمومی، فقط وقتی `domain_metrics` مقدار دارد.
- کارت‌ها با `StatCard` موجود.
- روند با `Recharts` که پروژه از قبل دارد.
- drill-down با `DataTable` موجود.
- انتخاب بازه با `PersianDateInput` موجود.
- فیلتر پزشک و یونیت با `SearchableSelect`.
- `TanStack Query` با `staleTime` معقول، چون این اعداد ثانیه‌ای عوض نمی‌شوند.
هیچ کامپوننت تازه‌ای ساخته نمی‌شود.
---
## ۷. تسک‌ها
| کد | تسک | فایل‌های اصلی | معیار پذیرش |
|---|---|---|---|
| DM3-01 | اینترفیس و رجیستری `DomainMetricProvider` | `src/Dashboard/Metric/` | محیط بدون حوزه، `null` می‌گیرد |
| DM3-02 | `MetricRequest` و `MetricSet` | همان | بازهٔ بزرگتر از یک سال ۴۲۲ می‌دهد |
| DM3-03 | `DentalMetricProvider` بخش مالی | `src/Dental/Metric/` | تولید و وصولی با داده‌ی ساختگی درست است |
| DM3-04 | بخش اشغال یونیت | همان | منبع بدون تقویم، صفر می‌دهد نه خطا |
| DM3-05 | بخش نوبت، حاضرنشده و لغو | همان | مخرج صفر، `null` می‌دهد نه تقسیم بر صفر |
| DM3-06 | بخش ترکیب خدمات و دندان‌های درمان‌شده | همان | خدمت بدون گروه در «سایر» می‌رود |
| DM3-07 | اتصال `domain_metrics` به چهار اندپوینت نقشی | `src/Dashboard/Controller/DashboardController.php` | داشبورد حوزه‌های دیگر کوئری اضافه نمی‌زند |
| DM3-08 | اندپوینت بازه و روند | همان | فیلترها ترکیبی کار می‌کنند |
| DM3-09 | بخش دندانی در صفحهٔ داشبورد | `assets/admin/pages/DashboardPage.tsx` | برای حوزهٔ زیبایی رندر نمی‌شود |
| DM3-10 | نمودار روند و drill-down | همان | خالی بودن داده، حالت خالی نشان می‌دهد نه خطا |
| DM3-11 | به‌روزرسانی `docs/api/dashboard.md` | `docs/api/` | نمونهٔ پاسخ با کد یکی است |
---
## ۸. تست‌ها
- محیط بدون حوزه: `domain_metrics` برابر `null` و هیچ کوئری اضافه‌ای اجرا نمی‌شود.
- محیط زیبایی: همان.
- محیط دندانپزشکی بدون هیچ ویزیت: همهٔ شاخص‌ها صفر یا `null`، بدون خطا.
- تقسیم بر صفر در هر نرخ: `null` برمی‌گردد و فرانت خط تیره نشان می‌دهد.
- پزشک فقط عدد خودش را می‌بیند، حتی با فیلتر پزشک دیگر.
- منشی به شاخص‌های مالی دسترسی ندارد.
- بازهٔ بزرگتر از یک سال: ۴۲۲.
- تست کارایی: تعداد کوئری با افزایش تعداد شاخص ثابت می‌ماند.
---
## ۹. ریسک‌ها
**کندی داشبورد با رشد داده.**
تصمیم فاز ۳ محاسبهٔ زنده است.
مهار: سقف بازه، ایندکس روی ستون‌های تاریخ و محیط، و یک تست کارایی که تعداد کوئری را قفل می‌کند.
اگر بعداً کند شد، جدول تجمیع پشت همین سرویس اضافه می‌شود بدون تغییر API.
**دو عدد متفاوت برای یک شاخص.**
مهار: هیچ فرمولی در فرانت نوشته نمی‌شود.