/** * 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 { SelectionModel } from '@angular/cdk/collections'; import { switchMap } from 'rxjs/operators'; import { MatTableDataSource, MatTable, MatColumnDef, MatHeaderCellDef, MatHeaderCell, MatCellDef, MatCell, MatHeaderRowDef, MatHeaderRow, MatRowDef, MatRow } from '@angular/material/table'; import { MatDialog } from '@angular/material/dialog'; import { MatIcon } from '@angular/material/icon'; import { MatCheckbox } from '@angular/material/checkbox'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { TasksService } from '../../tasks.service'; import { SettingsService } from 'app/settings/settings.service'; import { Dates } from 'app/core/utils/dates'; import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base'; import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ selector: 'mifosx-pending-account', templateUrl: './pending-account.component.html', styleUrls: ['./pending-account.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, FaIconComponent, MatTable, MatColumnDef, MatHeaderCellDef, MatHeaderCell, MatCheckbox, MatCellDef, MatCell, MatHeaderRowDef, MatHeaderRow, MatRowDef, MatRow, MatIcon ] }) export class PendingAccountComponent { private route = inject(ActivatedRoute); private router = inject(Router); private dialog = inject(MatDialog); private dateUtils = inject(Dates); private settingsService = inject(SettingsService); private tasksService = inject(TasksService); dataSource: MatTableDataSource; selection: SelectionModel; showData = false; displayedColumns: string[] = [ 'select', 'accountNo', 'clientName', 'productName', 'accountType', 'officeName', 'fieldOfficerName', 'submittedOnDate', 'status' ]; constructor() { this.route.data.subscribe((data: { pendingAccountsData: any }) => { // status 100 = submitted and pending approval only; exclude terminated accounts const items = (data.pendingAccountsData?.pageItems || []).filter( (a: any) => a.status?.id === 100 && !a.terminateTrue ); this.showData = items.length > 0; this.dataSource = new MatTableDataSource(items); this.selection = new SelectionModel(true, []); }); } isAllSelected() { return this.selection.selected.length === this.dataSource.data.length; } masterToggle() { this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach((row) => this.selection.select(row)); } checkboxLabel(row?: any): string { if (!row) return `${this.isAllSelected() ? 'deselect' : 'select'} all`; return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row`; } applyFilter(filterValue: string = '') { this.dataSource.filter = filterValue.trim().toLowerCase(); } navigateToAccount(account: any): void { const clientId = account.clientId; const accountId = account.id; const depositTypeId = account.depositType?.id; let path: string; if (account.accountType === 'Current') { path = `/clients/${clientId}/current-accounts/${accountId}`; } else if (depositTypeId === 200) { path = `/clients/${clientId}/fixed-deposits-accounts/${accountId}`; } else if (depositTypeId === 300) { path = `/clients/${clientId}/recurring-deposits-accounts/${accountId}`; } else { path = `/clients/${clientId}/savings-accounts/${accountId}`; } this.router.navigate([path]); } approveAccounts() { if (!this.selection.hasValue()) return; const formfields: FormfieldBase[] = [ new DatepickerBase({ controlName: 'approvedOnDate', label: 'Approved On Date', value: new Date(), type: 'datetime-local', required: true }) ]; const dialogRef = this.dialog.open(FormDialogComponent, { data: { title: 'Enter Approval Date', layout: { addButtonText: 'Confirm' }, formfields, pristine: false } }); dialogRef.afterClosed().subscribe((response: any) => { if (response?.data) { this.bulkApprove(response.data); } }); } rejectAccounts() { if (!this.selection.hasValue()) return; const formfields: FormfieldBase[] = [ new DatepickerBase({ controlName: 'rejectedOnDate', label: 'Rejected On Date', value: new Date(), type: 'datetime-local', required: true }) ]; const dialogRef = this.dialog.open(FormDialogComponent, { data: { title: 'Enter Rejection Date', layout: { addButtonText: 'Confirm' }, formfields, pristine: false } }); dialogRef.afterClosed().subscribe((response: any) => { if (response?.data) { this.bulkReject(response.data); } }); } private bulkReject(submittedData: any) { const dateFormat = this.settingsService.dateFormat; const locale = this.settingsService.language.code; const rejectedOnDate = this.dateUtils.formatDate(submittedData.value.rejectedOnDate, dateFormat); const payload = { rejectedOnDate, dateFormat, locale }; const selected = this.selection.selected; let settled = 0; selected.forEach((account: any) => { const depositTypeId = account.depositType?.id; let reject$; if (account.accountType === 'Current') { reject$ = this.tasksService.rejectCurrentAccount(account.id, payload); } else if (depositTypeId === 200) { reject$ = this.tasksService.rejectFDAccount(account.id, payload); } else if (depositTypeId === 300) { reject$ = this.tasksService.rejectRDAccount(account.id, payload); } else { reject$ = this.tasksService.rejectSavingsAccount(account.id, payload); } reject$.subscribe({ next: () => { settled++; if (settled === selected.length) { this.reload(); } }, error: () => { settled++; if (settled === selected.length) { this.reload(); } } }); }); } /** * Approves an account, then immediately activates it on the same date -- Approve and Activate * are still two separate Fineract commands underneath (each shows up in the audit trail), but * the checker only sees and clicks through the one bulk action, matching what happens when * approving from inside an individual account (see e.g. ApproveSavingsAccountComponent). */ private approveAndActivate$(account: any, approvedOnDate: string, dateFormat: string, locale: string) { const approvePayload = { approvedOnDate, dateFormat, locale }; const activatePayload = { activatedOnDate: approvedOnDate, dateFormat, locale }; const depositTypeId = account.depositType?.id; let approve$; let activate$; if (account.accountType === 'Current') { approve$ = this.tasksService.approveCurrentAccount(account.id, approvePayload); activate$ = () => this.tasksService.activateCurrentAccount(account.id, activatePayload); } else if (depositTypeId === 200) { approve$ = this.tasksService.approveFDAccount(account.id, approvePayload); activate$ = () => this.tasksService.activateFDAccount(account.id, activatePayload); } else if (depositTypeId === 300) { approve$ = this.tasksService.approveRDAccount(account.id, approvePayload); activate$ = () => this.tasksService.activateRDAccount(account.id, activatePayload); } else { approve$ = this.tasksService.approveSavingsAccount(account.id, approvePayload); activate$ = () => this.tasksService.activateSavingsAccount(account.id, activatePayload); } return approve$.pipe(switchMap(activate$)); } private bulkApprove(submittedData: any) { const dateFormat = this.settingsService.dateFormat; const locale = this.settingsService.language.code; const approvedOnDate = this.dateUtils.formatDate(submittedData.value.approvedOnDate, dateFormat); const selected = this.selection.selected; let settled = 0; selected.forEach((account: any) => { this.approveAndActivate$(account, approvedOnDate, dateFormat, locale).subscribe({ next: () => { settled++; if (settled === selected.length) { this.reload(); } }, error: () => { settled++; if (settled === selected.length) { this.reload(); } } }); }); } formatDate(date: any): string { if (!date) return ''; if (Array.isArray(date)) { const d = new Date(date[0], date[1] - 1, date[2]); return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); } const d = new Date(date); if (!isNaN(d.getTime())) { return d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); } return date; } reload() { const url = this.router.url; this.router .navigateByUrl('/checker-inbox-and-tasks', { skipLocationChange: true }) .then(() => this.router.navigate([url])); } }