feat: add database export, welcome kit dialog, and DIP/sanctions updates
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user