# بازطراحی صفحه Claims به داشبورد مدیریتی بیمار-محور
## پروژه
`clinicpro` (Backend Symfony + پنل ادمین React)
پیشنیاز: `clinicpro/.claude/prompt/insurance-shared-calculation.md` باید **قبل** از این پرامپت اجرا شود — ستونهای سهم بیمه/بیمار روی session و منطق واحد محاسبه از آنجا میآید.
## زمینه
صفحه `/admin/claims` امروز یک لیست کارتی از رکوردهای Claim است: هر Claim یک کارت، و داخل هر کارت یک `
` تودرتو از اقلام. این نه DataTable استاندارد پروژه است و نه برای پیگیری پروندههای بیمهی یک بیمار کاربردی — کاربر نمیتواند ببیند «بیمار X مجموعاً چقدر ادعا دارد و چقدر وصول شده».
## هدف
تبدیل صفحه به داشبورد دو سطحی:
- **سطح ۱ — لیست بیماران:** هر ردیف یک بیمار با تجمیع مبالغ و وضعیت کلی.
- **سطح ۲ — جزئیات یک بیمار:** همهی درخواستهای بیمهی آن بیمار با جزئیات کامل و لاگ تغییرات.
## فایلهای مرتبط
| فایل | نقش |
|------|-----|
| `assets/admin/pages/ClaimsPage.tsx` | صفحه فعلی (۳۶۶ خط) — بازنویسی میشود |
| `assets/admin/App.tsx:237` | ثبت route `claims`؛ نقشهای `['doctor','clinic']` + `blockClinicScope` + `` |
| `src/Billing/Controller/BillingController.php:252` | `GET /api/v1/billing/claims` |
| `src/Billing/Controller/BillingController.php:286` | `POST /api/v1/billing/claims/{uuid}/{submit\|approve\|reject\|pay}` |
| `src/Billing/Controller/BillingController.php:332` | `GET /api/v1/billing/reports/insurance-debt` |
| `src/Billing/Entity/Claim.php` | `insuranceId`, `insuranceKind`, `totalClaimedRials`, `totalApprovedRials`, `totalPaidRials`, `status`, `TRANSITIONS` (`:26`), `rejectReason`, `submittedAt`, `settledAt` |
| `src/Billing/Entity/ClaimItem.php` | `invoiceItemId`, `claimedRials` |
| `src/Billing/Entity/Invoice.php` / `InvoiceItem.php` | مبالغ اصلی و سهمها |
| `src/Billing/Service/ClaimService.php` | `buildClaim()` و انتقال وضعیت |
| `assets/admin/components/ui/` | `DataTable`, `Pagination`, `SearchableSelect`, `PageHeader`, `Modal`, `StatCard`, `StatusBadge`, `PersianDatePicker`, `FeatureGate` |
## وضعیت فعلی
`ClaimsPage.tsx` — کارت بهازای هر Claim، totalها بهصورت یک رشته متنی درهم (`:271-276`):
```
ادعا … · تأیید … · پرداخت … · دلیل رد: …
```
و جدول داخلی (`:302-337`):
```tsx
شرح
تاریخ مراجعه
تعداد
مبلغ کل
سهم بیمه (ادعا)
تأییدشده
```
مشکلات موجود که باید در بازطراحی رفع شوند:
- `GET /api/v1/billing/reports/insurance-debt` **دو بار** با دو query key جدا fetch میشود (`:116`, `:122`) — یکی برای پنل بدهی، یکی برای پر کردن dropdown بیمه.
- فیلترها با URL همگام نیستند (همه `useState`)؛ رفرش صفحه همه فیلترها را پاک میکند.
- وضعیتها در `STATUS_META` محلی تعریف شدهاند (`:69-75`) بهجای `StatusBadge`.
- بدون `DoctorClaimsPage.tsx` اشتباه گرفته نشود — آن صفحه (`/admin/doctor-claims`) مربوط به ادعای مالکیت پروفایل پزشک است، نه بیمه.
## وظایف
### ۱. Backend — endpoint تجمیع بیمار-محور
endpoint جدید:
```
GET /api/v1/billing/claims/by-patient
```
پارامترها: `page`, `limit`, `search` (نام/موبایل/کد ملی), `insurance_id`, `doctor_id`, `clinic_id`, `status`, `payment_status`, `from`, `to` (Unix), `sort`, `dir`.
پاسخ paginated (`$this->paginated()`) با هر آیتم:
```json
{
"patient_uuid": "...",
"full_name": "...",
"mobile": "...",
"national_code": "...",
"claims_count": 0,
"total_services_rials": 0,
"total_insurance_rials": 0,
"total_patient_rials": 0,
"total_approved_rials": 0,
"total_paid_rials": 0,
"overall_status": "pending|submitted|approved|rejected|paid|mixed",
"last_activity_at": 0
}
```
قاعده `overall_status`: اگر همه Claimهای بیمار یک وضعیت دارند همان؛ در غیر این صورت `mixed`.
پیادهسازی با DQL و array hydration (`->getArrayResult()`)، تجمیع در SQL (`GROUP BY patient`) — نه در PHP روی کل رکوردها.
### ۲. Backend — endpoint جزئیات یک بیمار
```
GET /api/v1/billing/claims/by-patient/{patientUuid}
```
لیست همه Claimهای آن بیمار (paginated)، هر رکورد شامل:
```json
{
"uuid": "...",
"visit_date": 0,
"doctor_name": "...",
"clinic_name": "...",
"service_title": "...",
"service_base_rials": 0,
"coverage_percent": 0,
"coverage_rials": 0,
"patient_share_rials": 0,
"insurance_share_rials": 0,
"insurance_title": "...",
"insurance_kind": "base|supplementary",
"status": "...",
"submitted_at": 0,
"settled_at": 0,
"tracking_number": null,
"reject_reason": null,
"description": null,
"allowed_transitions": ["submit"],
"logs": [
{ "at": 0, "from": "pending", "to": "submitted", "by": "نام کاربر", "note": null }
]
}
```
`allowed_transitions` از `Claim::TRANSITIONS` بیاید تا فرانت دکمههای مجاز را خودش hardcode نکند.
### ۳. Backend — شماره پیگیری و لاگ تغییرات
دو مورد در دیتابیس وجود ندارند و باید اضافه شوند:
**الف) `trackingNumber`** روی `Claim` (string nullable) — شماره پرونده/پیگیری بیمه. هنگام `submit` قابل ورود باشد و در `POST /api/v1/billing/claims/{uuid}/submit` بهعنوان فیلد اختیاری بدنه پذیرفته شود.
**ب) `ClaimStatusLog`** — entity جدید:
| فیلد | نوع |
|------|-----|
| `claimId` | int |
| `fromStatus` | string nullable |
| `toStatus` | string |
| `note` | text nullable |
| `createdBy` | userId |
| `createdAt` | Unix int |
در `ClaimService` هر جا انتقال وضعیت انجام میشود یک رکورد لاگ نوشته شود. migration لازم است:
```bash
ddev exec php bin/console make:migration
ddev exec php bin/console doctrine:migrations:migrate -n
```
**Backfill:** برای Claimهای موجود از `submittedAt` / `settledAt` رکوردهای لاگ تقریبی ساخته شود تا صفحه جزئیات برای داده قدیمی خالی نباشد.
### ۴. Frontend — سطح ۱: DataTable بیماران
`ClaimsPage.tsx` بازنویسی شود با `DataTable` استاندارد پروژه (نه کارت):
ستونها: نام بیمار | موبایل | کد ملی | تعداد درخواست | مجموع خدمات | مجموع سهم بیمه | مجموع سهم بیمار | وضعیت کلی (`StatusBadge`)
بالای جدول: ردیف `StatCard` با مجموع کل خدمات / کل سهم بیمه / وصولشده / مانده وصولنشده — از `GET /api/v1/billing/reports/insurance-debt` که باید **فقط یک بار** fetch شود (یک `useQuery`، خروجیاش هم پنل و هم options فیلتر بیمه را بدهد).
کلیک روی ردیف → سطح ۲.
### ۵. Frontend — سطح ۲: جزئیات بیمار
مسیر جدید `/admin/claims/:patientUuid` در `App.tsx` با همان roleها و ``.
- `PageHeader` با breadcrumb: پروندههای بیمه ← نام بیمار.
- `DataTable` از درخواستهای بیمه با ستونهای: تاریخ مراجعه | پزشک | کلینیک | سرویس | مبلغ اصلی | پوشش بیمه (درصد + مبلغ) | سهم بیمار | سهم بیمه | وضعیت | تاریخ ارسال | تاریخ پرداخت | شماره پیگیری.
- `actions?(row)` → دکمههای انتقال وضعیت بر اساس `allowed_transitions`.
- کلیک روی ردیف → `Modal` (size `lg`) با توضیحات کامل + **لاگ کامل تغییرات** بهصورت timeline.
- عملیات `reject` باید `reject_reason` بگیرد؛ `submit` باید `tracking_number` اختیاری بگیرد.
### ۶. Frontend — فیلتر، جستجو، مرتبسازی، URL sync
فیلترها در هر دو سطح: بیمار (فقط سطح ۱) | بیمه | پزشک | کلینیک | وضعیت Claim | بازه زمانی (`PersianDatePicker` + میانبر یک ماه/یک سال اخیر) | وضعیت پرداخت.
همهی فیلترها + `page` + `sort`/`dir` باید در **query string آدرس** ذخیره شوند (`useSearchParams`) تا رفرش و اشتراکگذاری لینک کار کند. این رفع مشکل فعلی است.
مرتبسازی سرور-ساید از طریق `sort`/`dir` روی: نام بیمار، تعداد درخواست، مجموع خدمات، مجموع سهم بیمه، آخرین فعالیت.
## نکات مهم
- **الزامی:** صفحه جدید با تم، Layout و کامپوننتهای موجود ساخته شود — بدون طراحی جدید. از `components/ui/` استفاده شود، نه جدول دستساز.
- برای dropdownها **همیشه** `SearchableSelect`، هرگز `