diff --git a/.claude/prompt/insurance-pricing-and-selects-cleanup.md b/.claude/prompt/insurance-pricing-and-selects-cleanup.md new file mode 100644 index 00000000..256bbb53 --- /dev/null +++ b/.claude/prompt/insurance-pricing-and-selects-cleanup.md @@ -0,0 +1,98 @@ +# یکپارچه‌سازی قراردادهای بیمه، select یکدست، و تعداد خدمت در مراجعه + +## پروژه + +`clinicpro` (admin frontend + کمی backend برای `quantity`) + +## زمینه + +چهار مورد به‌هم‌مرتبط در پنل ادمین: + +1. صفحه‌ی `/admin/insurance-pricing` دو بخش دارد که هم‌پوشانی دارند → باید یکی شود. +2. select listها در کل پنل یک‌شکل نیستند (بعضی ` + + {opts.map(o => )} + +``` +و در کنارش کامپوننت `SearchableSelect` (`assets/admin/components/ui/SearchableSelect.tsx`) برای بخش خدمات استفاده می‌شود. ظاهر این دو فرق دارد. + +### تعداد خدمت + +`src/Patient/Entity/SessionService.php` — فیلد `quantity` ندارد: +```php +private ServiceItem $serviceItem; +private ?ClinicStaff $staff = null; +private int $priceRials; // = serviceItem price، بدون ضرب در تعداد +``` +`PatientService::createSession` services را تک‌واحدی جمع می‌زند: +```php +$servicesTotal = array_sum(array_column($serviceItems, 'price_rials')); +``` +فرانت (`NewSessionPage.tsx`) هم هر خدمت را یک‌بار اضافه می‌کند (chip بدون تعداد). + +### select بیمه در فرم مراجعه + +`NewSessionPage.tsx` گزینه‌های بیمه را از `GET /api/v1/insurance-pricing` می‌گیرد (`pricing.insurances`)، ولی آن endpoint **همه‌ی بیمه‌های فعال** را برمی‌گرداند (نه فقط قراردادهای tenant). درصد هم از `patient_share_rials` همان endpoint محاسبه می‌شود — که بعد از حذف بخش قیمت‌گذاری دیگر منبع درستی نیست. + +## وظایف + +### ۱. ادغام صفحه‌ی insurance-pricing + +- `InsurancePricingSection` و endpoint/جدول `entity_insurance_pricing` را کنار بگذار (از `InsurancePricingPage` حذف کن). قرارداد (`TenantInsuranceContracts`) تنها منبع باشد. +- `TenantInsuranceContracts` باید واضح هم basic هم supplementary را پشتیبانی کند (الان هم می‌کند؛ متن/گروه‌بندی را شفاف کن: دو دسته «بیمه پایه» و «بیمه تکمیلی»). +- اگر «قیمت ویزیت آزاد» جایی لازم است، آن را به‌صورت یک فیلد ساده در همان صفحه/پروفایل نگه‌دار یا حذف کن (تصمیم را در پیاده‌سازی مستند کن). مسیر `/admin/insurance-pricing` و آیتم سایدبار باقی بماند. +- `InsurancePricingSection.tsx` حذف یا خالی شود؛ importها پاک‌سازی. + +### ۲. یک کامپوننت select یکدست + +- یک کامپوننت `Select` در `assets/admin/components/ui/` بساز (یا `SearchableSelect` را به‌عنوان استاندارد واحد بپذیر) با ظاهر یکسان: همان استایل، RTL، chevron، حالت focus/disabled، placeholder. +- همه‌ی ` - setShares((p) => ({ ...p, [i.insurance_id]: e.target.value })) - } - /> - - - ); - }; - - return ( -
-
-

قیمت‌گذاری ویزیت بر اساس بیمه

-

- مبلغ ویزیت آزاد و سهم بیمار به ازای هر بیمه را تعیین کنید. خالی‌گذاشتن یک بیمه یعنی پذیرفته نمی‌شود. -

-
- - {isLoading ? ( -
در حال بارگذاری...
- ) : ( -
-
- - setFreeVisit(e.target.value)} - /> - - {freeVisit !== '' ? formatRial(Number(freeVisit) || 0) : '—'} - -
- - {basics.length > 0 && ( -
-
{TYPE_LABEL.basic}
-
{basics.map(renderRow)}
-
- )} - - {basics.length === 0 && ( -
بیمه پایه‌ای در سیستم تعریف نشده است.
- )} - -
- -
-
- )} -
- ); -} diff --git a/assets/admin/components/TenantInsuranceContracts.tsx b/assets/admin/components/TenantInsuranceContracts.tsx index 6c269725..64c48409 100644 --- a/assets/admin/components/TenantInsuranceContracts.tsx +++ b/assets/admin/components/TenantInsuranceContracts.tsx @@ -168,28 +168,38 @@ export default function TenantInsuranceContracts() { هنوز با هیچ بیمه‌ای قرارداد فعال ندارید. ) : ( -
- {contracts.map((c: Contract) => ( -
-
-
- {c.insurance_name ?? `#${c.insurance_id}`} - {c.insurance_kind && {KIND_LABEL[c.insurance_kind] ?? c.insurance_kind}} +
+ {(['basic', 'supplementary'] as const).map((kind) => { + const group = contracts.filter((c: Contract) => c.insurance_kind === kind); + if (group.length === 0) return null; + return ( +
+
+ بیمه {KIND_LABEL[kind]}
-
- پوشش {c.coverage_percent}٪ - {c.franchise_rials > 0 && ` · فرانشیز ${formatRial(c.franchise_rials)}`} - {c.annual_ceiling_rials != null && ` · سقف ${formatRial(c.annual_ceiling_rials)}`} +
+ {group.map((c: Contract) => ( +
+
+
{c.insurance_name ?? `#${c.insurance_id}`}
+
+ پوشش {c.coverage_percent}٪ + {c.franchise_rials > 0 && ` · فرانشیز ${formatRial(c.franchise_rials)}`} + {c.annual_ceiling_rials != null && ` · سقف ${formatRial(c.annual_ceiling_rials)}`} +
+
+ + +
+ ))}
- - -
- ))} + ); + })}
)}
diff --git a/assets/admin/pages/InsurancePricingPage.tsx b/assets/admin/pages/InsurancePricingPage.tsx index 12215481..781dd643 100644 --- a/assets/admin/pages/InsurancePricingPage.tsx +++ b/assets/admin/pages/InsurancePricingPage.tsx @@ -1,16 +1,14 @@ import PageHeader from '../components/ui/PageHeader'; -import InsurancePricingSection from '../components/InsurancePricingSection'; import TenantInsuranceContracts from '../components/TenantInsuranceContracts'; export default function InsurancePricingPage() { return (
-
); }