From b3a5cda80808f8d6dc9efdb6a4a43eadc8d9b9d1 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 18 Jul 2026 22:56:46 +0330 Subject: [PATCH] Refactor insurance share calculation logic in PatientService - Consolidated the calculation of patient and insurance shares into a single method using BillingCalculator. - Introduced new fields in PatientSession to store breakdown of insurance shares and patient share. - Updated the API responses to include the new fields for consistency across payment, invoice, and claims dashboard. - Added migration to backfill existing sessions with appropriate values for the new fields. - Implemented tests to ensure the correctness of the new logic and verify that the breakdown sums to the gross total. - Redesigned the claims dashboard to provide a more user-friendly overview of patient claims and their statuses. --- .claude/prompt/claims-dashboard-redesign.md | 210 ++++++++++++++++++ .../prompt/insurance-shared-calculation.md | 202 +++++++++++++++++ .../admin/components/InvoiceSummaryModal.tsx | 52 +++-- .../admin/components/SessionServiceCard.tsx | 6 + .../admin/components/session/PaymentStep.tsx | 33 ++- assets/admin/types/index.ts | 8 +- docs/api/clinic-services.md | 7 +- docs/api/patient.md | 24 +- migrations/Version20260718184932.php | 47 ++++ src/Billing/Controller/BillingController.php | 36 +-- .../Controller/ClinicServiceController.php | 6 - src/ClinicService/Entity/ServiceItem.php | 7 +- .../Controller/InsuranceController.php | 16 +- src/Patient/Entity/PatientSession.php | 52 ++++- src/Patient/Service/PatientService.php | 93 +++++--- tests/Patient/SessionInsuranceShareTest.php | 153 +++++++++++++ 16 files changed, 842 insertions(+), 110 deletions(-) create mode 100644 .claude/prompt/claims-dashboard-redesign.md create mode 100644 .claude/prompt/insurance-shared-calculation.md create mode 100644 migrations/Version20260718184932.php create mode 100644 tests/Patient/SessionInsuranceShareTest.php diff --git a/.claude/prompt/claims-dashboard-redesign.md b/.claude/prompt/claims-dashboard-redesign.md new file mode 100644 index 00000000..e087fbea --- /dev/null +++ b/.claude/prompt/claims-dashboard-redesign.md @@ -0,0 +1,210 @@ +# بازطراحی صفحه 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`، هرگز `
شرحتاریخ مراجعهتعدادمبلغ کلسهم بیمه (ادعا)تأییدشده