Files
cbsnexora/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component.ts
T

314 lines
10 KiB
TypeScript

/**
* Copyright since 2025 Mifos Initiative
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/** Angular Imports */
import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { MatDivider } from '@angular/material/divider';
import { NgIf, NgSwitch, TitleCasePipe, CurrencyPipe } from '@angular/common';
import {
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow
} from '@angular/material/table';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { MatStepperPrevious } from '@angular/material/stepper';
import { FindPipe } from '../../../../pipes/find.pipe';
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
import { YesnoPipe } from '../../../../pipes/yesno.pipe';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
* Fixed Deposit Preview Step
*/
@Component({
selector: 'mifosx-fixed-deposit-account-preview-step',
templateUrl: './fixed-deposit-account-preview-step.component.html',
styleUrls: ['./fixed-deposit-account-preview-step.component.scss'],
animations: [
trigger('expandChartSlab', [
state('collapsed', style({ height: '0px', minHeight: '0' })),
state('expanded', style({ height: '*' })),
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
])
],
imports: [
...STANDALONE_SHARED_IMPORTS,
MatDivider,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
FaIconComponent,
NgSwitch,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
MatStepperPrevious,
TitleCasePipe,
CurrencyPipe,
FindPipe,
DateFormatPipe,
FormatNumberPipe,
YesnoPipe
]
})
export class FixedDepositAccountPreviewStepComponent implements OnChanges {
/** Fixed Deposits Account Template */
@Input() fixedDepositsAccountTemplate: any;
/** Fixed Deposits Account Product Template */
@Input() fixedDepositsAccountProductTemplate: any;
/** Fixed Deposits Account Data */
@Input() fixedDepositAccountData: any;
/** Tenure selected from the interest rate chart step (includes pre-calculated maturity if user used calculator) */
@Input() selectedTenure: {
depositPeriod: number;
depositPeriodFrequencyId: number;
depositAmount?: number;
maturityAmount?: number;
interestEarned?: number;
appliedRate?: number;
} | null = null;
/** Charges Displayed Columns */
chargesDisplayedColumns: string[] = [
'name',
'chargeCalculationType',
'amount',
'chargeTimeType',
'date',
'repaymentsEvery'
];
/** Interest Rate Chart Data */
interestRateChartData: any = [];
/** Columns to be displayed in interest rate chart table. */
chartSlabsDisplayedColumns: any[] = [
'period',
'amountRange',
'interest',
'description',
'actions'
];
/** Columns to be displayed in incentives sub-table. */
incentivesDisplayedColumns: string[] = [
'entityType',
'attributeName',
'conditionType',
'attributeValue',
'incentiveType',
'amount'
];
/** Additional Column to disblac incentives table */
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
/** Expand Chart Slab Index used in the view */
expandChartSlabIndex: number;
readonly fdTypeLabels: Record<string, string> = {
SINGLE: 'FD Single (Days < 1 Year)',
DOUBLE: 'FD Double (≥ 1 Year)',
SPECIAL: 'FD Special (Monthly Interest Payout)'
};
getFdTypeLabel(fdType: string): string {
return this.fdTypeLabels[fdType] || fdType;
}
get currencyName(): string {
return this.fixedDepositsAccountProductTemplate?.currency?.name || '—';
}
get decimalPlaces(): string | number {
return this.fixedDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—';
}
get inMultiplesOf(): string | number {
return this.fixedDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
}
/** Form submission event */
@Output() submitEvent = new EventEmitter();
constructor() {}
ngOnChanges() {
if (this.fixedDepositsAccountProductTemplate) {
this.interestRateChartData = this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
}
}
get matchedSlabIndex(): number {
if (!this.interestRateChartData?.length) return -1;
let depositPeriod: number;
let depositPeriodFrequencyId: number;
if (this.selectedTenure) {
depositPeriod = this.selectedTenure.depositPeriod;
depositPeriodFrequencyId = this.selectedTenure.depositPeriodFrequencyId;
} else if (
this.fixedDepositsAccountProductTemplate?.minDepositTerm != null &&
this.fixedDepositsAccountProductTemplate?.minDepositTermType?.id != null
) {
depositPeriod = this.fixedDepositsAccountProductTemplate.minDepositTerm;
depositPeriodFrequencyId = this.fixedDepositsAccountProductTemplate.minDepositTermType.id;
} else {
return -1;
}
// Convert selected tenure to days for comparison
const toDays = (v: number, id: number) => {
if (id === 1) return v * 7; // Weeks
if (id === 2) return v * 30; // Months
if (id === 3) return v * 365; // Years
return v; // Days (id=0)
};
const enteredDays = toDays(depositPeriod, depositPeriodFrequencyId);
return this.interestRateChartData.findIndex((slab: any) => {
const periodTypeValue = (slab.periodType?.value || '').toLowerCase();
const slabId = periodTypeValue.includes('month')
? 2
: periodTypeValue.includes('year')
? 3
: periodTypeValue.includes('week')
? 1
: 0;
const fromDays = toDays(slab.fromPeriod ?? 0, slabId);
const toDaysVal = slab.toPeriod != null ? toDays(slab.toPeriod, slabId) : Infinity;
return enteredDays >= fromDays && enteredDays <= toDaysVal;
});
}
get selectedTenureLabel(): string {
let depositPeriod: number;
let depositPeriodFrequencyId: number;
if (this.selectedTenure) {
depositPeriod = this.selectedTenure.depositPeriod;
depositPeriodFrequencyId = this.selectedTenure.depositPeriodFrequencyId;
} else if (
this.fixedDepositsAccountProductTemplate?.minDepositTerm != null &&
this.fixedDepositsAccountProductTemplate?.minDepositTermType?.id != null
) {
depositPeriod = this.fixedDepositsAccountProductTemplate.minDepositTerm;
depositPeriodFrequencyId = this.fixedDepositsAccountProductTemplate.minDepositTermType.id;
} else {
return '';
}
const unitMap: Record<number, string> = { 0: 'Days', 1: 'Weeks', 2: 'Months', 3: 'Years' };
return `${depositPeriod} ${unitMap[depositPeriodFrequencyId] ?? ''}`;
}
get maturitySummary(): {
depositAmount: number;
maturityAmount: number;
totalInterest: number;
interestRate: number;
tenureLabel: string;
} {
let depositPeriod = 0;
let depositPeriodFrequencyId = 0;
if (this.selectedTenure) {
depositPeriod = this.selectedTenure.depositPeriod;
depositPeriodFrequencyId = this.selectedTenure.depositPeriodFrequencyId;
} else if (
this.fixedDepositsAccountProductTemplate?.minDepositTerm != null &&
this.fixedDepositsAccountProductTemplate?.minDepositTermType?.id != null
) {
depositPeriod = this.fixedDepositsAccountProductTemplate.minDepositTerm;
depositPeriodFrequencyId = this.fixedDepositsAccountProductTemplate.minDepositTermType.id;
}
const depositAmount =
this.selectedTenure?.depositAmount ??
this.fixedDepositAccountData?.depositAmount ??
this.fixedDepositsAccountProductTemplate?.depositAmount ??
0;
// If the chart step already calculated maturity, use those values directly
if (this.selectedTenure?.maturityAmount != null && this.selectedTenure.maturityAmount > 0) {
const interestRate =
this.selectedTenure.appliedRate ?? this.fixedDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? 0;
return {
depositAmount,
maturityAmount: this.selectedTenure.maturityAmount,
totalInterest: this.selectedTenure.interestEarned ?? this.selectedTenure.maturityAmount - depositAmount,
interestRate,
tenureLabel: this.selectedTenureLabel
};
}
// Fallback: recalculate using product template settings
const chartRate =
this.matchedSlabIndex >= 0 ? this.interestRateChartData[this.matchedSlabIndex]?.annualInterestRate : null;
const interestRate = chartRate ?? this.fixedDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? 0;
const r = interestRate / 100;
// Tenure in years (t)
let t = 0;
const daysInYear =
this.fixedDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id === 360 ? 360 : 365;
if (depositPeriodFrequencyId === 0) {
t = depositPeriod / daysInYear;
} else if (depositPeriodFrequencyId === 1) {
t = (depositPeriod * 7) / daysInYear;
} else if (depositPeriodFrequencyId === 2) {
t = depositPeriod / 12;
} else if (depositPeriodFrequencyId === 3) {
t = depositPeriod;
}
const compPeriodValue = (
this.fixedDepositsAccountProductTemplate?.interestCompoundingPeriodType?.value || ''
).toLowerCase();
let n = 1;
if (compPeriodValue.includes('daily')) n = daysInYear;
else if (compPeriodValue.includes('monthly')) n = 12;
else if (compPeriodValue.includes('quarterly')) n = 4;
else if (compPeriodValue.includes('half')) n = 2;
else if (compPeriodValue.includes('biannual')) n = 2;
else if (compPeriodValue.includes('annual')) n = 1;
const calcTypeValue = (
this.fixedDepositsAccountProductTemplate?.interestCalculationType?.value || ''
).toLowerCase();
const isSimple = calcTypeValue.includes('simple');
let maturityAmount = 0;
if (isSimple) {
maturityAmount = depositAmount + depositAmount * r * t;
} else {
maturityAmount = depositAmount * Math.pow(1 + r / n, n * t);
}
const totalInterest = maturityAmount - depositAmount;
return {
depositAmount,
maturityAmount,
totalInterest,
interestRate,
tenureLabel: this.selectedTenureLabel
};
}
}