200 lines
7.3 KiB
TypeScript
200 lines
7.3 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, OnInit, OnDestroy, inject, ElementRef, ViewChild } from '@angular/core';
|
|
import { ActivatedRoute } from '@angular/router';
|
|
import { UntypedFormBuilder } 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 {
|
|
CurrentAccountTransaction,
|
|
CurrentAccountTransactionType
|
|
} from 'app/current-accounts/models/current-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<void>();
|
|
|
|
@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: CurrentAccountTransaction[] = [];
|
|
filteredTransactions: CurrentAccountTransaction[] = [];
|
|
|
|
/** 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: { currentAccountData: any }) => {
|
|
const account = data.currentAccountData;
|
|
this.accountNo = account.accountNo || '';
|
|
this.clientName = account.clientName || account.groupName || '';
|
|
this.productName = account.currentAccountProductName || '';
|
|
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.computeRunningBalances(account.transactions || []);
|
|
this.filteredTransactions = [...this.allTransactions];
|
|
});
|
|
|
|
this.dateRangeForm = this.formBuilder.group({
|
|
fromDate: [''],
|
|
toDate: ['']
|
|
});
|
|
}
|
|
|
|
private computeRunningBalances(transactions: CurrentAccountTransaction[]): CurrentAccountTransaction[] {
|
|
let balance = 0;
|
|
return transactions.map((txn) => {
|
|
if (txn.runningBalance != null) {
|
|
balance = txn.runningBalance;
|
|
} else {
|
|
const amount = txn.amount || 0;
|
|
if (this.isDebit(txn.transactionType)) {
|
|
balance = balance - amount;
|
|
} else {
|
|
balance = balance + amount;
|
|
}
|
|
txn = { ...txn, runningBalance: balance };
|
|
}
|
|
return txn;
|
|
});
|
|
}
|
|
|
|
isDebit(transactionType: CurrentAccountTransactionType): 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;
|
|
|
|
this.filteredTransactions = this.allTransactions.filter((txn: CurrentAccountTransaction) => {
|
|
const txnDate = this.getTransactionDateAsTime(txn.date);
|
|
if (fromDate && new Date(fromDate).getTime() > txnDate) return false;
|
|
if (toDate) {
|
|
const toEnd = new Date(toDate);
|
|
toEnd.setHours(23, 59, 59, 999);
|
|
if (toEnd.getTime() < txnDate) return false;
|
|
}
|
|
return true;
|
|
});
|
|
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(`
|
|
<html>
|
|
<head>
|
|
<title>Current Account Passbook - ${this.accountNo}</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: 'Segoe UI', Arial, sans-serif; padding: 20px; color: #333; }
|
|
.passbook-header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #1976d2; padding-bottom: 12px; }
|
|
.passbook-header h2 { color: #1976d2; font-size: 18px; margin-bottom: 4px; }
|
|
.passbook-header h3 { font-size: 14px; font-weight: 400; color: #666; }
|
|
.passbook-info-grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px 16px; margin-bottom: 16px; padding: 10px; background: #f5f5f5; border-radius: 4px; }
|
|
.passbook-info-item { font-size: 12px; }
|
|
.passbook-info-item .label { font-weight: 600; color: #666; text-transform: uppercase; font-size: 10px; }
|
|
.passbook-info-item .value { color: #333; }
|
|
.passbook-balance-row { display: flex; justify-content: space-between; margin-bottom: 16px; padding: 8px 10px; background: #e3f2fd; border-radius: 4px; font-size: 13px; }
|
|
.passbook-balance-row .balance-item b { color: #1976d2; }
|
|
table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
|
th { background: #1976d2; color: #fff; padding: 6px 8px; text-align: left; font-weight: 600; }
|
|
td { padding: 5px 8px; border-bottom: 1px solid #e0e0e0; }
|
|
tr:nth-child(even) { background: #fafafa; }
|
|
.text-right { text-align: right; }
|
|
.passbook-footer { margin-top: 16px; font-size: 10px; color: #999; text-align: center; border-top: 1px solid #e0e0e0; padding-top: 8px; }
|
|
.reversed { text-decoration: line-through; color: #999; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
${content}
|
|
</body>
|
|
</html>
|
|
`);
|
|
printWindow.document.close();
|
|
printWindow.focus();
|
|
printWindow.print();
|
|
printWindow.close();
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this.destroy$.next();
|
|
this.destroy$.complete();
|
|
}
|
|
}
|