- 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.
300 lines
14 KiB
Markdown
300 lines
14 KiB
Markdown
# فاز ۲ — چارت دندان و هدفگیری دندان روی خدمت ویزیت
|
||
|
||
> پیشنیاز: فاز ۱.
|
||
> خروجی قابل تست: دندانپزشک وضعیت دندانهای بیمار را میبیند، ثبت خدمت روی دندان انجام میدهد و چارت خودکار بهروز میشود.
|
||
|
||
---
|
||
|
||
## ۱. هدف
|
||
|
||
سه چیز:
|
||
|
||
۱. هر بیمار در هر محیط یک چارت دندان داشته باشد.
|
||
۲. ثبت خدمت در ویزیت بتواند دندان و سطح را هدف بگیرد.
|
||
۳. چارت بعد از ثبت خدمت خودکار بهروز شود، و وضعیتهای قدیمی هم دستی قابل ثبت باشند.
|
||
|
||
---
|
||
|
||
## ۲. شمارهگذاری دندان
|
||
|
||
استاندارد `FDI` دو رقمی، همان `ISO 3950`.
|
||
|
||
```
|
||
دائمی : 11–18, 21–28, 31–38, 41–48
|
||
شیری : 51–55, 61–65, 71–75, 81–85
|
||
```
|
||
|
||
ذخیره بهصورت `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 از پیشفرض ساخته میشوند.
|
||
|
||
**سمت چپ و راست جابهجا.**
|
||
خطای رایج در چارت دندان و در سند پزشکی خطرناک است.
|
||
مهار: قرارداد جهت در کامنت بالای کامپوننت، و یک تست که دندان ۱۱ را در جای درست ادعا میکند.
|