pending account terminate feature develop

This commit is contained in:
keshav898966
2026-07-01 17:19:38 +05:30
parent 9afb177c69
commit d00eac0aa1
19 changed files with 471 additions and 17 deletions
@@ -115,11 +115,10 @@ export class CheckerInboxComponent implements OnInit {
*/
constructor() {
this.route.data.subscribe((data: { makerCheckerResource: any; makerCheckerTemplate: any }) => {
this.searchData = data.makerCheckerResource;
if (this.searchData.length > 0) {
this.checkerData = true;
}
this.makerCheckerTemplate = data.makerCheckerTemplate;
const raw = data.makerCheckerResource;
this.searchData = Array.isArray(raw) ? raw : (raw?.pageItems ?? []);
this.checkerData = this.searchData.length > 0;
this.makerCheckerTemplate = data.makerCheckerTemplate ?? {};
this.dataSource = new MatTableDataSource(this.searchData);
this.selection = new SelectionModel(true, []);
});
@@ -82,8 +82,10 @@ export class PendingAccountComponent {
constructor() {
this.route.data.subscribe((data: { pendingAccountsData: any }) => {
// status 100 = submitted and pending approval only; exclude active/approved/etc.
const items = (data.pendingAccountsData?.pageItems || []).filter((a: any) => a.status?.id === 100);
// 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, []);
@@ -0,0 +1,75 @@
<!--
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/.
-->
<div class="cv-tab-content">
@if (showData) {
<div class="cv-action-bar">
<mat-form-field appearance="outline" class="cv-search-field">
<mat-icon matPrefix>search</mat-icon>
<input matInput placeholder="Search accounts..." (keyup)="applyFilter($event.target['value'])" />
</mat-form-field>
</div>
<div class="table-container">
<table mat-table [dataSource]="dataSource" class="modern-table">
<ng-container matColumnDef="accountNo">
<th mat-header-cell *matHeaderCellDef>Account No</th>
<td mat-cell *matCellDef="let row" class="cell-mono">{{ row.accountNo }}</td>
</ng-container>
<ng-container matColumnDef="clientName">
<th mat-header-cell *matHeaderCellDef>Client Name</th>
<td mat-cell *matCellDef="let row">{{ row.clientName }}</td>
</ng-container>
<ng-container matColumnDef="productName">
<th mat-header-cell *matHeaderCellDef>Product</th>
<td mat-cell *matCellDef="let row">{{ row.savingsProductName || row.productName }}</td>
</ng-container>
<ng-container matColumnDef="accountType">
<th mat-header-cell *matHeaderCellDef>Type</th>
<td mat-cell *matCellDef="let row">{{ row.accountType }}</td>
</ng-container>
<ng-container matColumnDef="officeName">
<th mat-header-cell *matHeaderCellDef>Office</th>
<td mat-cell *matCellDef="let row">{{ row.officeName }}</td>
</ng-container>
<ng-container matColumnDef="submittedOnDate">
<th mat-header-cell *matHeaderCellDef>Submitted On</th>
<td mat-cell *matCellDef="let row">{{ formatDate(row.timeline?.submittedOnDate || row.submittedOnDate) }}</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef>Status</th>
<td mat-cell *matCellDef="let row">
<span class="status-badge status-badge--terminated">Terminated</span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr
mat-row
*matRowDef="let row; columns: displayedColumns"
(click)="navigateToAccount(row)"
class="table-row"
></tr>
</table>
</div>
}
@if (!showData) {
<div class="empty-state">
<i class="fas fa-times-circle empty-icon"></i>
<p class="empty-title">No terminated accounts</p>
<p class="empty-text">Accounts marked for termination will appear here.</p>
</div>
}
</div>
@@ -0,0 +1,17 @@
/**
* 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/.
*/
.status-badge--terminated {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
padding: 2px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
@@ -0,0 +1,106 @@
/**
* 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 {
MatTableDataSource,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow
} from '@angular/material/table';
import { MatIcon } from '@angular/material/icon';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
selector: 'mifosx-terminated-account',
templateUrl: './terminated-account.component.html',
styleUrls: ['./terminated-account.component.scss'],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
MatIcon
]
})
export class TerminatedAccountComponent {
private route = inject(ActivatedRoute);
private router = inject(Router);
dataSource: MatTableDataSource<any>;
showData = false;
displayedColumns: string[] = [
'accountNo',
'clientName',
'productName',
'accountType',
'officeName',
'submittedOnDate',
'status'
];
constructor() {
this.route.data.subscribe((data: { terminatedAccountsData: any }) => {
const items = data.terminatedAccountsData?.pageItems || [];
this.showData = items.length > 0;
this.dataSource = new MatTableDataSource(items);
});
}
navigateToAccount(account: any): void {
const clientId = account.clientId;
const accountId = account.id;
const depositTypeId = account.depositType?.id;
if (account.accountType === 'Current') {
this.router.navigate([`/clients/${clientId}/current-accounts/${accountId}`]);
} else if (depositTypeId === 200) {
this.router.navigate([`/clients/${clientId}/fixed-deposits-accounts/${accountId}`]);
} else if (depositTypeId === 300) {
this.router.navigate([`/clients/${clientId}/recurring-deposits-accounts/${accountId}`]);
} else {
this.router.navigate([`/clients/${clientId}/savings-accounts/${accountId}`]);
}
}
applyFilter(filterValue: string = '') {
this.dataSource.filter = filterValue.trim().toLowerCase();
}
formatDate(date: any): string {
if (!date) return '';
if (Array.isArray(date)) {
return new Date(date[0], date[1] - 1, date[2]).toLocaleDateString('en-IN', {
day: '2-digit',
month: 'short',
year: 'numeric'
});
}
const d = new Date(date);
return isNaN(d.getTime())
? date
: d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' });
}
}
@@ -106,6 +106,18 @@
<fa-icon icon="ban" class="ac-tab__ic"></fa-icon>
<span>Reject Account</span>
</a>
<a
mat-tab-link
class="ac-tab"
[routerLink]="['./terminated-account']"
routerLinkActive
#terminatedAccount="routerLinkActive"
[active]="terminatedAccount.isActive"
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
>
<fa-icon icon="times-circle" class="ac-tab__ic"></fa-icon>
<span>Terminated Account</span>
</a>
<a
mat-tab-link
class="ac-tab"
@@ -0,0 +1,49 @@
/**
* 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 { Injectable, inject } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable, forkJoin, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { TasksService } from '../tasks.service';
@Injectable()
export class GetTerminatedAccounts implements Resolve<any> {
private tasksService = inject(TasksService);
resolve(): Observable<any> {
return forkJoin({
savings: this.tasksService.getTerminatedSavingsAccounts().pipe(catchError(() => of({ pageItems: [] }))),
current: this.tasksService.getTerminatedCurrentAccounts().pipe(catchError(() => of([]))),
offices: this.tasksService.getAllOffices().pipe(catchError(() => of([])))
}).pipe(
map((results) => {
const officeMap: Record<number, string> = {};
(results.offices || []).forEach((o: any) => {
officeMap[o.id] = o.name;
});
const savingsItems = (results.savings?.pageItems || []).map((a: any) => ({
...a,
officeName: officeMap[a.officeId] || a.officeName || '',
fieldOfficerName: a.fieldOfficerName || '',
accountType: a.depositType?.value || 'Savings'
}));
const currentItems = (Array.isArray(results.current) ? results.current : results.current?.pageItems || []).map(
(a: any) => ({ ...a, accountType: 'Current' })
);
return { pageItems: [
...savingsItems,
...currentItems
] };
})
);
}
}
+12 -1
View File
@@ -25,6 +25,7 @@ import { LoanDisbursalComponent } from './checker-inbox-and-tasks-tabs/loan-disb
import { RescheduleLoanComponent } from './checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component';
import { PendingAccountComponent } from './checker-inbox-and-tasks-tabs/pending-account/pending-account.component';
import { RejectedAccountComponent } from './checker-inbox-and-tasks-tabs/rejected-account/rejected-account.component';
import { TerminatedAccountComponent } from './checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component';
import { ViewCheckerInboxComponent } from './view-checker-inbox/view-checker-inbox.component';
/** Custom Resolvers */
@@ -38,6 +39,7 @@ import { MakerCheckerTemplate } from './common-resolvers/makerCheckerTemplate.re
import { GetCheckerInboxDetailResolver } from './common-resolvers/getCheckerInboxDetail.resolver';
import { GetPendingAccounts } from './common-resolvers/getPendingAccounts.resolver';
import { GetRejectedAccounts } from './common-resolvers/getRejectedAccounts.resolver';
import { GetTerminatedAccounts } from './common-resolvers/getTerminatedAccounts.resolver';
/** Tasks Routes */
const routes: Routes = [
@@ -105,6 +107,14 @@ const routes: Routes = [
rejectedAccountsData: GetRejectedAccounts
}
},
{
path: 'terminated-account',
component: TerminatedAccountComponent,
data: { title: 'Terminated Account' },
resolve: {
terminatedAccountsData: GetTerminatedAccounts
}
},
{
path: 'withdrawal-requests',
component: WithdrawalRequestsComponent,
@@ -175,7 +185,8 @@ const routes: Routes = [
MakerCheckerTemplate,
GetCheckerInboxDetailResolver,
GetPendingAccounts,
GetRejectedAccounts
GetRejectedAccounts,
GetTerminatedAccounts
]
})
export class TasksRoutingModule {}
+10
View File
@@ -140,6 +140,16 @@ export class TasksService {
return this.http.get('/currentaccounts', { params: httpParams });
}
getTerminatedSavingsAccounts(): Observable<any> {
const httpParams = new HttpParams().set('limit', '1000').set('status', 'terminated');
return this.http.get('/savingsaccounts', { params: httpParams });
}
getTerminatedCurrentAccounts(): Observable<any> {
const httpParams = new HttpParams().set('limit', '1000').set('status', 'terminated');
return this.http.get('/currentaccounts', { params: httpParams });
}
/**
* Approve a savings account
*/