# فاز ۴ — برآورد درمان و نرخ پذیرش

> پیش‌نیاز: فاز ۱ تا ۳.
> خروجی قابل تست: دندانپزشک برآورد چندخدمتی می‌سازد، بیمار تصمیم می‌گیرد، و نرخ پذیرش در داشبورد دیده می‌شود.

---

## ۱. چرا این فاز جدا افتاد

در جلسهٔ تصمیم‌گیری قرار شد فاز ۱ بدون برآورد جلو برود تا زودتر به خروجی برسیم.
هزینه‌اش این است که تا این فاز، چهار شاخص وجود ندارند:
نرخ پذیرش، درمان زمان‌بندی‌نشده، درمان مجدد، و ارزش معوق طرح.

---

## ۲. نام‌گذاری

واژهٔ انگلیسی `Treatment Plan` در `CONTEXT.md` ممنوع است، چون بین `TreatmentProtocol` و `TreatmentCase` ابهام می‌ساخت.

واژهٔ این مفهوم:

**Treatment Estimate** — فهرست پیشنهادی خدمات روی دندان‌های مشخص، با قیمت، که به بیمار ارائه می‌شود و بیمار کل یا بخشی از آن را می‌پذیرد.
در فارسی همان «طرح درمان» است، چون زبان روزمرهٔ دندانپزشک همین است.
جدایی نام انگلیسی و فارسی عمدی است: کد باید بدون ابهام باشد، رابط کاربری باید آشنا باشد.

---

## ۳. مدل داده

### `TreatmentEstimate` — جدول `dental_treatment_estimates`

| ستون | نوع | توضیح |
|---|---|---|
| `id`, `uuid` | | |
| `entity_type`, `entity_id` | | جفت محیط |
| `patient_record_id` | int | |
| `doctor_id` | int, nullable | پزشک ارائه‌دهنده |
| `title` | string 150 | |
| `status` | string 20 | |
| `total_rials` | int | جمع همهٔ ردیف‌ها |
| `accepted_rials` | int | جمع ردیف‌های پذیرفته‌شده |
| `presented_at` | int, nullable | لحظهٔ ارائه به بیمار |
| `decided_at` | int, nullable | لحظهٔ تصمیم بیمار |
| `expires_at` | int, nullable | |
| `created_at`, `updated_at` | int | |

### `TreatmentEstimateItem` — جدول `dental_treatment_estimate_items`

| ستون | نوع | توضیح |
|---|---|---|
| `id`, `uuid` | | |
| `estimate_id` | int | `ON DELETE CASCADE` |
| `service_item_id` | int | `ON DELETE RESTRICT` |
| `name_snapshot` | string 200 | نام خدمت در لحظهٔ ارائه |
| `tooth_number` | smallint, nullable | |
| `surfaces` | json, nullable | |
| `target_code` | string 10, nullable | |
| `quantity` | smallint | |
| `unit_price_rials` | int | |
| `amount_rials` | int | |
| `status` | string 20 | |
| `phase` | smallint | فاز درمان، برای اولویت‌بندی |
| `sort_order` | smallint | |
| `payer_type` | string 20 | پیش‌فرض `self_pay` |
| `insurance_ref_id` | int, nullable | فقط رزرو شده، بدون منطق |
| `session_service_id` | int, nullable | وقتی انجام شد به ردیف فاکتور وصل می‌شود |

`name_snapshot` و `unit_price_rials` عمدی‌اند.
همان دلیلی که `TreatmentCaseArea` اسنپ‌شات می‌گیرد و در `docs/adr/0002` ثبت شده:
برآوردی که به بیمار داده شده، سند است و با تغییر تعرفهٔ فردا نباید بازنویسی شود.

دو ستون `payer_type` و `insurance_ref_id` تنها نقطهٔ اتصال بیمه‌اند.
در این فاز هیچ محاسبه‌ای رویشان نوشته نمی‌شود.

---

## ۴. ماشین حالت

### برآورد

```
draft ──▶ presented ──▶ accepted ──▶ in_progress ──▶ completed
              │             │              │
              ├──▶ partially_accepted ─────┤
              ├──▶ rejected                └──▶ cancelled
              └──▶ expired
```

قواعد گذار:

- `draft → presented`: حداقل یک ردیف و جمع بزرگتر از صفر. `presented_at` ثبت می‌شود.
- `presented → accepted | partially_accepted | rejected`: با تصمیم بیمار. `decided_at` ثبت می‌شود و `accepted_rials` از جمع ردیف‌های پذیرفته‌شده حساب می‌شود.
- `presented → expired`: با یک job زمان‌بندی‌شده بعد از N روز. پیش‌فرض پیشنهادی ۹۰ روز، قابل تنظیم در `Config`.
- `accepted → in_progress`: با اولین ردیفی که انجام می‌شود.
- `→ completed`: وقتی همهٔ ردیف‌های پذیرفته‌شده انجام یا لغو شده‌اند. توسط پروجکتور، نه دستی.

### ردیف

```
proposed ──▶ accepted ──▶ scheduled ──▶ done
    │            │            │           │
    └▶ rejected  └▶ cancelled └▶ cancelled └▶ redo ──▶ scheduled
```

`redo` حالت مستقل است، نه حذف رکورد.
دلیل: ورودی شاخص کیفیت است.
اگر درمان مجدد با ویرایش رکورد قبلی جایگزین شود، آن شاخص برای همیشه از بین می‌رود.

---

## ۵. چرا `expired` لازم است

نرخ پذیرش باید مخرجش برآوردهایی باشد که در آن بازه **ارائه** شده‌اند، نه برآوردهایی که در آن بازه **تصمیم‌گیری** شده‌اند.

اگر مخرج بر اساس تصمیم باشد، برآوردهایی که هنوز جواب نگرفته‌اند از مخرج بیرون می‌مانند و نرخ به‌صورت مصنوعی بالا می‌رود.
`expired` همان چیزی است که برآورد بی‌جواب قدیمی را از حالت معلق در می‌آورد.

---

## ۶. اتصال به موتور موجود

ردیف پذیرفته‌شده وقتی زمان‌بندی می‌شود:

- اگر خدمتش پروتکل فعال دارد، همان مسیر موجود `TreatmentCaseStarter` یک `TreatmentCase` باز می‌کند.
- اگر ندارد، فقط یک نوبت ساخته می‌شود.

هیچ مسیر رزرو تازه‌ای نوشته نمی‌شود.

وقتی ردیف انجام شد و در ویزیت فاکتور شد، `session_service_id` پر می‌شود و وضعیت ردیف `done` می‌گیرد.
از همان‌جا پروجکتور فاز ۲ چارت را به‌روز می‌کند.

هیچ ستون پولی از برآورد به `TreatmentSession` نمی‌رود.
قاعدهٔ `docs/adr/0006` سر جایش می‌ماند.

---

## ۷. API

```
GET    /api/v1/dental/estimates?patientRecordUuid=&status=
POST   /api/v1/dental/estimate
GET    /api/v1/dental/estimate/{uuid}
PATCH  /api/v1/dental/estimate/{uuid}
POST   /api/v1/dental/estimate/{uuid}/present
POST   /api/v1/dental/estimate/{uuid}/decision
DELETE /api/v1/dental/estimate/{uuid}

POST   /api/v1/dental/estimate/{uuid}/items
PATCH  /api/v1/dental/estimate-item/{uuid}
DELETE /api/v1/dental/estimate-item/{uuid}
POST   /api/v1/dental/estimate-item/{uuid}/schedule
```

دسترسی:

| عملیات | clinic | doctor | secretary |
|---|---|---|---|
| ساخت و ویرایش برآورد | نه | بله | نه |
| ارائه به بیمار | نه | بله | بله |
| ثبت تصمیم بیمار | بله | بله | بله |
| زمان‌بندی ردیف پذیرفته‌شده | بله | بله | بله |

دلیل اینکه ثبت تصمیم را منشی هم دارد: تصمیم بیمار معمولاً پشت میز پذیرش گفته می‌شود.

`docs/api/dental.md` گسترش پیدا می‌کند.

---

## ۸. شاخص‌های تازه در داشبورد

| کلید | تعریف |
|---|---|
| `case_acceptance_rate` | جمع پذیرفته‌شده تقسیم بر جمع ارائه‌شده، در بازهٔ ارائه |
| `unscheduled_treatment` | جمع مبلغ ردیف‌های پذیرفته‌شده بدون نوبت |
| `redo_rate` | ردیف‌های درمان مجدد تقسیم بر ردیف‌های انجام‌شده |
| `estimate_backlog` | جمع مبلغ برآوردهای ارائه‌شدهٔ بی‌جواب |

اضافه‌شدنشان به `DentalMetricProvider` است، بدون تغییر اینترفیس.

---

## ۹. پنل ادمین

- تب تازه در پروندهٔ بیمار: «طرح درمان».
- ساخت ردیف با انتخاب خدمت و انتخاب دندان از روی همان `ToothChart` فاز ۲.
- نمای چاپی برای دادن به بیمار.
- ثبت تصمیم به‌صورت ردیف‌به‌ردیف با `Switch`، نه یک دکمهٔ کلی. دلیل: پذیرش جزئی حالت رایج است.
- کارت‌های تازه در بخش دندانی داشبورد.

---

## ۱۰. تسک‌ها

| کد | تسک | معیار پذیرش |
|---|---|---|
| DM4-01 | موجودیت‌های برآورد و ردیف | `TenantSchemaCoverageTest` سبز |
| DM4-02 | ماشین حالت برآورد در یک کلاس جدا | گذار غیرمجاز `AppException` می‌دهد |
| DM4-03 | ماشین حالت ردیف | همان |
| DM4-04 | محاسبهٔ جمع و جمع پذیرفته‌شده در پروجکتور | ویرایش ردیف، جمع را همگام نگه می‌دارد |
| DM4-05 | job انقضا با مهلت قابل تنظیم | برآورد قدیمی `expired` می‌شود، برآورد پذیرفته‌شده نه |
| DM4-06 | اندپوینت‌های برآورد | همهٔ حالت‌های دسترسی تست می‌شوند |
| DM4-07 | زمان‌بندی ردیف و اتصال به `TreatmentCaseStarter` | خدمت پروتکل‌دار دوره باز می‌کند، بقیه فقط نوبت |
| DM4-08 | اتصال ردیف به `SessionService` هنگام انجام | وضعیت `done` و به‌روزرسانی چارت |
| DM4-09 | چهار شاخص تازه | مخرج صفر، `null` می‌دهد |
| DM4-10 | تب طرح درمان در پنل | پذیرش جزئی درست ثبت می‌شود |
| DM4-11 | نمای چاپی | در تم تیره هم درست چاپ می‌شود |
| DM4-12 | مستندات API | مسیرها با کد یکی است |

---

## ۱۱. تصمیم‌های باز

این‌ها قبل از شروع فاز ۴ باید جواب بگیرند:

۱. مهلت انقضای برآورد چند روز باشد؟ پیشنهاد ۹۰ روز.
۲. درمان مجدد هزینه‌دار است یا صفر؟ روی شاخص تولید اثر مستقیم دارد.
۳. قیمت ردیف برآورد از تعرفهٔ لحظهٔ ارائه می‌آید یا قابل ویرایش دستی است؟ پیشنهاد: پیش‌فرض از تعرفه، قابل ویرایش با ثبت لاگ.
۴. آیا یک بیمار می‌تواند همزمان دو برآورد ارائه‌شده داشته باشد؟ پیشنهاد: بله، ولی داشبورد باید هشدار بدهد.
