199 lines
6.0 KiB
TypeScript
199 lines
6.0 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, ViewChild, inject } from '@angular/core';
|
|
import { MatPaginator } from '@angular/material/paginator';
|
|
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
|
import {
|
|
MatTable,
|
|
MatTableDataSource,
|
|
MatColumnDef,
|
|
MatHeaderCellDef,
|
|
MatHeaderCell,
|
|
MatCellDef,
|
|
MatCell,
|
|
MatHeaderRowDef,
|
|
MatHeaderRow,
|
|
MatRowDef,
|
|
MatRow
|
|
} from '@angular/material/table';
|
|
import { MatTooltip } from '@angular/material/tooltip';
|
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
|
|
/** Custom Services */
|
|
import { OrganizationService } from 'app/organization/organization.service';
|
|
|
|
/** Custom Components */
|
|
import { NomineeListDialogComponent } from './nominee-list-dialog/nominee-list-dialog.component';
|
|
|
|
/**
|
|
* Party Master list.
|
|
*
|
|
* The DDS accounts on file - each one a customer's daily deposit account, opened under a scheme
|
|
* (Sub Type) and assigned to the agent who collects for it. Follows the same list -> create/edit
|
|
* shape as Agent Master and the product lists, and shares their stylesheet.
|
|
*/
|
|
@Component({
|
|
selector: 'mifosx-party-master',
|
|
templateUrl: './party-master.component.html',
|
|
styleUrls: ['./party-master.component.scss'],
|
|
imports: [
|
|
...STANDALONE_SHARED_IMPORTS,
|
|
FaIconComponent,
|
|
MatTable,
|
|
MatSort,
|
|
MatColumnDef,
|
|
MatHeaderCellDef,
|
|
MatHeaderCell,
|
|
MatSortHeader,
|
|
MatCellDef,
|
|
MatCell,
|
|
MatHeaderRowDef,
|
|
MatHeaderRow,
|
|
MatRowDef,
|
|
MatRow,
|
|
MatPaginator,
|
|
MatTooltip
|
|
]
|
|
})
|
|
export class PartyMasterComponent implements OnInit {
|
|
private organizationService = inject(OrganizationService);
|
|
private dialog = inject(MatDialog);
|
|
|
|
displayedColumns: string[] = [
|
|
'partyCode',
|
|
'partyName',
|
|
'subType',
|
|
'agentName',
|
|
'savingsAccountNo',
|
|
'dailyDepositAmount',
|
|
'currentBalance',
|
|
'nominees',
|
|
'status',
|
|
'actions'
|
|
];
|
|
|
|
/**
|
|
* Who a party is nominated to, without opening it for editing.
|
|
*
|
|
* Asked often enough to belong on the list, and answered from what the list already holds - the
|
|
* parties come back with their nominees on them, so this opens rather than fetches.
|
|
*/
|
|
showNominees(party: any): void {
|
|
this.dialog.open(NomineeListDialogComponent, {
|
|
data: {
|
|
partyCode: party.partyCode,
|
|
partyName: party.partyName,
|
|
splitMode: party.nomineeSplitMode,
|
|
nominees: party.nominees || []
|
|
},
|
|
autoFocus: false
|
|
});
|
|
}
|
|
|
|
/** How many people are named on an account, which is what the list column shows. */
|
|
nomineeCount(party: any): number {
|
|
return (party?.nominees || []).length;
|
|
}
|
|
|
|
/**
|
|
* Matured accounts still open, and what they add up to. Shown here because this is the screen an
|
|
* officer already opens - money the bank owes a customer should not wait on somebody thinking to
|
|
* run a report.
|
|
*/
|
|
dueForClosureCount = 0;
|
|
dueForClosurePayable = 0;
|
|
|
|
dataSource: MatTableDataSource<any> = new MatTableDataSource<any>([]);
|
|
parties: any[] = [];
|
|
filteredCount = 0;
|
|
|
|
paginator: MatPaginator;
|
|
sort: MatSort;
|
|
|
|
// The table sits inside an @if, so these only exist once the list has loaded. Read statically they
|
|
// were never there, the table was never paged, and the paginator read "0 of 0" over a full list.
|
|
@ViewChild(MatPaginator) set matPaginator(paginator: MatPaginator) {
|
|
this.paginator = paginator;
|
|
if (paginator) {
|
|
this.dataSource.paginator = paginator;
|
|
}
|
|
}
|
|
|
|
@ViewChild(MatSort) set matSort(sort: MatSort) {
|
|
this.sort = sort;
|
|
if (sort) {
|
|
this.dataSource.sort = sort;
|
|
}
|
|
}
|
|
|
|
get totalCount(): number {
|
|
return this.parties.length;
|
|
}
|
|
|
|
get activeCount(): number {
|
|
return this.parties.filter((party) => party.status !== 'CLOSED').length;
|
|
}
|
|
|
|
/** What the whole book is worth today - every open account's running balance added up. */
|
|
get totalCurrentBalance(): number {
|
|
return this.parties.reduce((sum, party) => sum + Number(party.currentBalance || 0), 0);
|
|
}
|
|
|
|
/** What the agents should be collecting between them on a full collection day. */
|
|
get totalDailyDue(): number {
|
|
return this.parties
|
|
.filter((party) => party.status !== 'CLOSED')
|
|
.reduce((sum, party) => sum + Number(party.dailyDepositAmount || 0), 0);
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.organizationService.getParties().subscribe((parties: any[]) => {
|
|
this.parties = parties || [];
|
|
this.dataSource = new MatTableDataSource(this.parties);
|
|
this.dataSource.paginator = this.paginator;
|
|
this.dataSource.sort = this.sort;
|
|
this.filteredCount = this.parties.length;
|
|
this.dataSource.connect().subscribe((rows) => (this.filteredCount = rows.length));
|
|
});
|
|
|
|
this.loadDueForClosure();
|
|
}
|
|
|
|
/**
|
|
* Matured accounts still open. A separate call rather than filtered out of the list above, because
|
|
* the server decides what counts as matured - comparing dates here would drift from it the first
|
|
* time either changed.
|
|
*/
|
|
private loadDueForClosure(): void {
|
|
this.organizationService.getPartiesDueForClosure().subscribe({
|
|
next: (due: any) => {
|
|
const rows = Array.isArray(due) ? due : due?.pageItems || [];
|
|
this.dueForClosureCount = rows.length;
|
|
this.dueForClosurePayable = rows.reduce(
|
|
(sum: number, party: any) => sum + Number(party.currentBalance || 0),
|
|
0
|
|
);
|
|
},
|
|
// The party list is the point of this screen; a failed banner must not take it down with it.
|
|
error: () => {
|
|
this.dueForClosureCount = 0;
|
|
this.dueForClosurePayable = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
applyFilter(filterValue: string): void {
|
|
this.dataSource.filter = filterValue.trim().toLowerCase();
|
|
}
|
|
}
|