/** * 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, OnDestroy, inject, ElementRef, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { UntypedFormBuilder, Validators } from '@angular/forms'; import { CurrencyPipe } from '@angular/common'; import { Subject, takeUntil } from 'rxjs'; /** Custom Services */ import { SettingsService } from 'app/settings/settings.service'; import { Dates } from 'app/core/utils/dates'; import { Currency } from 'app/shared/models/general.model'; import { SavingsAccountTransaction, SavingsAccountTransactionType } from 'app/savings/models/savings-account-transaction.model'; import { MatIcon } from '@angular/material/icon'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { DateFormatPipe } from '../../../pipes/date-format.pipe'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ selector: 'mifosx-passbook-tab', templateUrl: './passbook-tab.component.html', styleUrls: ['./passbook-tab.component.scss'], standalone: true, imports: [ ...STANDALONE_SHARED_IMPORTS, MatIcon, FaIconComponent, DateFormatPipe, CurrencyPipe ] }) export class PassbookTabComponent implements OnInit, OnDestroy { private route = inject(ActivatedRoute); private formBuilder = inject(UntypedFormBuilder); private settingsService = inject(SettingsService); private dateUtils = inject(Dates); private destroy$ = new Subject(); @ViewChild('passbookContent') passbookContent: ElementRef; /** Account data */ accountNo: string = ''; clientName: string = ''; productName: string = ''; currency: Currency | null = null; status: string = ''; interestRate: number = 0; accountBalance: number = 0; availableBalance: number = 0; activatedOnDate: any = null; /** Transactions */ allTransactions: SavingsAccountTransaction[] = []; filteredTransactions: SavingsAccountTransaction[] = []; /** Date range form */ minDate = new Date(2000, 0, 1); maxDate = new Date(); dateRangeForm: any; isFiltered = false; ngOnInit(): void { this.maxDate = this.settingsService.businessDate; this.route.parent.data.pipe(takeUntil(this.destroy$)).subscribe((data: { savingsAccountData: any }) => { const account = data.savingsAccountData; this.accountNo = account.accountNo || ''; this.clientName = account.clientName || account.groupName || ''; this.productName = account.savingsProductName || ''; this.currency = account.currency || null; this.status = account.status?.value || ''; this.interestRate = account.nominalAnnualInterestRate || 0; this.accountBalance = account.summary?.accountBalance || 0; this.availableBalance = account.summary?.availableBalance || 0; this.activatedOnDate = account.timeline?.activatedOnDate || null; this.allTransactions = this.recalculateRunningBalances(this.sortAscending(account.transactions || [])); this.filteredTransactions = [...this.allTransactions]; }); this.dateRangeForm = this.formBuilder.group({ fromDate: [ '', Validators.required ], toDate: [ '', Validators.required ] }); } isDebit(transactionType: SavingsAccountTransactionType): boolean { return ( transactionType.withdrawal === true || transactionType.feeDeduction === true || transactionType.overdraftInterest === true || transactionType.withholdTax === true ); } filterTransactions(): void { const fromDate = this.dateRangeForm.value.fromDate; const toDate = this.dateRangeForm.value.toDate; if (!fromDate || !toDate) return; const from = new Date(fromDate).getTime(); const to = new Date(toDate).getTime(); this.filteredTransactions = this.allTransactions.filter((txn: SavingsAccountTransaction) => { const txnDate = this.getTransactionDateAsTime(txn.date); return txnDate >= from && txnDate <= to; }); this.isFiltered = true; } clearFilter(): void { this.dateRangeForm.reset(); this.filteredTransactions = [...this.allTransactions]; this.isFiltered = false; } private getTransactionDateAsTime(date: any): number { if (Array.isArray(date) && date.length >= 3) { return new Date(date[0], date[1] - 1, date[2]).getTime(); } return new Date(date).getTime(); } printPassbook(): void { const content = this.passbookContent?.nativeElement?.innerHTML; if (!content) return; const printWindow = window.open('', 'Passbook', 'height=700,width=900'); printWindow.document.write(` Savings Account Passbook - ${this.accountNo} ${content} `); printWindow.document.close(); printWindow.focus(); printWindow.print(); printWindow.close(); } private recalculateRunningBalances(transactions: SavingsAccountTransaction[]): SavingsAccountTransaction[] { let balance = 0; return transactions.map((txn) => { // Accruals and on-hold entries don't affect actual account balance const affectsBalance = !txn.transactionType.accrual && !txn.transactionType.amountHold && !txn.transactionType.amountRelease; if (affectsBalance) { if (this.isDebit(txn.transactionType)) { balance -= txn.amount; } else { balance += txn.amount; } } return { ...txn, runningBalance: parseFloat(balance.toFixed(2)) }; }); } private sortAscending(transactions: SavingsAccountTransaction[]): SavingsAccountTransaction[] { return [...transactions].sort((a, b) => { const dateA = this.getTransactionDateAsTime(a.date); const dateB = this.getTransactionDateAsTime(b.date); if (dateA !== dateB) return dateA - dateB; return (a.id as number) - (b.id as number); }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); } }