feat(migrations): update franchise to percentage in tenant_insurances and tenant_service_coverage

- Changed franchise_rials to franchise_percent in tenant_insurances and tenant_service_coverage tables.
- Reset old rial values to 0/NULL as they are not convertible to percentage.

feat(command): add SeedInsuranceScenarioCommand for seeding insurance data

- Implemented a command to seed supplementary insurance contracts, patients, and claims for a specified doctor.
- Includes functionality for purging existing scenario data and generating new entries with predefined contracts and patient scenarios.
This commit is contained in:
hamed
2026-07-29 13:28:59 +03:30
parent 11b4dcdd34
commit 4f4bce9fe2
31 changed files with 1497 additions and 137 deletions
+11 -8
View File
@@ -7,7 +7,7 @@
export interface CoverageRule {
covered: boolean;
percent: number;
/** فقط در بیمهٔ تکمیلی معنا دارد. */
/** درصد سهم اجباری بیمار از مبلغ تحت پوشش؛ فقط در بیمهٔ تکمیلی معنا دارد. */
franchise: number;
ceiling: number | null;
}
@@ -19,7 +19,7 @@ export interface TenantContract {
insurance_kind: string | null;
is_active?: boolean;
coverage_percent: number;
franchise_rials: number;
franchise_percent: number;
annual_ceiling_rials: number | null;
category_coverages?: Record<string, number>;
}
@@ -28,8 +28,9 @@ export interface TenantContract {
export const DEFAULT_SERVICE_CATEGORY = 'outpatient';
/**
* سهم بیمار یک ردیف: کل − سهم پایه (با سقف) − سهم تکمیلی (روی باقیمانده) + فرانشیزِ تکمیلی.
* فرانشیزِ بیمهٔ پایه در محاسبه دخالت نمی‌کند.
* سهم بیمار یک ردیف: کل − سهم پایه (با سقف) − سهم تکمیلی.
* فرانشیزِ تکمیلی درصدی از همان مبلغِ تحت پوشش است و از سهم تکمیلی کسر می‌شود،
* نه اینکه روی سهم بیمار سوار شود؛ فرانشیزِ بیمهٔ پایه در محاسبه دخالت نمی‌کند.
*/
export function patientShareOf(total: number, base: CoverageRule | null, supp: CoverageRule | null): number {
let baseShare = 0;
@@ -41,12 +42,14 @@ export function patientShareOf(total: number, base: CoverageRule | null, supp: C
}
let suppShare = 0;
if (supp && supp.covered) {
suppShare = Math.round(remaining * (supp.percent / 100));
suppShare = Math.max(
0,
Math.round(remaining * (supp.percent / 100)) - Math.round(remaining * (supp.franchise / 100)),
);
if (supp.ceiling !== null) suppShare = Math.min(suppShare, supp.ceiling);
remaining = remaining - suppShare;
}
return Math.min(remaining + (supp?.franchise ?? 0), total);
return total - baseShare - suppShare;
}
/** درصد مؤثر قرارداد برای یک نوع خدمت؛ نبودِ ردیف → ستون قدیمی قرارداد. */
@@ -61,7 +64,7 @@ export function ruleOf(contract: TenantContract | null, category: string): Cover
return {
covered: true,
percent: contractPercentFor(contract, category),
franchise: contract.insurance_kind === 'supplementary' ? contract.franchise_rials : 0,
franchise: contract.insurance_kind === 'supplementary' ? contract.franchise_percent : 0,
ceiling: contract.annual_ceiling_rials,
};
}