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,299 @@
# فاز ۲ — چارت دندان و هدف‌گیری دندان روی خدمت ویزیت
> پیش‌نیاز: فاز ۱.
> خروجی قابل تست: دندانپزشک وضعیت دندان‌های بیمار را می‌بیند، ثبت خدمت روی دندان انجام می‌دهد و چارت خودکار به‌روز می‌شود.
---
## ۱. هدف
سه چیز:
۱. هر بیمار در هر محیط یک چارت دندان داشته باشد.
۲. ثبت خدمت در ویزیت بتواند دندان و سطح را هدف بگیرد.
۳. چارت بعد از ثبت خدمت خودکار به‌روز شود، و وضعیت‌های قدیمی هم دستی قابل ثبت باشند.
---
## ۲. شماره‌گذاری دندان
استاندارد `FDI` دو رقمی، همان `ISO 3950`.
```
دائمی : 1118, 2128, 3138, 4148
شیری : 5155, 6165, 7175, 8185
```
ذخیره به‌صورت `smallint`، نه رشته.
دلیل: مقایسه و بازه و ایندکس روی عدد کار می‌کند و «۱۱» و «11» دو مقدار جدا نمی‌سازد.
اعتبارسنجی در یک نقطه:
```
src/Dental/Validator/ToothNumberValidator.php
```
استانداردهای `Universal` و `Palmer` در لایهٔ داده استفاده نمی‌شوند.
اگر بعداً لازم شد، فقط لایهٔ نمایش تبدیل می‌کند.
سطوح دندان:
```
M مزیال · D دیستال · O اکلوزال · B باکال · L لینگوال · P پالاتال · I اینسایزال
```
---
## ۳. مدل داده
همه در `src/Dental/Entity`.
### `ToothChart` — جدول `dental_tooth_charts`
| ستون | نوع | توضیح |
|---|---|---|
| `id`, `uuid` | | |
| `entity_type`, `entity_id` | | جفت محیط |
| `patient_record_id` | int | یکتا در هر محیط |
| `dentition_type` | string 20 | `permanent`, `primary`, `mixed` |
| `last_examined_at` | int, nullable | |
| `created_at`, `updated_at` | int | |
یکتایی: `(entity_type, entity_id, patient_record_id)`.
چارت با اولین نیاز ساخته می‌شود، نه با ساخت پرونده.
دلیل: پروندهٔ بیماری که هرگز درمان دندانی نمی‌گیرد نباید ردیف خالی بسازد.
### `ToothStatus` — جدول `dental_tooth_statuses`
| ستون | نوع | توضیح |
|---|---|---|
| `id`, `uuid` | | |
| `chart_id` | int | `ON DELETE CASCADE` |
| `tooth_number` | smallint | FDI |
| `condition` | string 30 | وضعیت کلی دندان |
| `surface_map` | json, nullable | وضعیت هر سطح |
| `note` | string 500, nullable | |
| `source` | string 20 | `manual` یا `visit` |
| `recorded_by_user_id` | int, nullable | |
| `updated_at` | int | |
یکتایی: `(chart_id, tooth_number)`.
مقادیر `condition`:
```
healthy | caries | filled | crown | bridge_pontic | root_canal
implant | missing | extracted | impacted | to_extract | unerupted
```
`surface_map` شکل ثابت دارد:
```json
{ "O": "filled", "M": "caries", "D": "healthy" }
```
**چرا وضعیت جدا از تاریخچه ذخیره می‌شود:**
رندر چارت باید با یک کوئری انجام شود.
اگر وضعیت هر بار از بازپخش تاریخچهٔ ویزیت‌ها ساخته شود، هر باز کردن تب یک محاسبهٔ سنگین است و وضعیت قبل از اولین مراجعه اصلاً قابل ثبت نیست.
هزینهٔ پذیرفته‌شده: این جدول باید بعد از هر ویزیت به‌روز شود و این کار فقط در یک کلاس انجام می‌شود، نه پراکنده در کنترلرها.
### `ToothStatusLog` — جدول `dental_tooth_status_logs`
هر تغییر وضعیت یک ردیف اضافه می‌کند. فقط افزودنی است.
| ستون | نوع |
|---|---|
| `id`, `uuid` | |
| `chart_id`, `tooth_number` | |
| `from_condition`, `to_condition` | string 30 |
| `surface_map_before`, `surface_map_after` | json, nullable |
| `session_service_id` | int, nullable |
| `changed_by_user_id` | int, nullable |
| `changed_at` | int |
دلیل وجودش: چارت سند پزشکی است.
«چه کسی دندان ۱۶ را کشیده‌شده علامت زد» باید قابل جواب دادن باشد.
---
## ۴. هدف‌گیری دندان روی خدمت ویزیت
### تغییر روی دامنهٔ موجود
روی `SessionService` سه ستون اختیاری اضافه می‌شود:
```
tooth_number smallint nullable
surfaces json nullable
target_code string 10 nullable کد فک یا ناحیه
```
**چرا اینجا و نه در جدول دندانی جدا:**
این‌ها ویژگی همان ردیف خدمتِ فاکتورشده‌اند.
جدا کردنشان یعنی برای هر ردیف فاکتور یک join اضافه، و امکان اینکه ردیف فاکتور بدون هدف بماند بدون اینکه کسی بفهمد.
برخلاف `ServiceItem` که تنظیمات است و مشترک همهٔ حوزه‌هاست، `SessionService` سند یک ویزیت است و این سه ستون بخشی از همان سند.
### اعتبارسنجی
در `src/Dental/Service/ToothTargetValidator.php`.
قاعده بر اساس `target_scope` پروفایل خدمت:
| `target_scope` | لازم | ممنوع |
|---|---|---|
| `none` و `mouth` | — | هر سه |
| `tooth` | `tooth_number` | `surfaces`, `target_code` |
| `tooth_surface` | `tooth_number` و حداقل یک سطح | `target_code` |
| `quadrant` | `target_code` از ۱ تا ۴ | `tooth_number`, `surfaces` |
| `arch` | `target_code` برابر `upper` یا `lower` | `tooth_number`, `surfaces` |
قاعدهٔ دوم: `tooth_scope` خدمت با شمارهٔ دندان بخواند.
خدمت `permanent_only` روی دندان ۵۱ خطا می‌دهد.
قاعدهٔ سوم: خدمتی که پروفایل دندانی ندارد، هیچ هدفی نمی‌پذیرد.
خطا با `ERR_VALIDATION_002` و نام فیلد.
### پروجکتور چارت
مسیر: `src/Dental/Service/ToothChartProjector.php`
بعد از ثبت یا ویرایش ردیف خدمت با هدف دندانی:
```
سرویس ترمیمی روی سطوح → همان سطوح در surface_map مقدار filled می‌گیرند
سرویس کشیدن دندان → condition برابر extracted
سرویس درمان ریشه → condition برابر root_canal
سرویس روکش → condition برابر crown
سرویس ایمپلنت → condition برابر implant
بقیه → وضعیت دست نمی‌خورد، فقط لاگ ثبت می‌شود
```
نگاشت خدمت به اثر، در همان `DentalPreset` تعریف می‌شود با کلید `chart_effect`.
دلیل: مدیر می‌تواند خدمت دلخواه بسازد و اثرش را انتخاب کند، بدون اینکه کد عوض شود.
حذف ردیف خدمت، وضعیت را به عقب برنمی‌گرداند.
دلیل: دندان کشیده‌شده با حذف یک ردیف فاکتور برنمی‌گردد.
به‌جایش یک لاگ با توضیح ثبت می‌شود و اصلاح دستی می‌ماند.
---
## ۵. API
`docs/api/dental.md` گسترش پیدا می‌کند.
```
GET /api/v1/dental/chart/{patientRecordUuid}
→ { chart: {...}, teeth: [ { tooth_number, condition, surfaces, note } ] }
PUT /api/v1/dental/chart/{patientRecordUuid}/tooth/{toothNumber}
→ ثبت یا اصلاح دستی وضعیت یک دندان
GET /api/v1/dental/chart/{patientRecordUuid}/tooth/{toothNumber}/history
→ لاگ تغییرات همان دندان
```
دسترسی:
| عملیات | clinic | doctor | secretary | staff |
|---|---|---|---|---|
| دیدن چارت | بله | بیماران خودش | خواندنی | نه |
| ویرایش دستی چارت | نه | بله | نه | نه |
| ثبت هدف دندانی در ویزیت | نه | بله | نه | نه |
خطاها:
| کد | HTTP | حالت |
|---|---|---|
| `ERR_VALIDATION_002` | 422 | شمارهٔ دندان نامعتبر یا هدف ناسازگار |
| `ERR_NOT_FOUND_001` | 404 | پرونده در این محیط نیست |
| `ERR_FORBIDDEN_001` | 403 | نقش مجاز نیست |
اندپوینت ثبت خدمت ویزیت هم کلیدهای تازه می‌گیرد و `docs/api/patient.md` همان جلسه به‌روز می‌شود.
---
## ۶. پنل ادمین
### تب تازه
فایل: `assets/admin/pages/PatientDetailPage.tsx`
- کلید تب: `dental`، برچسب «چارت دندان».
- فقط وقتی حوزهٔ محیط دندانپزشکی است رندر می‌شود.
- بین «پرونده پزشکی» و «ضمیمه» می‌نشیند.
### کامپوننت چارت
فایل: `assets/admin/components/dental/ToothChart.tsx`
این تنها جایی است که ساخت کامپوننت تازه موجه است، چون هیچ کامپوننت موجودی این کار را نمی‌کند.
قواعد:
- `SVG` دست‌نویس، بدون کتابخانهٔ بیرونی.
- هر دندان یک گروه قابل کلیک با شمارهٔ FDI.
- هر سطح یک مسیر جدا، تا کلیک روی سطح جدا از کلیک روی دندان باشد.
- رنگ‌ها فقط از توکن‌های `styles.css`. هیچ رنگ ثابتی در کد کامپوننت نیست.
- چیدمان `RTL` و سازگار با تم تیره.
- فک بالا در ردیف بالا، فک پایین در ردیف پایین، سمت راست بیمار در سمت راست تصویر. این قرارداد در بالای فایل به‌صورت کامنت نوشته شود چون خطای رایج همین است.
- حالت شیری و مختلط: دندان‌های شیری در همان گرید، کوچکتر.
- بدون تعامل هم باید خوانا باشد، چون در چاپ پرونده استفاده می‌شود.
### فرم ثبت خدمت در ویزیت
فایل: `assets/admin/pages/EditSessionPage.tsx`
- بعد از انتخاب خدمت، اگر پروفایل دندانی دارد، انتخابگر هدف نشان داده شود.
- انتخاب دندان از روی همان `ToothChart` انجام شود، نه از یک `select` با ۳۲ گزینه.
- انتخاب سطح فقط وقتی `target_scope` برابر `tooth_surface` است.
---
## ۷. تسک‌ها
| کد | تسک | فایل‌های اصلی | معیار پذیرش |
|---|---|---|---|
| DM2-01 | `ToothNumberValidator` و ثابت‌های FDI | `src/Dental/Validator/` | همهٔ شماره‌های معتبر و نامعتبر تست می‌شوند |
| DM2-02 | موجودیت `ToothChart` | `src/Dental/Entity/` | یکتایی پرونده در محیط |
| DM2-03 | موجودیت `ToothStatus` با `surface_map` | همان | شکل json اعتبارسنجی می‌شود |
| DM2-04 | موجودیت `ToothStatusLog` | همان | فقط افزودنی، بدون متد حذف |
| DM2-05 | سه ستون هدف روی `SessionService` با migration | `src/Patient/Entity/SessionService.php` | ردیف بدون هدف مثل قبل کار می‌کند |
| DM2-06 | `ToothTargetValidator` | `src/Dental/Service/` | هر پنج حالت `target_scope` تست می‌شود |
| DM2-07 | `ToothChartProjector` و نگاشت `chart_effect` | `src/Dental/Service/`, `src/Dental/Preset/` | ثبت کشیدن دندان، وضعیت را عوض می‌کند و لاگ می‌زند |
| DM2-08 | سه اندپوینت چارت | `src/Dental/Controller/DentalChartController.php` | موفق، بدون دسترسی، پروندهٔ محیط دیگر |
| DM2-09 | گسترش ثبت خدمت ویزیت برای هدف دندانی | `src/Patient/Controller/PatientController.php` | هدف ناسازگار ۴۲۲ می‌دهد |
| DM2-10 | کامپوننت `ToothChart` | `assets/admin/components/dental/` | تست: کلیک دندان، کلیک سطح، حالت فقط‌خواندنی |
| DM2-11 | تب چارت در پروندهٔ بیمار | `assets/admin/pages/PatientDetailPage.tsx` | برای حوزهٔ غیر دندانی رندر نمی‌شود |
| DM2-12 | انتخابگر هدف در فرم ثبت خدمت | `assets/admin/pages/EditSessionPage.tsx` | خدمت بدون پروفایل، انتخابگر نشان نمی‌دهد |
| DM2-13 | به‌روزرسانی `docs/api/dental.md` و `docs/api/patient.md` | `docs/api/` | مسیرها با کد یکی است |
---
## ۸. تست‌ها
- ثبت خدمت روی دندان شیری با خدمت `permanent_only`: خطای ۴۲۲.
- ثبت خدمت `tooth_surface` بدون سطح: خطای ۴۲۲.
- ثبت خدمت `arch` با شمارهٔ دندان: خطای ۴۲۲.
- ثبت کشیدن دندان: وضعیت `extracted` و یک ردیف لاگ.
- ویرایش دستی وضعیت: منبع `manual` ثبت می‌شود.
- خواندن چارت بیمار محیط دیگر: خطای ۴۰۴، نه ۴۰۳. دلیل: نباید وجود پرونده در محیط دیگر لو برود.
- منشی چارت را می‌بیند ولی نمی‌تواند ویرایش کند.
- چارت بیماری که هیچ درمانی نگرفته: ساخته می‌شود و همهٔ دندان‌ها `healthy` برمی‌گردند بدون اینکه ۳۲ ردیف در دیتابیس ساخته شود.
---
## ۹. ریسک‌ها
**واگرایی چارت از فاکتور.**
اگر کاربر خدمت را ثبت کند ولی هدف را خالی بگذارد، چارت به‌روز نمی‌شود و کسی نمی‌فهمد.
مهار: برای خدمتی که پروفایل دندانی دارد، هدف اجباری است و ردیف بدون هدف اصلاً ذخیره نمی‌شود.
**تعداد ردیف وضعیت.**
اگر برای هر بیمار ۳۲ ردیف ساخته شود، جدول سریع بزرگ می‌شود.
مهار: فقط دندان‌هایی که وضعیتشان از `healthy` فاصله گرفته ردیف می‌گیرند. بقیه در پاسخ API از پیش‌فرض ساخته می‌شوند.
**سمت چپ و راست جابه‌جا.**
خطای رایج در چارت دندان و در سند پزشکی خطرناک است.
مهار: قرارداد جهت در کامنت بالای کامپوننت، و یک تست که دندان ۱۱ را در جای درست ادعا می‌کند.