Files
cbsnexora/src/app/deposits/recurring-deposits/recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component.ts
T
niteshandClaude Sonnet 5 e58fd3c79a
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
feat(deposits): sync linked account with maturity terms, add deposit certificate printing
Fixed and Recurring Deposit wizards now bidirectionally sync the Product tab's
linked-account selection with the new Terms tab's Transfer-to-Savings account,
disable the Withdraw Deposit maturity instruction end-to-end, and card-style
the Transfer to Savings picker to match the existing linked-account UI. Adds
a printable FD/RD deposit certificate from the account view page, following
the existing letter-mgmt certificate pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 11:05:25 +05:30

294 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, 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 } 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';
/**
* Recurring Deposit Preview Step
*/
@Component({
selector: 'mifosx-recurring-deposits-account-preview-step',
templateUrl: './recurring-deposits-account-preview-step.component.html',
styleUrls: ['./recurring-deposits-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,
FindPipe,
DateFormatPipe,
FormatNumberPipe,
YesnoPipe
]
})
export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
/** Input Data */
@Input() recurringDepositsAccountTemplate: any;
@Input() hideButtons = false;
@Input() recurringDepositsAccountProductTemplate: any;
@Input() recurringDepositAccountData: any;
@Input() rdNominees: any[] = [];
@Input() rdCurrencyFlags: any = null;
@Input() rdTenureDays: number | null = null;
@Input() rdApplicableSlab: any = null;
@Input() rdMaturityAmount: number | null = null;
@Input() rdInterestEarned: number | null = null;
@Input() rdDaysInYear: number | null = null;
@Input() rdAppliedRate: number | null = null;
@Input() rdIsSeniorCitizen = false;
get rdAppliedInterestRate(): number {
if (this.rdAppliedRate != null) return this.rdAppliedRate;
return this.rdApplicableSlab?.annualInterestRate ?? 0;
}
get rdSeniorCitizenBonus(): number {
if (!this.rdIsSeniorCitizen || this.rdAppliedRate == null) return 0;
const normalRate = this.rdApplicableSlab?.annualInterestRate ?? 0;
return this.rdAppliedRate - normalRate;
}
/** Output the submit action */
@Output() submitEvent = new EventEmitter();
get currencyName(): string {
return this.recurringDepositsAccountProductTemplate?.currency?.name || '—';
}
get decimalPlaces(): string | number {
return this.recurringDepositsAccountProductTemplate?.currency?.decimalPlaces ?? '—';
}
get inMultiplesOf(): string | number {
return this.recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
}
readonly rdSlabColumns = [
'srNo',
'description',
'fromPeriod',
'toPeriod',
'periodTypeLabel',
'annualInterestRate'
];
/** Charges Displayed Columns */
chargesDisplayedColumns: string[] = [
'name',
'chargeCalculationType',
'amount',
'chargeTimeType',
'date',
'repaymentsEvery'
];
get interestRateChartData(): any[] {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
if (!template) return [];
return (
template.accountChart?.chartSlabs ??
template.activeChart?.chartSlabs ??
template.interestRateCharts?.[0]?.chartSlabs ??
template.interestRateChart?.chartSlabs ??
template.chartSlabs ??
[]
);
}
/** Matched Slab Index for highlighting */
get matchedSlabIndex(): number {
if (!this.rdTenureDays || !this.interestRateChartData) return -1;
// Find index of the applicable slab in the chart data
return this.interestRateChartData.findIndex((slab) => {
const pTypeValue = slab.periodType?.value || 'Days';
const fromDays = this.slabPeriodToDays(slab.fromPeriod ?? 0, pTypeValue);
const toDays = slab.toPeriod != null ? this.slabPeriodToDays(slab.toPeriod, pTypeValue) : Infinity;
return this.rdTenureDays >= fromDays && this.rdTenureDays <= toDays;
});
}
/** Selected Tenure Label for banner */
get selectedTenureLabel(): string {
if (!this.rdTenureDays) return '';
if (this.rdTenureDays >= 365) {
const years = Math.floor(this.rdTenureDays / 365);
const rem = this.rdTenureDays % 365;
return years + (years === 1 ? ' Year' : ' Years') + (rem > 0 ? ` ${rem} Days` : '');
}
if (this.rdTenureDays >= 30) {
const months = Math.floor(this.rdTenureDays / 30);
const rem = this.rdTenureDays % 30;
return months + (months === 1 ? ' Month' : ' Months') + (rem > 0 ? ` ${rem} Days` : '');
}
return this.rdTenureDays + ' Days';
}
private slabPeriodToDays(period: number, periodTypeValue: string): number {
const p = (periodTypeValue || '').toLowerCase();
if (p.includes('day')) return period;
if (p.includes('week')) return period * 7;
if (p.includes('month')) return period * 30;
if (p.includes('year')) return period * 365;
return period;
}
/** 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;
/** Maturity/pre-mature-closure choices made in the Terms step -- shown here read-only. */
@Input() closureSettings: any = {};
get closureMaturityInstructionLabel(): string {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
const options = template?.maturityInstructionOptions ?? [];
const match = options.find((o: any) => o.id === this.closureSettings?.maturityInstructionId);
return match?.value ?? '—';
}
get closureTransferToSavingsLabel(): string {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
const accounts = template?.savingsAccounts ?? [];
const match = accounts.find((a: any) => a.id === this.closureSettings?.transferToSavingsId);
return match ? `(${match.accountNo}) ${match.savingsProductName}` : '—';
}
get closurePenalInterestOnTypeLabel(): string {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
const options = template?.preClosurePenalInterestOnTypeOptions ?? [];
const match = options.find((o: any) => o.id === this.closureSettings?.preClosurePenalInterestOnTypeId);
return match?.value ?? '—';
}
ngOnChanges() {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
if (template) {
// Logic for metadata getters is now handled by the dynamic template reference
}
}
// ── Header Getters ──────────────────────────────────────────────────────
get chartName(): string {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
return (
template?.accountChart?.name ||
template?.activeChart?.name ||
template?.interestRateCharts?.[0]?.name ||
template?.interestRateChart?.name ||
template?.name ||
''
);
}
get chartFromDate(): any {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
return (
template?.accountChart?.fromDate ||
template?.activeChart?.fromDate ||
template?.interestRateCharts?.[0]?.fromDate ||
template?.interestRateChart?.fromDate ||
template?.fromDate
);
}
get chartEndDate(): any {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
return (
template?.accountChart?.endDate ||
template?.activeChart?.endDate ||
template?.interestRateCharts?.[0]?.endDate ||
template?.interestRateChart?.endDate ||
template?.endDate
);
}
get chartDescription(): string {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
return (
template?.accountChart?.description ||
template?.activeChart?.description ||
template?.interestRateCharts?.[0]?.description ||
template?.interestRateChart?.description ||
template?.description ||
''
);
}
get isPrimaryGroupingByAmount(): boolean {
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
return !!(
template?.accountChart?.isPrimaryGroupingByAmount ||
template?.activeChart?.isPrimaryGroupingByAmount ||
template?.interestRateCharts?.[0]?.isPrimaryGroupingByAmount ||
template?.interestRateChart?.isPrimaryGroupingByAmount ||
template?.isPrimaryGroupingByAmount
);
}
}