Files
clinicpro/docs/new_feture/dental-module/phase-3-dashboard.md
T
hamed 2f030edef1 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.
2026-08-20 16:21:08 +03:30

201 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# فاز ۳ — داشبورد دندانپزشکی
> پیش‌نیاز: فاز ۱ و ۲.
> خروجی قابل تست: مدیر و پزشک و پذیرش، شاخص‌های دندانپزشکی را در همان داشبورد فعلی خودشان می‌بینند.
---
## ۱. هدف
شاخص‌های مخصوص حوزهٔ فعالیت به داشبورد نقشی اضافه شوند، بدون دست‌زدن به منطق داشبورد عمومی و بدون کند کردن داشبورد حوزه‌های دیگر.
---
## ۲. الگو
همان الگوی `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.
**دو عدد متفاوت برای یک شاخص.**
مهار: هیچ فرمولی در فرانت نوشته نمی‌شود.