/** * 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 ); } }