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:
2026-10-29 18:33:15 +05:30
co-authored by Claude Opus 5.5
parent c766a6f092
commit 1f87ad475c
29 changed files with 3409 additions and 556 deletions
@@ -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 {