Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.ts
T
niteshandClaude Sonnet 5 17bfe4c7e6 fix(tasks): separate Pending Share Accounts from the Pending Account screen
Removes the Pending Share Accounts section that had been bolted onto the
Pending Account screen (duplicate data/actions alongside the normal-account
table), restoring it as its own standalone queue. The standalone
pending-share-accounts route/component already existed but had no tab link
anywhere in the Approval Center nav, so it was unreachable from the UI -
added a "Pending Share Accounts" tab for it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-06 10:56:25 +05:30

293 lines
9.5 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, 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<any>;
selection: SelectionModel<any>;
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]));
}
}