574 lines
21 KiB
TypeScript
574 lines
21 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/.
|
||
*/
|
||
|
||
import {
|
||
Component,
|
||
Input,
|
||
OnChanges,
|
||
SimpleChanges,
|
||
Output,
|
||
EventEmitter,
|
||
ViewChild,
|
||
ChangeDetectorRef,
|
||
inject
|
||
} from '@angular/core';
|
||
import {
|
||
MatTableDataSource,
|
||
MatTable,
|
||
MatColumnDef,
|
||
MatHeaderCellDef,
|
||
MatHeaderCell,
|
||
MatCellDef,
|
||
MatCell,
|
||
MatHeaderRowDef,
|
||
MatHeaderRow,
|
||
MatRowDef,
|
||
MatRow
|
||
} from '@angular/material/table';
|
||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||
import { NgIf, NgSwitch, TitleCasePipe, DecimalPipe } from '@angular/common';
|
||
import { FormsModule } from '@angular/forms';
|
||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||
import { FixedDepositsService } from '../../fixed-deposits.service';
|
||
import { ClientsService } from 'app/clients/clients.service';
|
||
import { SettingsService } from 'app/settings/settings.service';
|
||
import { SENIOR_CITIZEN_AGE_THRESHOLD, deriveSeniorCitizenStatus } from 'app/clients/shared/client-age.util';
|
||
|
||
export type FdPeriodUnit = 'Days' | 'Months' | 'Years';
|
||
|
||
export interface SlabMatchResult {
|
||
slab: any;
|
||
slabIndex: number;
|
||
appliedRate: number;
|
||
estimatedMonthlyPayout?: number;
|
||
maturityAmount?: number;
|
||
interestEarned?: number;
|
||
tenureDays?: number;
|
||
}
|
||
|
||
@Component({
|
||
selector: 'mifosx-fixed-deposit-account-interest-rate-chart-step',
|
||
templateUrl: './fixed-deposit-account-interest-rate-chart-step.component.html',
|
||
styleUrls: ['./fixed-deposit-account-interest-rate-chart-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,
|
||
FormsModule,
|
||
MatTable,
|
||
MatColumnDef,
|
||
MatHeaderCellDef,
|
||
MatHeaderCell,
|
||
MatCellDef,
|
||
MatCell,
|
||
NgSwitch,
|
||
MatHeaderRowDef,
|
||
MatHeaderRow,
|
||
MatRowDef,
|
||
MatRow,
|
||
TitleCasePipe,
|
||
DateFormatPipe,
|
||
DecimalPipe
|
||
]
|
||
})
|
||
export class FixedDepositAccountInterestRateChartStepComponent implements OnChanges {
|
||
private fixedDepositsService = inject(FixedDepositsService);
|
||
private clientsService = inject(ClientsService);
|
||
private settingsService = inject(SettingsService);
|
||
private cdr = inject(ChangeDetectorRef);
|
||
|
||
@Input() fixedDepositsAccountTemplate: any;
|
||
@Input() fixedDepositsAccountProductTemplate: any;
|
||
/** Active CBS base interest rate versions — when provided, overrides the product chart slabs */
|
||
@Input() cbsBaseRates: any[] = [];
|
||
/** Pre-populate tenure from existing account (edit mode) */
|
||
@Input() initialTenure: {
|
||
depositPeriod: number;
|
||
depositPeriodFrequencyId: number;
|
||
depositAmount?: number;
|
||
isSeniorCitizen?: boolean;
|
||
appliedRate?: number;
|
||
} | null = null;
|
||
/** Submission date for exact calculation (passed from create/edit form) */
|
||
@Input() submittedOnDate: Date | null = null;
|
||
|
||
activeBase: any = null;
|
||
|
||
@Output() tenureSelected = new EventEmitter<{
|
||
depositPeriod: number;
|
||
depositPeriodFrequencyId: number;
|
||
depositAmount?: number;
|
||
maturityAmount?: number;
|
||
interestEarned?: number;
|
||
appliedRate?: number;
|
||
isSeniorCitizen?: boolean;
|
||
}>();
|
||
|
||
interestRateChartData: any[] = [];
|
||
|
||
chartSlabsDisplayedColumns: string[] = [
|
||
'period',
|
||
'amountRange',
|
||
'interest',
|
||
'seniorCitizenRate',
|
||
'description',
|
||
'actions'
|
||
];
|
||
|
||
/**
|
||
* Derived from the client's own date of birth -- never staff-editable. Setting this by hand is what
|
||
* let a 24-year-old's FD get priced at the senior-citizen rate, so the checkbox is display-only and
|
||
* this value comes solely from {@link deriveSeniorCitizenStatus}.
|
||
*/
|
||
isSeniorCitizen = false;
|
||
readonly seniorCitizenAgeThreshold = SENIOR_CITIZEN_AGE_THRESHOLD;
|
||
/** Client age in completed years, or null when the DOB is unknown / the client is an entity. */
|
||
clientAge: number | null = null;
|
||
/** False for a company/entity client, which has no age and so can never qualify. */
|
||
clientIsIndividual = true;
|
||
/** True once the client lookup has come back, so the UI can explain the locked checkbox honestly. */
|
||
clientAgeResolved = false;
|
||
incentivesDisplayedColumns: string[] = [
|
||
'entityType',
|
||
'attributeName',
|
||
'conditionType',
|
||
'attributeValue',
|
||
'incentiveType',
|
||
'amount'
|
||
];
|
||
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
||
|
||
expandChartSlabIndex: number | null = null;
|
||
|
||
@ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable<Element>;
|
||
|
||
// ── Tenure Calculator ───────────────────────────────────────────────────
|
||
tenureValue: number | null = null;
|
||
tenurePeriodUnit: FdPeriodUnit = 'Days';
|
||
principalAmount: number | null = null;
|
||
|
||
get showPrincipalInput(): boolean {
|
||
return true; // shown for all FD types
|
||
}
|
||
|
||
matchResult: SlabMatchResult | null = null;
|
||
validationWarning: string | null = null;
|
||
|
||
// ── FD Type labels ──────────────────────────────────────────────────────
|
||
readonly fdTypeLabels: Record<string, string> = {
|
||
SINGLE: 'FD Single (Days < 1 Year)',
|
||
DOUBLE: 'FD Double (≥ 1 Year)',
|
||
SPECIAL: 'FD Special (Monthly Interest Payout)'
|
||
};
|
||
|
||
get fdType(): string {
|
||
return this.fixedDepositsAccountProductTemplate?.fdType || '';
|
||
}
|
||
|
||
get fdTypeLabel(): string {
|
||
return this.fdTypeLabels[this.fdType] || this.fdType;
|
||
}
|
||
|
||
/** Period unit options driven by fdType */
|
||
get allowedPeriodUnits(): FdPeriodUnit[] {
|
||
if (this.fdType === 'SINGLE') return ['Days'];
|
||
if (this.fdType === 'DOUBLE') return [
|
||
'Months',
|
||
'Years'
|
||
];
|
||
return [
|
||
'Days',
|
||
'Months',
|
||
'Years'
|
||
];
|
||
}
|
||
|
||
get isSpecial(): boolean {
|
||
return this.fdType === 'SPECIAL';
|
||
}
|
||
|
||
ngOnChanges(changes: SimpleChanges) {
|
||
if (changes['fixedDepositsAccountTemplate']) {
|
||
this.resolveSeniorCitizenFromClient();
|
||
}
|
||
// Only reset/re-apply when TEMPLATE or cbsBaseRates changes, NOT when initialTenure/submittedOnDate changes alone
|
||
const templateChanged = changes['fixedDepositsAccountProductTemplate'] || changes['cbsBaseRates'];
|
||
if (templateChanged && this.fixedDepositsAccountProductTemplate) {
|
||
const fdType = this.fixedDepositsAccountProductTemplate?.fdType;
|
||
this.activeBase = (this.cbsBaseRates || []).find((r) => r.fdType === fdType && r.isActive);
|
||
this.interestRateChartData = this.activeBase?.slabs
|
||
? this.normalizeCbsSlabs(this.activeBase.slabs)
|
||
: (this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? []);
|
||
if (this.chartsTableRef) {
|
||
this.chartsTableRef.renderRows();
|
||
}
|
||
this.resetCalculator();
|
||
this.tenurePeriodUnit = this.allowedPeriodUnits[0];
|
||
// Pre-fill tenure in edit mode
|
||
this.applyInitialTenure();
|
||
}
|
||
}
|
||
|
||
private applyInitialTenure(): void {
|
||
if (!this.initialTenure?.depositPeriod) {
|
||
return;
|
||
}
|
||
// Map frequency ID to period unit: Days=0, Weeks=1, Months=2, Years=3
|
||
const freqId = this.initialTenure.depositPeriodFrequencyId;
|
||
const unitMap: Record<number, FdPeriodUnit> = { 0: 'Days', 1: 'Days', 2: 'Months', 3: 'Years' };
|
||
const unit: FdPeriodUnit = unitMap[freqId] ?? 'Months';
|
||
if (this.allowedPeriodUnits.includes(unit)) {
|
||
this.tenurePeriodUnit = unit;
|
||
} else {
|
||
this.tenurePeriodUnit = this.allowedPeriodUnits[0];
|
||
}
|
||
this.tenureValue = this.initialTenure.depositPeriod;
|
||
if (this.initialTenure.depositAmount) {
|
||
this.principalAmount = this.initialTenure.depositAmount;
|
||
}
|
||
// Deliberately NOT restoring initialTenure.isSeniorCitizen: the flag is derived from the client's
|
||
// date of birth in resolveSeniorCitizenFromClient(). Re-applying a stored value here would carry a
|
||
// previously mis-set flag forward on every edit, which is exactly how a 24-year-old's FD kept its
|
||
// senior-citizen rate.
|
||
// Auto-calculate when tenure is pre-filled (edit mode)
|
||
setTimeout(() => this.calculateRate(), 0);
|
||
}
|
||
|
||
private normalizeCbsSlabs(slabs: any[]): any[] {
|
||
return (slabs || []).map((s) => ({
|
||
...s,
|
||
periodType: { id: s.periodTypeEnum, value: s.periodTypeLabel }
|
||
}));
|
||
}
|
||
|
||
// Convert entered tenure to Days for slab matching
|
||
private toDays(value: number, unit: FdPeriodUnit): number {
|
||
if (unit === 'Days') return value;
|
||
if (unit === 'Months') return value * 30;
|
||
return value * 365;
|
||
}
|
||
|
||
// Convert slab period (which is stored in its own periodType) to days
|
||
private slabPeriodToDays(period: number, periodTypeValue: string): number {
|
||
const p = (periodTypeValue || '').toLowerCase();
|
||
if (p.includes('month')) return period * 30;
|
||
if (p.includes('year')) return period * 365;
|
||
return period; // days
|
||
}
|
||
|
||
calculateRate() {
|
||
this.validationWarning = null;
|
||
this.matchResult = null;
|
||
|
||
if (!this.tenureValue || this.tenureValue <= 0) {
|
||
this.validationWarning = 'Please enter a valid tenure value greater than 0.';
|
||
return;
|
||
}
|
||
|
||
// FD Type specific validation
|
||
const enteredDays = this.toDays(this.tenureValue, this.tenurePeriodUnit);
|
||
|
||
if (this.fdType === 'SINGLE' && enteredDays >= 365) {
|
||
this.validationWarning =
|
||
'FD Single allows tenure less than 1 year (max 364 days). Please enter a shorter tenure.';
|
||
return;
|
||
}
|
||
|
||
if (this.fdType === 'DOUBLE' && enteredDays < 365) {
|
||
this.validationWarning =
|
||
'FD Double requires tenure of at least 1 year (minimum 12 months). Please enter a longer tenure.';
|
||
return;
|
||
}
|
||
|
||
const slabs: any[] = this.interestRateChartData || [];
|
||
if (!slabs.length) {
|
||
this.validationWarning = 'No interest rate slabs are defined for this product.';
|
||
return;
|
||
}
|
||
|
||
let matchedSlab: any = null;
|
||
let matchedIndex = -1;
|
||
|
||
for (let i = 0; i < slabs.length; i++) {
|
||
const slab = slabs[i];
|
||
const periodTypeValue = slab.periodType?.value || 'Days';
|
||
const fromDays = this.slabPeriodToDays(slab.fromPeriod ?? 0, periodTypeValue);
|
||
const toDays = slab.toPeriod != null ? this.slabPeriodToDays(slab.toPeriod, periodTypeValue) : Infinity;
|
||
|
||
if (enteredDays >= fromDays && enteredDays <= toDays) {
|
||
matchedSlab = slab;
|
||
matchedIndex = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!matchedSlab) {
|
||
this.validationWarning =
|
||
'No applicable interest rate slab found for the entered tenure. Please check the rate chart below.';
|
||
return;
|
||
}
|
||
|
||
const rate =
|
||
this.isSeniorCitizen && (matchedSlab.seniorCitizenRate ?? 0) > 0
|
||
? (matchedSlab.seniorCitizenRate ?? 0)
|
||
: (matchedSlab.annualInterestRate ?? 0);
|
||
const result: SlabMatchResult = {
|
||
slab: matchedSlab,
|
||
slabIndex: matchedIndex,
|
||
appliedRate: rate,
|
||
tenureDays: enteredDays
|
||
};
|
||
|
||
this.matchResult = result; // Show slab match immediately, maturity updates async if principal entered
|
||
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
||
|
||
if (this.principalAmount && this.principalAmount > 0) {
|
||
// Years and Months convert exactly; a Days tenure is sent as days and never converted.
|
||
let tenureInMonths: number | undefined;
|
||
let tenureInDays: number | undefined;
|
||
if (this.tenurePeriodUnit === 'Years') {
|
||
tenureInMonths = this.tenureValue * 12;
|
||
} else if (this.tenurePeriodUnit === 'Months') {
|
||
tenureInMonths = this.tenureValue;
|
||
} else {
|
||
// Sent as days, whatever the count. Converting to whole months silently dropped the
|
||
// remainder — a 200-day deposit quoted as six months lost 20 days of interest, and the
|
||
// screen still said "200 Days" above the figure. The backend takes days directly and
|
||
// works the calendar out itself.
|
||
tenureInDays = enteredDays;
|
||
}
|
||
|
||
// Map compounding period type id → months (0=Daily, 1=Daily, 4=Monthly, 5=Quarterly, 6=SemiAnnual, 7=Annual)
|
||
const compMap: Record<number, number> = { 1: 0, 4: 1, 5: 3, 6: 6, 7: 12 };
|
||
const compId = this.fixedDepositsAccountProductTemplate?.interestCompoundingPeriodType?.id ?? 4;
|
||
const compMonths = compMap[compId] ?? 1;
|
||
|
||
// Map posting period type id → months
|
||
const postId = this.fixedDepositsAccountProductTemplate?.interestPostingPeriodType?.id ?? 4;
|
||
const postMonths = compMap[postId] ?? 1;
|
||
|
||
const emitResult = () => {
|
||
if (frequencyId !== null) {
|
||
this.tenureSelected.emit({
|
||
depositPeriod: this.tenureValue,
|
||
depositPeriodFrequencyId: frequencyId,
|
||
depositAmount: this.principalAmount || undefined,
|
||
maturityAmount: result.maturityAmount,
|
||
interestEarned: result.interestEarned,
|
||
appliedRate: result.appliedRate,
|
||
isSeniorCitizen: this.isSeniorCitizen
|
||
});
|
||
}
|
||
};
|
||
|
||
// Get submittedOnDate — Input takes priority over template data
|
||
let submittedOnDateStr: string;
|
||
if (this.submittedOnDate) {
|
||
submittedOnDateStr = this.submittedOnDate.toISOString().split('T')[0];
|
||
} else {
|
||
const submittedOnDateRaw =
|
||
this.fixedDepositsAccountTemplate?.timeline?.submittedOnDate ??
|
||
this.fixedDepositsAccountProductTemplate?.timeline?.submittedOnDate;
|
||
if (submittedOnDateRaw) {
|
||
const d = Array.isArray(submittedOnDateRaw)
|
||
? new Date(submittedOnDateRaw[0], submittedOnDateRaw[1] - 1, submittedOnDateRaw[2])
|
||
: new Date(submittedOnDateRaw);
|
||
submittedOnDateStr = !isNaN(d.getTime())
|
||
? d.toISOString().split('T')[0]
|
||
: new Date().toISOString().split('T')[0];
|
||
} else {
|
||
submittedOnDateStr = new Date().toISOString().split('T')[0];
|
||
}
|
||
}
|
||
const daysInYearVal =
|
||
this.fixedDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id === 360 ? 360 : 365;
|
||
|
||
// Always use backend API for accurate calculation (including daily compounding with 0)
|
||
this.fixedDepositsService
|
||
.calculateFixedDepositInterest({
|
||
principalAmount: this.principalAmount,
|
||
annualInterestRate: rate,
|
||
tenureInMonths,
|
||
tenureInDays,
|
||
interestCompoundingPeriodInMonths: compMonths, // 0 = daily, now supported by backend
|
||
interestPostingPeriodInMonths: postMonths,
|
||
submittedOnDate: submittedOnDateStr,
|
||
daysInYear: daysInYearVal
|
||
})
|
||
.subscribe({
|
||
next: (apiResult: any) => {
|
||
result.maturityAmount = apiResult.maturityAmount;
|
||
result.interestEarned =
|
||
apiResult.totalInterest ?? Math.round((apiResult.maturityAmount - this.principalAmount) * 100) / 100;
|
||
this.matchResult = { ...result };
|
||
emitResult();
|
||
},
|
||
error: () => {
|
||
// Fallback if API fails: frontend formula
|
||
const r = rate / 100;
|
||
if (compMonths === 0) {
|
||
// Daily compounding fallback
|
||
result.maturityAmount =
|
||
Math.round(this.principalAmount * Math.pow(1 + r / daysInYearVal, enteredDays) * 100) / 100;
|
||
} else {
|
||
const n = 12 / compMonths;
|
||
const effectiveMonths = tenureInMonths ?? enteredDays / 30;
|
||
const rPerCompounding = rate / (n * 100);
|
||
// Whole compounding periods, then simple interest on the days left over. Flooring the
|
||
// periods and stopping there dropped the remainder entirely: a 200-day deposit
|
||
// compounding monthly quoted only six months of interest and lost the other twenty
|
||
// days, which is the depositor's money.
|
||
const wholePeriods = Math.floor(effectiveMonths / compMonths);
|
||
const compounded = this.principalAmount * Math.pow(1 + rPerCompounding, wholePeriods);
|
||
const leftoverDays = enteredDays - wholePeriods * compMonths * 30;
|
||
const leftoverInterest =
|
||
leftoverDays > 0 ? (compounded * rate * leftoverDays) / (100 * daysInYearVal) : 0;
|
||
result.maturityAmount = Math.round((compounded + leftoverInterest) * 100) / 100;
|
||
}
|
||
result.interestEarned = Math.round((result.maturityAmount - this.principalAmount) * 100) / 100;
|
||
this.matchResult = { ...result };
|
||
emitResult();
|
||
}
|
||
});
|
||
} else {
|
||
this.matchResult = result;
|
||
if (frequencyId !== null) {
|
||
this.tenureSelected.emit({
|
||
depositPeriod: this.tenureValue,
|
||
depositPeriodFrequencyId: frequencyId,
|
||
depositAmount: undefined,
|
||
maturityAmount: undefined,
|
||
interestEarned: undefined,
|
||
appliedRate: result.appliedRate,
|
||
isSeniorCitizen: this.isSeniorCitizen
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
private resolveFrequencyId(unit: FdPeriodUnit): number | null {
|
||
const options: any[] = this.fixedDepositsAccountProductTemplate?.periodFrequencyTypeOptions ?? [];
|
||
const unitLower = unit.toLowerCase();
|
||
const match = options.find((o: any) => (o.value || '').toLowerCase().includes(unitLower));
|
||
if (match) return match.id;
|
||
// Fineract standard fallback: Days=0, Months=2, Years=3
|
||
const fallback: Record<FdPeriodUnit, number> = { Days: 0, Months: 2, Years: 3 };
|
||
return fallback[unit] ?? null;
|
||
}
|
||
|
||
resetCalculator() {
|
||
this.tenureValue = null;
|
||
this.principalAmount = null;
|
||
this.matchResult = null;
|
||
this.validationWarning = null;
|
||
this.tenurePeriodUnit = this.allowedPeriodUnits[0];
|
||
}
|
||
|
||
onPeriodUnitChange() {
|
||
this.matchResult = null;
|
||
this.validationWarning = null;
|
||
}
|
||
|
||
/**
|
||
* Derives the senior-citizen flag from the client's date of birth. Runs whenever the account template
|
||
* arrives (create and edit alike), so the flag always reflects the customer on record rather than
|
||
* whatever was ticked -- or previously saved -- by hand.
|
||
*/
|
||
private resolveSeniorCitizenFromClient(): void {
|
||
const clientId = this.fixedDepositsAccountTemplate?.clientId ?? this.fixedDepositsAccountTemplate?.id;
|
||
if (!clientId) {
|
||
return;
|
||
}
|
||
this.clientsService.getClientData(String(clientId)).subscribe((client: any) => {
|
||
const status = deriveSeniorCitizenStatus(client, this.settingsService.businessDate);
|
||
this.clientAge = status.age;
|
||
this.clientIsIndividual = status.isIndividual;
|
||
this.isSeniorCitizen = status.isSeniorCitizen;
|
||
this.clientAgeResolved = true;
|
||
// Re-price against the now-known status if a tenure was already entered.
|
||
this.onSeniorCitizenChange();
|
||
this.cdr.detectChanges();
|
||
});
|
||
}
|
||
|
||
/** Explains the locked checkbox to staff, so a missing benefit never looks like a system fault. */
|
||
get seniorCitizenHint(): string {
|
||
if (!this.clientAgeResolved) {
|
||
return 'Determined from the client’s date of birth';
|
||
}
|
||
if (!this.clientIsIndividual) {
|
||
return 'Not applicable to a non-individual client';
|
||
}
|
||
if (this.clientAge === null) {
|
||
return 'Client has no date of birth on record';
|
||
}
|
||
if (this.isSeniorCitizen) {
|
||
return `Client is ${this.clientAge} years old — higher rate applied`;
|
||
}
|
||
return `Client is ${this.clientAge} — applies from age ${this.seniorCitizenAgeThreshold}`;
|
||
}
|
||
|
||
onSeniorCitizenChange() {
|
||
if (this.tenureValue && this.tenureValue > 0) {
|
||
this.calculateRate();
|
||
} else {
|
||
this.matchResult = null;
|
||
}
|
||
}
|
||
|
||
isMatchedSlab(index: number): boolean {
|
||
return this.matchResult?.slabIndex === index;
|
||
}
|
||
|
||
get chartName(): string {
|
||
if (this.activeBase) {
|
||
return this.activeBase.versionLabel || 'CBS Base Interest Rate';
|
||
}
|
||
return this.fixedDepositsAccountProductTemplate?.accountChart?.name || 'CBS Base Interest Rate';
|
||
}
|
||
|
||
get chartFromDate(): any {
|
||
if (this.activeBase) {
|
||
return this.activeBase.effectiveFromDate;
|
||
}
|
||
return this.fixedDepositsAccountProductTemplate?.accountChart?.fromDate;
|
||
}
|
||
|
||
get chartEndDate(): any {
|
||
if (this.activeBase) {
|
||
return null;
|
||
}
|
||
return this.fixedDepositsAccountProductTemplate?.accountChart?.endDate;
|
||
}
|
||
|
||
get chartDescription(): string {
|
||
if (this.activeBase) {
|
||
return this.activeBase.remarks || '';
|
||
}
|
||
return this.fixedDepositsAccountProductTemplate?.accountChart?.description || '';
|
||
}
|
||
|
||
get isActive(): boolean {
|
||
if (this.activeBase) {
|
||
return this.activeBase.isActive;
|
||
}
|
||
const template = this.fixedDepositsAccountProductTemplate || this.fixedDepositsAccountTemplate;
|
||
return template?.accountChart?.isActive ?? true;
|
||
}
|
||
|
||
get isPrimaryGroupingByAmount(): boolean {
|
||
return !!this.fixedDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount;
|
||
}
|
||
}
|