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.
This commit is contained in:
hamed
2026-07-18 22:56:46 +03:30
parent 466b649988
commit b3a5cda808
16 changed files with 842 additions and 110 deletions
+29 -23
View File
@@ -12,6 +12,9 @@ interface SessionData {
session_at: number | null; paid_at: number | null;
services_total_rials: number; consumables_total_rials: number;
discount_rials: number; final_price_rials: number; paid_total_rials: number;
gross_total_rials?: number; base_insurance_rials?: number;
supplementary_insurance_rials?: number; patient_share_rials?: number;
remaining_rials?: number;
payments: SessionPayment[]; consumables: SessionConsumable[];
}
interface Invoice {
@@ -64,11 +67,22 @@ export default function InvoiceSummaryModal({ invoiceUuid, onClose }: { invoiceU
const session = inv?.session ?? null;
const paid = inv?.status === 'paid';
// با session: مبالغ واقعی پرداخت (مبلغ نهایی منهای تخفیف و پرداخت‌ها)؛ بدون آن heuristic قبلی.
// مانده را سرور می‌دهد (session.remaining_rials)؛ محاسبه‌ی محلی با صفحه‌ی پرداخت واگرا می‌شد.
const remaining = session
? Math.max(0, session.final_price_rials - (session.discount_rials ?? 0) - session.paid_total_rials)
? session.remaining_rials ?? Math.max(0, session.final_price_rials - (session.discount_rials ?? 0) - session.paid_total_rials)
: inv ? (paid ? 0 : inv.patient_rials) : 0;
const paidAmount = session ? session.paid_total_rials : inv ? inv.total_rials - remaining : 0;
// یک شکل واحد برای خلاصه‌ی مالی؛ با session از خود مراجعه، بدون آن از فاکتور.
const summary = {
services: session ? session.services_total_rials : inv?.total_rials ?? 0,
consumables: session?.consumables_total_rials ?? 0,
discount: session?.discount_rials ?? 0,
baseInsurance: session?.base_insurance_rials ?? inv?.base_insurance_rials ?? 0,
suppInsurance: session?.supplementary_insurance_rials ?? inv?.supplementary_rials ?? 0,
patientShare: session?.patient_share_rials ?? session?.final_price_rials ?? inv?.patient_rials ?? 0,
gross: session?.gross_total_rials ?? inv?.total_rials ?? 0,
};
// وضعیت واقعی پرداخت (مستقل از وضعیت فریزشده‌ی فاکتور): تسویه‌شده اگر مانده صفر.
const statusLabel = session ? (remaining <= 0 ? 'تسویه شده' : 'بدهکار') : (inv ? (STATUS_LABEL[inv.status] ?? inv.status) : '');
@@ -101,27 +115,19 @@ export default function InvoiceSummaryModal({ invoiceUuid, onClose }: { invoiceU
: [['-', '-', '-']]}
/>
)}
{session ? (
<SectionTable
title="خلاصه مالی"
cols={['جمع مبلغ سرویس', 'جمع مبلغ کالا', 'تخفیف', 'سهم بیمه پایه', 'سهم بیمه تکمیلی', 'مبلغ کل']}
rows={[[
formatRial(session.services_total_rials), formatRial(session.consumables_total_rials),
session.discount_rials > 0 ? formatRial(session.discount_rials) : '-',
formatRial(inv.base_insurance_rials), formatRial(inv.supplementary_rials),
formatRial(session.final_price_rials),
]]}
/>
) : (
<SectionTable
title="خلاصه مالی"
cols={['جمع مبلغ سرویس', 'سهم بیمه پایه', 'سهم بیمه تکمیلی', 'سهم بیمار', 'مبلغ کل']}
rows={[[
formatRial(inv.total_rials), formatRial(inv.base_insurance_rials),
formatRial(inv.supplementary_rials), formatRial(inv.patient_rials), formatRial(inv.total_rials),
]]}
/>
)}
<SectionTable
title="خلاصه مالی"
cols={['جمع مبلغ سرویس', 'جمع مبلغ کالا', 'سهم بیمه پایه', 'سهم بیمه تکمیلی', 'سهم بیمار', 'تخفیف', 'مبلغ نهایی']}
rows={[[
formatRial(summary.services),
summary.consumables > 0 ? formatRial(summary.consumables) : '-',
formatRial(summary.baseInsurance),
formatRial(summary.suppInsurance),
formatRial(summary.patientShare),
summary.discount > 0 ? formatRial(summary.discount) : '-',
formatRial(Math.max(0, summary.patientShare - summary.discount)),
]]}
/>
{session && (
<SectionTable
title="پرداختی ها"