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>
This commit is contained in:
+71
-6
@@ -33,8 +33,9 @@ import { DdsApprovalReviewComponent } from 'app/deposits/dds-approvals/dds-appro
|
||||
import { ddsMadeOn } from '../../product-entities';
|
||||
|
||||
/**
|
||||
* Approvals › Agent Approval: every DDS agent request waiting on a checker - an agent being opened,
|
||||
* changed or closed.
|
||||
* 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
|
||||
@@ -73,8 +74,28 @@ export class AgentApprovalsComponent implements OnInit {
|
||||
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',
|
||||
@@ -104,6 +125,18 @@ export class AgentApprovalsComponent implements OnInit {
|
||||
.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();
|
||||
}
|
||||
|
||||
@@ -113,19 +146,51 @@ export class AgentApprovalsComponent implements OnInit {
|
||||
.getDdsApprovals('PENDING')
|
||||
.pipe(catchError(() => of([])))
|
||||
.subscribe((rows: any) => {
|
||||
const agents = (Array.isArray(rows) ? rows : []).filter((row: any) => row?.entity === 'AGENT');
|
||||
this.dataSource.data = agents.map((row: any) => ({ ...row, submittedOn: ddsMadeOn(row.madeOn) }));
|
||||
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();
|
||||
}
|
||||
|
||||
/** What the request is, as the badge reads it. */
|
||||
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 {
|
||||
return row.action === 'CREATE' ? 'New' : row.action === 'CLOSE' ? 'Close' : 'Edit';
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user