feat: add database export, welcome kit dialog, and DIP/sanctions updates
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 17:52:28 +05:30
co-authored by Claude Opus 5
parent c18219fce5
commit 7ef75c902d
33 changed files with 3042 additions and 208 deletions
+63
View File
@@ -347,6 +347,69 @@
<!-- STR -->
@if (activeTab === 'str') {
<!--
Sanctions STRs are a separate stream from the AML alerts below, not merged into them: an AML
alert is raised by a monetary transaction breaching a rule, whereas a sanctions STR is
triggered by who the customer is. Mixing them would put two different kinds of suspicion in
one queue and require inventing a transaction that never happened.
-->
@if (sanctionsStrCases.length) {
<div class="fiu-section">
<div class="fiu-form-meta">
<div class="fiu-form-title">
<h3>Sanctions STRs (UAPA s.51-A)</h3>
<p>
Confirmed matches against the UN Security Council Consolidated List. Accounts are frozen; the STR must
reach FIU-IND within 7 days of confirmation.
</p>
</div>
<select class="fiu-inp" [(ngModel)]="sanctionsStrFilter" (ngModelChange)="loadSanctionsStrCases()">
<option value="">All</option>
<option value="PENDING_FILING">Pending filing</option>
<option value="FILED">Filed</option>
</select>
</div>
<div class="fiu-tbl-wrap">
<table class="fiu-tbl">
<thead>
<tr>
<th>Alert</th>
<th>Client</th>
<th>Screened Name</th>
<th>Listed As</th>
<th>Confirmed On</th>
<th>Due</th>
<th>Status</th>
<th>STR Reference</th>
</tr>
</thead>
<tbody>
@for (str of sanctionsStrCases; track str.id) {
<tr>
<td class="mono">#{{ str.id }}</td>
<td>
@if (str.clientId) {
<a [routerLink]="['/clients', str.clientId, 'general']">{{ str.clientName || str.clientId }}</a>
} @else {
<span class="muted">Not onboarded</span>
}
</td>
<td>{{ str.screenedName }}</td>
<td>{{ str.matchedNames || '—' }}</td>
<td>{{ str.approvedOn | date: 'mediumDate' }}</td>
<td [class.str-overdue]="isStrOverdue(str)">{{ str.strDueDate || '—' }}</td>
<td>
<span [class]="strStatusClass(str)">{{ strStatusLabel(str) }}</span>
</td>
<td class="mono">{{ str.fiuStrReference || '—' }}</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<div class="fiu-section">
<!-- LIVE: suspicious AML alerts awaiting AML Officer review (Structuring, PEP, etc.) -->
<div class="fiu-form-meta">
+41
View File
@@ -11,6 +11,7 @@ import { FormsModule } from '@angular/forms';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { AmlService } from 'app/aml/aml.service';
import { SanctionsLogsService } from 'app/reports/sanctions-logs/sanctions-logs.service';
import { AuthenticationService } from 'app/core/authentication/authentication.service';
import {
BRANCHES,
@@ -130,6 +131,7 @@ export class FiuComponent implements OnInit {
private static readonly SEED_VERSION = '2026-01-cash-threshold';
private amlService = inject(AmlService);
private sanctionsLogsService = inject(SanctionsLogsService);
private authenticationService = inject(AuthenticationService);
/** Active tab */
activeTab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files' = 'dashboard';
@@ -176,6 +178,13 @@ export class FiuComponent implements OnInit {
// -------------------- Live AML monitoring (real backend, replaces mock CTR/STR tables) --------------------
amlAlerts: any[] = [];
/**
* Confirmed UNSC sanctions matches carrying an STR obligation. Held separately from amlAlerts
* because they are identity-driven rather than transaction-driven — see the template comment.
*/
sanctionsStrCases: any[] = [];
sanctionsStrFilter = '';
amlAlertStatusFilter = '';
amlReviewNotes: Record<number, string> = {};
@@ -320,6 +329,37 @@ export class FiuComponent implements OnInit {
return this.failedFindings.length > 0 ? 'SUSPICIOUS' : 'NOT_SUSPICIOUS';
}
/** Loads confirmed sanctions matches that owe an STR. */
loadSanctionsStrCases() {
this.sanctionsLogsService.getStrCases(this.sanctionsStrFilter || undefined).subscribe({
next: (rows: any) => (this.sanctionsStrCases = rows || []),
// A failure here must not blank the AML side of the tab, which is unrelated.
error: () => (this.sanctionsStrCases = [])
});
}
strStatusLabel(str: any): string {
if (str?.strStatus === 'FILED') {
return 'FILED';
}
return this.isStrOverdue(str) ? 'OVERDUE' : 'PENDING FILING';
}
strStatusClass(str: any): string {
if (str?.strStatus === 'FILED') {
return 'fiu-pill fiu-pill--ok';
}
return this.isStrOverdue(str) ? 'fiu-pill fiu-pill--danger' : 'fiu-pill fiu-pill--warn';
}
/** Past its 7-day PMLA deadline and still unfiled — the state that actually needs chasing. */
isStrOverdue(str: any): boolean {
if (!str?.strDueDate || str.strStatus === 'FILED') {
return false;
}
return new Date(str.strDueDate) < new Date(new Date().toDateString());
}
get isPrincipalOfficer(): boolean {
return this.authenticationService.isPrincipalOfficerUser();
}
@@ -547,6 +587,7 @@ export class FiuComponent implements OnInit {
*/
loadAmlAlerts(): void {
this.amlService.getAlerts().subscribe((data) => (this.amlAlerts = data || []));
this.loadSanctionsStrCases();
}
loadAmlCtrReports(): void {