feat(dds): upgrade DDS UI - agent, party, daily entry, approvals, product view

- Agent master: tabbed layout, customer search dialog, GL dropdown, account card, view page
- Party master: tabbed layout, expected party code, nominee cards, relation from code values
- Daily transaction entry: compact style, collections popup, Apply DDS Receipt placement
- Approvals: new review modal, Agent Approval tab, DDS routing into product/pending account
- DDS product: view page and list buttons; drop Auto Interest Post Day

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 10:50:03 +05:30
co-authored by Claude Sonnet 5.5
parent 357f666421
commit 293b671966
45 changed files with 5973 additions and 1292 deletions
@@ -0,0 +1,175 @@
/**
* 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 Approval: every DDS agent request waiting on a checker - an agent being opened,
* changed or closed.
*
* 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>([]);
loading = true;
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.load();
}
load(): void {
this.loading = true;
this.organizationService
.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) }));
this.loading = false;
});
}
applyFilter(value: string = ''): void {
this.dataSource.filter = value.trim().toLowerCase();
}
/** What the request is, as the badge reads it. */
requestLabel(row: any): string {
return row.action === 'CREATE' ? 'New' : row.action === 'CLOSE' ? 'Close' : 'Edit';
}
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);
}
}