266 lines
7.8 KiB
TypeScript
266 lines
7.8 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, OnDestroy, OnInit, inject } from '@angular/core';
|
|
import { Router } from '@angular/router';
|
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
import { NgClass } from '@angular/common';
|
|
import { MatPaginator, PageEvent } from '@angular/material/paginator';
|
|
import {
|
|
MatCell,
|
|
MatCellDef,
|
|
MatColumnDef,
|
|
MatHeaderCell,
|
|
MatHeaderCellDef,
|
|
MatHeaderRow,
|
|
MatHeaderRowDef,
|
|
MatRow,
|
|
MatRowDef,
|
|
MatTable
|
|
} from '@angular/material/table';
|
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
import { Subject, merge } from 'rxjs';
|
|
import { debounceTime, distinctUntilChanged, takeUntil } from 'rxjs/operators';
|
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
import { FormatNumberPipe } from 'app/pipes/format-number.pipe';
|
|
import { OrganizationService } from 'app/organization/organization.service';
|
|
|
|
/**
|
|
* The DDS accounts, listed the way Savings Accounts lists savings: account number, client, product,
|
|
* balance and status, searched by account number or client and narrowed by opening date. Each opens
|
|
* onto the account's own page and its transactions.
|
|
*
|
|
* Read-only. Accounts are opened and changed from DDS Party Master; money moves through collections,
|
|
* the cashier and the batch runs.
|
|
*/
|
|
@Component({
|
|
selector: 'mifosx-dds-accounts',
|
|
templateUrl: './dds-accounts.component.html',
|
|
// The Savings Accounts list's own look, so the two screens read as one family.
|
|
styleUrls: [
|
|
'../../accounts/accounts-list/accounts-list.component.scss',
|
|
'./dds-accounts.component.scss'
|
|
],
|
|
standalone: true,
|
|
imports: [
|
|
...STANDALONE_SHARED_IMPORTS,
|
|
ReactiveFormsModule,
|
|
NgClass,
|
|
FaIconComponent,
|
|
MatTable,
|
|
MatColumnDef,
|
|
MatHeaderCellDef,
|
|
MatHeaderCell,
|
|
MatCellDef,
|
|
MatCell,
|
|
MatHeaderRowDef,
|
|
MatHeaderRow,
|
|
MatRowDef,
|
|
MatRow,
|
|
MatPaginator,
|
|
MatTooltipModule,
|
|
MatProgressSpinnerModule,
|
|
FormatNumberPipe
|
|
]
|
|
})
|
|
export class DdsAccountsComponent implements OnInit, OnDestroy {
|
|
private organizationService = inject(OrganizationService);
|
|
private router = inject(Router);
|
|
private destroy$ = new Subject<void>();
|
|
|
|
displayedColumns = [
|
|
'accountNo',
|
|
'depositType',
|
|
'clientName',
|
|
'productName',
|
|
'balance',
|
|
'status',
|
|
'actions'
|
|
];
|
|
|
|
/** Every DDS account, fetched once; search, dates and paging work on this. */
|
|
private allAccounts: any[] = [];
|
|
/** The accounts matching the search and dates, whatever their status - what the status counts read. */
|
|
private beforeStatus: any[] = [];
|
|
/** The accounts matching the search, dates and status, before paging. */
|
|
private matching: any[] = [];
|
|
|
|
/** Which accounts are shown: all of them, only the open ones, or only the closed. */
|
|
statusFilter: 'ALL' | 'ACTIVE' | 'CLOSED' = 'ALL';
|
|
/** The page on screen. */
|
|
rows: any[] = [];
|
|
|
|
pageSize = 20;
|
|
pageIndex = 0;
|
|
isLoading = false;
|
|
|
|
searchControl = new FormControl('');
|
|
searchType: 'accountNo' | 'clientName' = 'accountNo';
|
|
fromDateControl = new FormControl('');
|
|
toDateControl = new FormControl('');
|
|
asOnDateControl = new FormControl('');
|
|
|
|
readonly today = new Date().toISOString().split('T')[0];
|
|
|
|
get totalRecords(): number {
|
|
return this.matching.length;
|
|
}
|
|
|
|
get activeCount(): number {
|
|
return this.beforeStatus.filter((account) => account.status === 'ACTIVE').length;
|
|
}
|
|
|
|
get closedCount(): number {
|
|
return this.beforeStatus.filter((account) => account.status === 'CLOSED').length;
|
|
}
|
|
|
|
get allCount(): number {
|
|
return this.beforeStatus.length;
|
|
}
|
|
|
|
setStatusFilter(status: 'ALL' | 'ACTIVE' | 'CLOSED'): void {
|
|
this.statusFilter = status;
|
|
this.pageIndex = 0;
|
|
this.applyFilters();
|
|
}
|
|
|
|
get hasDateFilters(): boolean {
|
|
return !!((this.fromDateControl.value && this.toDateControl.value) || this.asOnDateControl.value);
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.loadAccounts();
|
|
|
|
this.searchControl.valueChanges
|
|
.pipe(debounceTime(300), distinctUntilChanged(), takeUntil(this.destroy$))
|
|
.subscribe(() => {
|
|
this.pageIndex = 0;
|
|
this.applyFilters();
|
|
});
|
|
merge(this.fromDateControl.valueChanges, this.toDateControl.valueChanges, this.asOnDateControl.valueChanges)
|
|
.pipe(debounceTime(300), takeUntil(this.destroy$))
|
|
.subscribe(() => {
|
|
this.pageIndex = 0;
|
|
this.applyFilters();
|
|
});
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this.destroy$.next();
|
|
this.destroy$.complete();
|
|
}
|
|
|
|
loadAccounts(): void {
|
|
this.isLoading = true;
|
|
this.organizationService
|
|
.getParties()
|
|
.pipe(takeUntil(this.destroy$))
|
|
.subscribe({
|
|
next: (parties: any) => {
|
|
// Newest first, as the savings list shows them.
|
|
this.allAccounts = (Array.isArray(parties) ? parties : parties?.pageItems || []).sort(
|
|
(a: any, b: any) => Number(b.id) - Number(a.id)
|
|
);
|
|
this.isLoading = false;
|
|
this.applyFilters();
|
|
},
|
|
error: () => {
|
|
this.allAccounts = [];
|
|
this.isLoading = false;
|
|
this.applyFilters();
|
|
}
|
|
});
|
|
}
|
|
|
|
/** Search by account number or client, and by the day the account was opened. */
|
|
private applyFilters(): void {
|
|
const term = String(this.searchControl.value || '')
|
|
.trim()
|
|
.toLowerCase();
|
|
const fromVal = this.fromDateControl.value;
|
|
const toVal = this.toDateControl.value;
|
|
const bothDates = !!(fromVal && toVal);
|
|
const asOn = this.asOnDateControl.value;
|
|
|
|
this.beforeStatus = this.allAccounts.filter((account: any) => {
|
|
if (term) {
|
|
const haystack =
|
|
this.searchType === 'accountNo'
|
|
? `${account.accountNo || ''} ${account.partyCode || ''}`
|
|
: `${account.clientName || ''} ${account.partyName || ''}`;
|
|
if (!haystack.toLowerCase().includes(term)) {
|
|
return false;
|
|
}
|
|
}
|
|
const opened = String(account.openingDate || '');
|
|
if (bothDates && opened && (opened < String(fromVal) || opened > String(toVal))) {
|
|
return false;
|
|
}
|
|
// As on a date: the accounts that had been opened by then.
|
|
if (asOn && opened && opened > String(asOn)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
this.matching =
|
|
this.statusFilter === 'ALL'
|
|
? this.beforeStatus
|
|
: this.beforeStatus.filter((account: any) => account.status === this.statusFilter);
|
|
this.showPage();
|
|
}
|
|
|
|
private showPage(): void {
|
|
const start = this.pageIndex * this.pageSize;
|
|
this.rows = this.matching.slice(start, start + this.pageSize);
|
|
}
|
|
|
|
onPageChange(event: PageEvent): void {
|
|
this.pageIndex = event.pageIndex;
|
|
this.pageSize = event.pageSize;
|
|
this.showPage();
|
|
}
|
|
|
|
toggleSearchType(): void {
|
|
this.searchType = this.searchType === 'accountNo' ? 'clientName' : 'accountNo';
|
|
this.pageIndex = 0;
|
|
this.applyFilters();
|
|
}
|
|
|
|
clearSearch(): void {
|
|
this.searchControl.setValue('');
|
|
}
|
|
|
|
clearDateFilters(): void {
|
|
this.fromDateControl.setValue('', { emitEvent: false });
|
|
this.toDateControl.setValue('', { emitEvent: false });
|
|
this.asOnDateControl.setValue('', { emitEvent: false });
|
|
this.pageIndex = 0;
|
|
this.applyFilters();
|
|
}
|
|
|
|
viewAccount(account: any): void {
|
|
this.router.navigate([
|
|
'/accounts/dds-accounts',
|
|
account.id
|
|
]);
|
|
}
|
|
|
|
getStatusClass(account: any): string {
|
|
switch (account?.status) {
|
|
case 'ACTIVE':
|
|
return 'status--active';
|
|
case 'CLOSED':
|
|
return 'status--closed';
|
|
default:
|
|
return 'status--default';
|
|
}
|
|
}
|
|
}
|