feat: KYC document update, FIU & AML recurring alerts integration, Sponsor DD revoke workflow, and TDS reports

This commit is contained in:
2026-09-14 11:43:26 +05:30
parent 7afa9c7a50
commit 2124bb4b76
40 changed files with 3028 additions and 929 deletions
@@ -25,7 +25,6 @@ import { AmlService } from '../../../aml/aml.service';
import { AlertService } from '../../../core/alert/alert.service';
import { HasPermissionDirective } from '../../../directives/has-permission/has-permission.directive';
import { AmlRecurringReviewDialogComponent } from './aml-recurring-review-dialog.component';
import { amlRecurringAlertsDemoData } from './aml-recurring-alerts.demo-data';
/**
* Customers whose transactions keep tripping the AML engine, queued for an employee to review why.
@@ -95,8 +94,6 @@ export class AmlRecurringAlertsComponent implements OnInit {
loading = false;
loadFailed = false;
/** Set when the rows on screen are the sample set rather than engine output. */
usingDemoData = false;
/** Which customer's alert detail is expanded inline. */
expandedClientId: number | null = null;
@@ -111,20 +108,20 @@ export class AmlRecurringAlertsComponent implements OnInit {
this.amlService.getRecurringAlerts(this.minAlerts, this.windowDays, this.onlyNeedingReview).subscribe({
next: (data) => {
this.loading = false;
const rows = data || [];
this.usingDemoData = rows.length === 0;
this.rows.data = this.mapRows(this.usingDemoData ? this.demoRows() : rows);
this.rows.data = this.mapRows(data || []);
},
error: () => {
this.loading = false;
this.loadFailed = true;
this.usingDemoData = true;
this.rows.data = this.mapRows(this.demoRows());
// An AML review queue shows what the engine found or nothing at all. Standing in sample
// customers when the call fails invents suspicious activity against names that are not
// the bank's, which is worse than an empty table saying the load failed.
this.rows.data = [];
}
});
}
/** Parses the date fields the template renders. Shared by engine rows and the sample set. */
/** Parses the date fields the template renders. */
private mapRows(rows: any[]): any[] {
return rows.map((r: any) => ({
...r,
@@ -135,36 +132,6 @@ export class AmlRecurringAlertsComponent implements OnInit {
}));
}
/**
* The sample set narrowed by the on-screen controls, so the threshold and window selectors behave
* the way they would against the engine instead of returning the same three rows regardless.
*/
private demoRows(): any[] {
const cutoff = Date.now() - this.windowDays * 24 * 60 * 60 * 1000;
return amlRecurringAlertsDemoData()
.map((row: any) => {
const alerts = (row.alerts || []).filter((a: any) => new Date(a.raisedOn).getTime() >= cutoff);
const ruleCodes = Array.from(new Set(alerts.map((a: any) => a.ruleCode)));
return {
...row,
alerts,
alertCount: alerts.length,
openCount: alerts.filter((a: any) => a.status === 'OPEN').length,
escalatedCount: alerts.filter((a: any) => a.status === 'ESCALATED').length,
// Severity sits on the alert server-side; the sample rows carry a row total, so it is
// capped at what survived the window rather than recomputed.
highRiskCount: Math.min(row.highRiskCount, alerts.length),
distinctRules: ruleCodes.length,
ruleCodes,
totalAmount: alerts.reduce((sum: number, a: any) => sum + (Number(a.amount) || 0), 0),
firstAlertOn: alerts.length ? alerts[0].raisedOn : null,
lastAlertOn: alerts.length ? alerts[alerts.length - 1].raisedOn : null
};
})
.filter((row: any) => row.alertCount >= this.minAlerts)
.filter((row: any) => !this.onlyNeedingReview || row.needsReview);
}
toggleExpanded(row: any): void {
this.expandedClientId = this.expandedClientId === row.clientId ? null : row.clientId;
}