/** * 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, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; import { Currency } from 'app/shared/models/general.model'; import { CurrencyPipe } from '@angular/common'; import { ExternalIdentifierComponent } from '../../../../shared/external-identifier/external-identifier.component'; import { DateFormatPipe } from '../../../../pipes/date-format.pipe'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { InterestScheduleDialogComponent } from '../custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component'; @Component({ selector: 'mifosx-general-tab', templateUrl: './general-tab.component.html', styleUrls: ['./general-tab.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, ExternalIdentifierComponent, FaIconComponent, CurrencyPipe, DateFormatPipe ] }) export class GeneralTabComponent { private route = inject(ActivatedRoute); private router = inject(Router); private dialog = inject(MatDialog); fixedDepositsAccountData: any; entityType: string; currency: Currency; constructor() { this.route.parent.data.subscribe((data: { fixedDepositsAccountData: any }) => { this.fixedDepositsAccountData = data.fixedDepositsAccountData; this.currency = this.fixedDepositsAccountData.currency; }); if (this.router.url.includes('clients')) { this.entityType = 'Client'; } else if (this.router.url.includes('groups')) { this.entityType = 'Group'; } else if (this.router.url.includes('centers')) { this.entityType = 'Center'; } } /** * A reinvested FD always keeps the same client/group/center as the account it came from, so the * counterpart account's view page sits at the same route -- only the account-id segment differs. */ private siblingAccountLink(targetAccountId: number): string | null { const segments = this.router.url.split('?')[0].split('/').filter(Boolean); const idx = segments.indexOf('fixed-deposits-accounts'); if (idx === -1) return null; return '/' + segments.slice(0, idx + 1).join('/') + '/' + targetAccountId + '/general'; } get reinvestedFromLink(): string | null { const id = this.fixedDepositsAccountData?.reinvestedFromAccountId; return id ? this.siblingAccountLink(id) : null; } get reinvestedIntoLink(): string | null { const id = this.fixedDepositsAccountData?.reinvestedIntoAccountId; return id ? this.siblingAccountLink(id) : null; } /** FD Special never compounds -- every month's interest is transferred out to the linked savings * account instead of growing this account's own balance, so its "Maturity Amount" is just the * principal, not principal-plus-interest (see FixedDepositAccount#updateMaturityDateAndAmount). */ get isSpecialFixedDeposit(): boolean { return this.fixedDepositsAccountData?.fdType === 'SPECIAL'; } /** Real, engine-computed period-by-period interest schedule for FD Special (see FixedDepositAccountsApiResource#retrieveOne). */ get projectedInterestSchedule(): Array<{ fromDate: any; toDate: any; interestAmount: number; paid: boolean }> { return this.fixedDepositsAccountData?.projectedInterestSchedule ?? []; } get paidPeriodsCount(): number { return this.projectedInterestSchedule.filter((period) => period.paid).length; } /** Opens the full month-wise schedule in a popup rather than always inline -- a 24+ month term makes * for a long table that would otherwise push every other card on this tab down the page. */ openInterestSchedule(): void { this.dialog.open(InterestScheduleDialogComponent, { width: '680px', data: { schedule: this.projectedInterestSchedule, currencyCode: this.currency?.code } }); } /** Backend-computed sum of the schedule (see FixedDepositAccountsApiResource#retrieveOne) -- the single * source of truth, not a client-side re-sum of the schedule array. */ get totalProjectedInterest(): number | null { return this.fixedDepositsAccountData?.totalProjectedInterest ?? null; } /** * The first NOT-YET-PAID period's real amount -- not just index 0, which would keep showing an * already-posted-and-transferred period's amount as "next" forever once the account has any real * history. Deliberately has NO rate/12 fallback: that would quietly present an inaccurate money figure * as if it were the real one. If the schedule is missing, or every period is already paid, the row is * hidden instead. */ get nextInterestPayout(): number | null { return this.projectedInterestSchedule.find((period) => !period.paid)?.interestAmount ?? null; } /** What the customer ends up with overall across the FD's whole life: the principal returned by the FD * itself at maturity, PLUS every month's interest already paid out to the linked savings account along * the way. Backend-computed (see FixedDepositAccountsApiResource#retrieveOne), same as maturityAmount * itself, rather than derived here from the schedule array. */ get totalValueAtMaturity(): number | null { return this.fixedDepositsAccountData?.totalValueAtMaturity ?? null; } }