# بازطراحی صفحه 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`، هرگز `
شرحتاریخ مراجعه تعداد مبلغ کل سهم بیمه (ادعا) تأییدشده