Files
cbsnexora/src/app/tasks/checker-inbox-and-tasks-tabs/agent-approvals/agent-approvals.component.ts
T
niteshandClaude Opus 5.5 1f87ad475c feat(dds): agent-first Party Master, account actions menu, FD-style statuses
- Party Master opens on agents (paged, from the server summary), then one
  agent's dashboard: accounts, maturity, money and collection cards, each
  with a View that filters the accounts table. The page title follows the
  view, and Back from an account returns to its agent.
- DDS account page: an actions menu with Edit, Premature Closure, and
  Maturity Payout & Close. The manual Close Account buttons are gone, since
  payout and premature closure close the account themselves.
- Statuses Active / Matured / Closed / Premature Closed are shown and
  filtered on every DDS screen through a shared dds-status helper.
- Premature closure dialog redesigned: term progress, numbered steps, and a
  payout calculation for each choice using the server's formulas.
- Agent & Slab Approval tab: slab approvals moved in beside agents, with
  toggle buttons and labels named for the action; richer slab review.
- Batch processing: calculation runs are reported as applied, and an empty
  run is reported as nothing to do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:33:15 +05:30

241 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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, OnInit, inject } from '@angular/core';
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 { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { of } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { OrganizationService } from 'app/organization/organization.service';
import { AuthenticationService } from 'app/core/authentication/authentication.service';
import { DdsApprovalReviewComponent } from 'app/deposits/dds-approvals/dds-approval-review/dds-approval-review.component';
import { ddsMadeOn } from '../../product-entities';
/**
* Approvals › Agent & Slab Approval: every DDS agent request waiting on a checker - an agent being
* opened, changed or closed - and every commission slab being added or changed, since the slabs are
* what an agent is paid by.
*
* An agent has no voucher and no amount, so the money columns of DDS Approvals say nothing about one.
* This lists them by what identifies an agent - who they are, which branch, which savings account the
* commission goes into, how many guarantors - and opens the same review the DDS queue uses, which
* shows the agent and their guarantors in full and approves or rejects through the DDS queue.
*/
@Component({
selector: 'mifosx-agent-approvals',
templateUrl: './agent-approvals.component.html',
// The same look as Product Approval, so the tabs of this screen read alike.
styleUrls: [
'../pending-savings-products/pending-savings-products.component.scss',
'./agent-approvals.component.scss'
],
imports: [
...STANDALONE_SHARED_IMPORTS,
FaIconComponent,
MatTable,
MatColumnDef,
MatHeaderCellDef,
MatHeaderCell,
MatCellDef,
MatCell,
MatHeaderRowDef,
MatHeaderRow,
MatRowDef,
MatRow,
MatIcon
]
})
export class AgentApprovalsComponent implements OnInit {
private organizationService = inject(OrganizationService);
private authenticationService = inject(AuthenticationService);
private dialog = inject(MatDialog);
private readonly currentUserId = this.authenticationService.getCredentials()?.userId;
dataSource = new MatTableDataSource<any>([]);
/** Commission slab requests: a slab is what an agent's commission is worked out by. */
slabSource = new MatTableDataSource<any>([]);
loading = true;
/** Which list is on show: agents by default, commission slabs from the button beside it. */
listView: 'agents' | 'slabs' = 'agents';
show(view: 'agents' | 'slabs'): void {
this.listView = view;
}
slabColumns: string[] = [
'request',
'slabType',
'band',
'commission',
'maker',
'madeOn',
'status',
'actions'
];
displayedColumns: string[] = [
'request',
'agent',
'client',
'branch',
'savingAccount',
'guarantors',
'maker',
'madeOn',
'status',
'actions'
];
ngOnInit(): void {
this.dataSource.filterPredicate = (row: any, filter: string) =>
[
row.agentName,
row.agentCode,
row.clientName,
row.officeName,
row.savingAccountNo,
row.type,
row.maker,
row.makerName
]
.filter(Boolean)
.join(' ')
.toLowerCase()
.includes(filter);
this.slabSource.filterPredicate = (row: any, filter: string) =>
[
row.slabType,
row.details,
row.type,
row.maker,
row.makerName
]
.filter(Boolean)
.join(' ')
.toLowerCase()
.includes(filter);
this.load();
}
load(): void {
this.loading = true;
this.organizationService
.getDdsApprovals('PENDING')
.pipe(catchError(() => of([])))
.subscribe((rows: any) => {
const all = (Array.isArray(rows) ? rows : []).map((row: any) => ({
...row,
submittedOn: ddsMadeOn(row.madeOn)
}));
this.dataSource.data = all.filter((row: any) => row?.entity === 'AGENT');
this.slabSource.data = all.filter((row: any) => row?.entity === 'COMMISSIONSLAB');
this.loading = false;
});
}
applyFilter(value: string = ''): void {
this.dataSource.filter = value.trim().toLowerCase();
this.slabSource.filter = value.trim().toLowerCase();
}
get hasRequests(): boolean {
return this.dataSource.data.length > 0 || this.slabSource.data.length > 0;
}
/** What the request is, as the badge reads it: "New Agent", "Edit Slab", "Close Agent". */
requestLabel(row: any): string {
const verb = row.action === 'CREATE' ? 'New' : row.action === 'CLOSE' ? 'Close' : 'Edit';
return `${verb} ${row.entity === 'COMMISSIONSLAB' ? 'Slab' : 'Agent'}`;
}
/** ACCOUNT → Account. */
slabTypeLabel(type: string | null | undefined): string {
return type ? type.charAt(0).toUpperCase() + type.slice(1).toLowerCase() : '—';
}
private static readonly RUPEES = new Intl.NumberFormat('en-IN', {
style: 'currency',
currency: 'INR',
minimumFractionDigits: 0,
maximumFractionDigits: 2
});
rupees(value: any): string {
return value === null || value === undefined || value === ''
? '—'
: AgentApprovalsComponent.RUPEES.format(Number(value));
}
percent(value: any): string {
return value === null || value === undefined || value === '' ? '—' : `${Number(value)}%`;
}
isMine(row: any): boolean {
return !!this.currentUserId && Number(row.makerId) === Number(this.currentUserId);
}
formatDate(date: Date | null): string {
return date ? date.toLocaleString('en-IN') : '';
}
/** Opens the agent in full - the client, address, branch, accounts and guarantors the request carries. */
view(row: any): void {
this.dialog
.open(DdsApprovalReviewComponent, {
data: { entry: row, currentUserId: this.currentUserId },
autoFocus: false
})
.afterClosed()
.subscribe((done: boolean) => {
if (done) {
this.load();
}
});
}
/**
* Whether this user may approve or reject the request. It is the same act the review and DDS
* Approvals perform, through the DDS queue, which refuses whoever made the request - so it is not
* offered to them here either.
*/
canAct(row: any): boolean {
return !this.isMine(row);
}
/**
* Approve and Reject open the agent in full rather than acting at once: a request is never actioned
* without its client, address, branch, accounts and guarantors on screen. The review carries the
* Approve and Reject buttons, and asks before rejecting.
*/
approve(row: any): void {
this.view(row);
}
reject(row: any): void {
this.view(row);
}
}