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
@@ -116,10 +116,17 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
const finalPrice = session.final_price_rials ?? 0;
const discountRials = session.discount_rials ?? 0;
const debt = session.patient_debt_rials ?? 0;
const payments = session.payments ?? [];
const appliedRuleLabel = session.applied_discount_rule_label ?? null;
const payable = Math.max(0, finalPrice - discountRials);
// تفکیک بیمه و مانده را سرور می‌دهد؛ محاسبه‌ی محلی باعث واگرایی با فاکتور می‌شد.
const grossTotal = session.gross_total_rials ?? finalPrice;
const baseInsurance = session.base_insurance_rials ?? 0;
const suppInsurance = session.supplementary_insurance_rials ?? 0;
const hasInsurance = baseInsurance > 0 || suppInsurance > 0;
const payable = session.remaining_rials !== undefined
? session.remaining_rials + (session.paid_total_rials ?? 0)
: Math.max(0, finalPrice - discountRials);
const debt = session.remaining_rials ?? session.patient_debt_rials ?? 0;
return (
<>
@@ -194,8 +201,28 @@ export default function PaymentStep({ recordUuid, session, walletBalance, onCont
{/* خلاصه‌ی مبلغ: قبل از تخفیف / تخفیف / نهایی / منبع */}
<div className="dark:border-[#35343D]" style={{ border: '1px solid #e0e0e0', borderRadius: 8, padding: 12, marginBottom: 16, display: 'flex', flexDirection: 'column', gap: 6 }}>
{hasInsurance && (
<>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>هزینه کل خدمات:</span>
<span className="dark:text-[#D7D8ED]" style={{ color: '#2f2f2f' }}>{formatRial(grossTotal)}</span>
</div>
{baseInsurance > 0 && (
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>سهم بیمه پایه:</span>
<span style={{ color: '#2E7D32' }}>{formatRial(baseInsurance)}</span>
</div>
)}
{suppInsurance > 0 && (
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>سهم بیمه تکمیلی:</span>
<span style={{ color: '#2E7D32' }}>{formatRial(suppInsurance)}</span>
</div>
)}
</>
)}
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>مبلغ قبل از تخفیف:</span>
<span className="dark:text-[#A1A1A1]" style={{ color: '#6B7280' }}>{hasInsurance ? 'سهم بیمار:' : 'مبلغ قبل از تخفیف:'}</span>
<span className="dark:text-[#D7D8ED]" style={{ color: '#2f2f2f' }}>{formatRial(finalPrice)}</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 13 }}>