Files
CBS-Mobile-App/src/modules/deposit/utils/depositCalculators.ts
T

148 lines
3.8 KiB
TypeScript

import type {
DepositAccount,
DepositInterestPayoutType,
DepositStatus,
} from '../../../types/app';
const MS_PER_DAY = 1000 * 60 * 60 * 24;
const clamp = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);
export const calculateFixedDepositMaturity = (
principal: number,
annualRate: number,
tenureMonths: number,
payoutType: DepositInterestPayoutType = 'cumulative',
) => {
if (principal <= 0 || annualRate <= 0 || tenureMonths <= 0) {
return 0;
}
const years = tenureMonths / 12;
if (payoutType === 'non-cumulative') {
return principal + principal * (annualRate / 100) * years;
}
const quarterlyRate = annualRate / 100 / 4;
const periods = years * 4;
return principal * Math.pow(1 + quarterlyRate, periods);
};
export const calculateRecurringDepositMaturity = (
monthlyInstallment: number,
annualRate: number,
tenureMonths: number,
) => {
if (monthlyInstallment <= 0 || annualRate <= 0 || tenureMonths <= 0) {
return 0;
}
const monthlyRate = annualRate / 100 / 12;
let maturityAmount = 0;
for (let month = 0; month < tenureMonths; month += 1) {
maturityAmount += monthlyInstallment * Math.pow(1 + monthlyRate, tenureMonths - month);
}
return maturityAmount;
};
export const getDepositCanonicalStatus = (
deposit: Pick<DepositAccount, 'status' | 'maturityDate'>,
now = new Date(),
): DepositStatus => {
if (deposit.status === 'closed') {
return 'closed';
}
return new Date(deposit.maturityDate).getTime() <= now.getTime() ? 'matured' : 'active';
};
export const getDaysUntilMaturity = (maturityDate: string, now = new Date()) => {
const diff = new Date(maturityDate).getTime() - now.getTime();
return Math.ceil(diff / MS_PER_DAY);
};
export const isDepositNearMaturity = (
deposit: Pick<DepositAccount, 'status' | 'maturityDate'>,
now = new Date(),
) => {
const daysUntilMaturity = getDaysUntilMaturity(deposit.maturityDate, now);
return getDepositCanonicalStatus(deposit, now) === 'active' && daysUntilMaturity <= 7;
};
export const getDepositProgress = (
startDate: string,
maturityDate: string,
now = new Date(),
) => {
const start = new Date(startDate).getTime();
const maturity = new Date(maturityDate).getTime();
const current = now.getTime();
if (maturity <= start) {
return 1;
}
return clamp((current - start) / (maturity - start), 0, 1);
};
export const calculatePrematureClosureAmount = (
deposit: Pick<
DepositAccount,
'kind' | 'principalAmount' | 'monthlyInstallment' | 'interestRate' | 'startDate' | 'maturityDate' | 'maturityAmount' | 'prematurePenaltyRate' | 'status'
>,
now = new Date(),
) => {
const canonicalStatus = getDepositCanonicalStatus(deposit, now);
if (canonicalStatus === 'closed') {
return {
penaltyAmount: 0,
netAmount: deposit.maturityAmount,
};
}
if (canonicalStatus === 'matured') {
return {
penaltyAmount: 0,
netAmount: deposit.maturityAmount,
};
}
const elapsedMonths = Math.max(
1,
Math.round((now.getTime() - new Date(deposit.startDate).getTime()) / (MS_PER_DAY * 30)),
);
const currentValue =
deposit.kind === 'fd'
? calculateFixedDepositMaturity(
deposit.principalAmount,
deposit.interestRate,
elapsedMonths,
'non-cumulative',
)
: calculateRecurringDepositMaturity(
deposit.monthlyInstallment ?? 0,
deposit.interestRate,
elapsedMonths,
);
const investedBase =
deposit.kind === 'fd'
? deposit.principalAmount
: (deposit.monthlyInstallment ?? 0) * elapsedMonths;
const penaltyAmount = currentValue * ((deposit.prematurePenaltyRate ?? 0) / 100);
return {
penaltyAmount,
netAmount: Math.max(currentValue - penaltyAmount, investedBase * 0.9),
};
};