941 lines
39 KiB
HTML
941 lines
39 KiB
HTML
<!--
|
||
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/.
|
||
-->
|
||
|
||
<div class="fiu-root">
|
||
<!-- HEADER -->
|
||
<div class="fiu-head">
|
||
<div class="fiu-head-left">
|
||
<fa-icon icon="shield-alt" class="fiu-head-ic"></fa-icon>
|
||
<div>
|
||
<h2 class="fiu-head-t">Financial Intelligence Unit – India (FIU-IND)</h2>
|
||
<div class="fiu-head-s">
|
||
{{ bank.name }} · {{ scopeName }} · Reporting Month
|
||
<strong>{{ reportingMonthLabel }}</strong> · PMLA 2002 / FINnet 2.0
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-head-meta">
|
||
<div>
|
||
<span class="fiu-meta-l">Branch</span>
|
||
@if (isHeadOffice) {
|
||
<select class="fiu-inp" [ngModel]="branchFilter" (ngModelChange)="setBranch($event)">
|
||
<option value="">All Branches</option>
|
||
@for (b of branches; track b.code) {
|
||
<option [value]="b.code">{{ b.name }}</option>
|
||
}
|
||
</select>
|
||
} @else {
|
||
<span class="fiu-meta-v">{{ scopeName }}</span>
|
||
}
|
||
</div>
|
||
<!-- Named only when the person at the screen actually holds the role. A filing screen that
|
||
names an officer who is not the one working it misstates who signed the return. -->
|
||
@if (principalOfficerName) {
|
||
<div>
|
||
<span class="fiu-meta-l">Principal Officer</span><span class="fiu-meta-v">{{ principalOfficerName }}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TABS -->
|
||
<div class="fiu-tabs">
|
||
<button class="fiu-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
|
||
<fa-icon icon="chart-pie"></fa-icon> Dashboard
|
||
</button>
|
||
<button class="fiu-tab" [class.active]="activeTab === 'ctr'" (click)="showTab('ctr')">
|
||
<fa-icon icon="coins"></fa-icon> CTR — Cash Transactions
|
||
</button>
|
||
<button class="fiu-tab" [class.active]="activeTab === 'str'" (click)="showTab('str')">
|
||
<fa-icon icon="exclamation-triangle"></fa-icon> STR — Suspicious
|
||
</button>
|
||
<button class="fiu-tab" [class.active]="activeTab === 'customers'" (click)="showTab('customers')">
|
||
<fa-icon icon="users"></fa-icon> Customer Cards
|
||
</button>
|
||
<button class="fiu-tab" [class.active]="activeTab === 'files'" (click)="showTab('files')">
|
||
<fa-icon icon="file-csv"></fa-icon> FINnet Files
|
||
</button>
|
||
</div>
|
||
|
||
<!-- DASHBOARD -->
|
||
@if (activeTab === 'dashboard') {
|
||
<div class="fiu-section">
|
||
<div class="fiu-kpis">
|
||
<div class="fiu-kpi fiu-kpi--blue">
|
||
<div class="fiu-kpi-ic"><fa-icon icon="file-alt"></fa-icon></div>
|
||
<div>
|
||
<div class="fiu-kpi-n">{{ totalReports | number }}</div>
|
||
<div class="fiu-kpi-l">CTR Reports</div>
|
||
<div class="fiu-kpi-sub">{{ totalCtrTxns | number }} txns this cycle</div>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-kpi fiu-kpi--teal">
|
||
<div class="fiu-kpi-ic"><fa-icon icon="coins"></fa-icon></div>
|
||
<div>
|
||
<div class="fiu-kpi-n">₹{{ totalCtrAmount | number: '1.0-0' }}</div>
|
||
<div class="fiu-kpi-l">Total CTR Amount</div>
|
||
<div class="fiu-kpi-sub">aggregate cash movement</div>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-kpi fiu-kpi--amber">
|
||
<div class="fiu-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
|
||
<div>
|
||
<div class="fiu-kpi-n">{{ liveStrPending | number }}</div>
|
||
<div class="fiu-kpi-l">STR Pending</div>
|
||
<div class="fiu-kpi-sub">
|
||
{{ liveStrOpen }} to investigate · {{ liveStrAwaitingFiling }} awaiting filing
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-kpi fiu-kpi--green">
|
||
<div class="fiu-kpi-ic"><fa-icon icon="check-circle"></fa-icon></div>
|
||
<div>
|
||
<div class="fiu-kpi-n">{{ liveStrFiled | number }}</div>
|
||
<div class="fiu-kpi-l">STR Filed</div>
|
||
<div class="fiu-kpi-sub">{{ liveStrCleared }} concluded clear</div>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-kpi fiu-kpi--purple">
|
||
<div class="fiu-kpi-ic"><fa-icon icon="users"></fa-icon></div>
|
||
<div>
|
||
<div class="fiu-kpi-n">{{ totalCustomers | number }}</div>
|
||
<div class="fiu-kpi-l">Customers in Cycle</div>
|
||
<div class="fiu-kpi-sub">{{ kc1.length }} ind · {{ kc2.length }} entity</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- WHEN TO REPORT — the five PMLA obligations -->
|
||
<div class="ob-block">
|
||
<div class="ob-head">
|
||
<div>
|
||
<h3 class="ob-title"><fa-icon icon="gavel"></fa-icon> When to report to FIU-IND</h3>
|
||
<p class="ob-sub">
|
||
The five reports a reporting entity owes under the Prevention of Money Laundering Act. All are filed
|
||
electronically through the FINnet 2.0 portal.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ob-grid">
|
||
@for (o of obligations; track o.code) {
|
||
<div class="ob-card" [class.ob-card--off]="!o.handled">
|
||
<div class="ob-card-h">
|
||
<fa-icon [icon]="o.icon" class="ob-ic"></fa-icon>
|
||
<span class="ob-code">{{ o.code }}</span>
|
||
<span class="ob-name">{{ o.name }}</span>
|
||
@if (o.handled) {
|
||
<span class="ob-flag ob-flag--on">Filed here</span>
|
||
} @else {
|
||
<span class="ob-flag">Tracked</span>
|
||
}
|
||
</div>
|
||
<div class="ob-row">
|
||
<label>Report when</label><span>{{ o.trigger }}</span>
|
||
</div>
|
||
<div class="ob-row">
|
||
<label>Deadline</label><span class="ob-due">{{ o.deadline }}</span>
|
||
</div>
|
||
<div class="ob-note">{{ o.note }}</div>
|
||
@if (o.code === 'CTR') {
|
||
<button class="dc-btn" (click)="showTab('ctr')">
|
||
<fa-icon icon="external-link-alt"></fa-icon> Open CTR
|
||
</button>
|
||
} @else if (o.code === 'STR') {
|
||
<button class="dc-btn" (click)="showTab('str')">
|
||
<fa-icon icon="external-link-alt"></fa-icon> Open STR
|
||
</button>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Compliance cards -->
|
||
<div class="fiu-compliance">
|
||
<div class="dc-card">
|
||
<div class="dc-card-h">
|
||
<fa-icon icon="check-circle" class="dc-ok"></fa-icon><span>CTR — Cash Transaction Report</span>
|
||
</div>
|
||
<div class="dc-meta">
|
||
Threshold: <strong>₹{{ bank.cashThreshold | number }}</strong> per single txn / aggregate per month
|
||
</div>
|
||
<!-- The 15th of the month after the one being reported, worked out rather than fixed at
|
||
15 Feb 2026 — a due date that never moves stops being a due date. -->
|
||
<div class="dc-meta">Due: <strong>{{ ctrDueDateLabel }}</strong></div>
|
||
<button class="dc-btn" (click)="showTab('ctr')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
|
||
</div>
|
||
<div class="dc-card">
|
||
<div class="dc-card-h">
|
||
<fa-icon icon="exclamation-triangle" class="dc-warn"></fa-icon><span>STR — Suspicious Transaction</span>
|
||
</div>
|
||
<div class="dc-meta">Filing window: <strong>within 7 days of detection</strong></div>
|
||
<div class="dc-meta">
|
||
To investigate: <strong>{{ liveStrOpen }}</strong> · Awaiting filing:
|
||
<strong>{{ liveStrAwaitingFiling }}</strong>
|
||
</div>
|
||
<div class="dc-meta">
|
||
Filed: <strong>{{ liveStrFiled }}</strong> · Concluded clear:
|
||
<strong>{{ liveStrCleared }}</strong>
|
||
</div>
|
||
<button class="dc-btn" (click)="showTab('str')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
|
||
</div>
|
||
<div class="dc-card">
|
||
<div class="dc-card-h"><fa-icon icon="file-csv" class="dc-info"></fa-icon><span>FINnet 2.0 Files</span></div>
|
||
<div class="dc-meta">6 CSVs: TC1 / TC2 / KC1 / KC2 / Account_Detail / Account_Person_Relation</div>
|
||
<div class="dc-meta">+ STR XML for suspicious filings</div>
|
||
<button class="dc-btn" (click)="showTab('files')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- CTR -->
|
||
@if (activeTab === 'ctr') {
|
||
<div class="fiu-section">
|
||
<!-- FINNET FILE SEQUENCE — the CTR submission, in prescribed order -->
|
||
<div class="seq-block">
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>CTR Submission Files — FINnet 2.0</h3>
|
||
<p>
|
||
The CTR is built in this order: the account is described first, then who is behind it, then the two
|
||
transaction files. Every grid below is the exact content of its CSV. The customer cards (KC1 / KC2) travel
|
||
with the same bundle and are worked on the Customer Cards tab.
|
||
</p>
|
||
</div>
|
||
<button class="fiu-btn fiu-btn-xls" [disabled]="!ctrTxns.length" (click)="exportAll()">
|
||
<fa-icon icon="file-csv"></fa-icon> Download full bundle
|
||
</button>
|
||
</div>
|
||
|
||
<!-- The grids are built from the reports the bank generated. Until one exists there is
|
||
nothing to file, and saying so is better than a screen of plausible-looking rows
|
||
nobody can submit. -->
|
||
@if (ctrLinesLoading) {
|
||
<div class="fiu-note">Loading the generated CTR lines…</div>
|
||
} @else if (ctrLinesError) {
|
||
<div class="fiu-note fiu-note--warn">
|
||
<fa-icon icon="exclamation-triangle"></fa-icon> {{ ctrLinesError }}
|
||
</div>
|
||
} @else if (!ctrTxns.length) {
|
||
<div class="fiu-note fiu-note--warn">
|
||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||
No CTR has been generated yet. Pick a month under “Generate CTR” below and run it — every cash transaction
|
||
of ₹10,00,000 or more, and every customer whose smaller cash transactions add up to that within the month,
|
||
will be picked up from the books.
|
||
</div>
|
||
}
|
||
|
||
<!-- Scope: these controls decide which transactions the six files carry. -->
|
||
<div class="fiu-filters">
|
||
<div class="fiu-filter">
|
||
<label>Type</label>
|
||
<select class="fiu-inp" [(ngModel)]="ctrFilterMode">
|
||
<option value="">All</option>
|
||
<option value="Deposit">Deposit</option>
|
||
<option value="Withdrawal">Withdrawal</option>
|
||
</select>
|
||
</div>
|
||
<div class="fiu-filter">
|
||
<label>Account contains</label>
|
||
<input class="fiu-inp" type="text" [(ngModel)]="ctrFilterAccount" placeholder="A/c partial" />
|
||
</div>
|
||
<div class="fiu-filter">
|
||
<label>Min Amount (₹)</label>
|
||
<input class="fiu-inp" type="number" [(ngModel)]="ctrFilterMinAmount" placeholder="0" />
|
||
</div>
|
||
<button class="fiu-btn fiu-btn-g" (click)="resetCtrFilters()">Reset</button>
|
||
</div>
|
||
|
||
<!-- Step rail -->
|
||
<div class="seq-rail">
|
||
@for (f of finnetSequence; track f.key) {
|
||
<button class="seq-step" [class.active]="activeFinnetFile === f.key" (click)="showFinnetFile(f.key)">
|
||
<span class="seq-n">{{ f.step }}</span>
|
||
<span class="seq-txt">
|
||
<strong>{{ f.name }}</strong>
|
||
<em>{{ f.table.rows.length }} {{ f.table.rows.length === 1 ? 'row' : 'rows' }}</em>
|
||
</span>
|
||
</button>
|
||
@if (!$last) {
|
||
<fa-icon icon="chevron-right" class="seq-arrow"></fa-icon>
|
||
}
|
||
}
|
||
</div>
|
||
|
||
<!-- Open file -->
|
||
<div class="seq-panel">
|
||
<div class="seq-panel-h">
|
||
<div>
|
||
<h4>Step {{ openFinnetFile.step }} — {{ openFinnetFile.title }}</h4>
|
||
<p>{{ openFinnetFile.purpose }}</p>
|
||
</div>
|
||
<button class="fiu-btn fiu-btn-csv" (click)="downloadOpenFinnetFile()">
|
||
<fa-icon icon="file-csv"></fa-icon> {{ openFinnetFile.name }}.csv
|
||
</button>
|
||
</div>
|
||
|
||
<div class="seq-tbl-wrap">
|
||
<table class="seq-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th class="seq-rownum">#</th>
|
||
@for (h of openFinnetFile.table.headers; track $index) {
|
||
<th>{{ h }}</th>
|
||
}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of openFinnetFile.table.rows; track $index) {
|
||
<tr>
|
||
<td class="seq-rownum">{{ $index + 1 }}</td>
|
||
@for (c of r; track $index) {
|
||
<td>{{ c }}</td>
|
||
}
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td [attr.colspan]="openFinnetFile.table.headers.length + 1" class="seq-empty">
|
||
@if (openFinnetFile.key === 'tc1') {
|
||
No ATM or card transactions match the current scope. When the bank books none at all, TC1 is
|
||
still submitted as a header-only file — that is what FIU-IND expects.
|
||
} @else {
|
||
No rows for the current filters.
|
||
}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- LIVE: monthly CTR reports (mechanical, threshold-based) -->
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>Monthly CTR Reports</h3>
|
||
<p>
|
||
Threshold: <strong>₹{{ bank.cashThreshold | number }}</strong> per single txn / connected aggregate
|
||
per month
|
||
</p>
|
||
</div>
|
||
<div class="fiu-action-group">
|
||
<input class="fiu-inp" type="month" [(ngModel)]="ctrGenerateMonth" />
|
||
<button class="fiu-btn fiu-btn-g" *mifosxHasPermission="'REVIEW_AML_ALERT'" (click)="generateCtrReport()">
|
||
Generate
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-tbl-wrap">
|
||
<table class="fiu-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th>Month</th>
|
||
<th>Generated On</th>
|
||
<th class="ta-c">Records</th>
|
||
<th>Status</th>
|
||
<th>Submitted On</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of amlCtrReports; track r.id) {
|
||
<tr>
|
||
<td>{{ r.reportMonth }}</td>
|
||
<td>{{ r.generatedOn | date: 'medium' }}</td>
|
||
<td class="ta-c">{{ r.recordCount }}</td>
|
||
<td>
|
||
<span [class]="amlStatusClass(r.status)">{{ r.status }}</span>
|
||
</td>
|
||
<td>{{ r.submittedOn ? (r.submittedOn | date: 'medium') : '—' }}</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="5" class="fiu-empty">No CTR reports generated yet.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- 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">
|
||
<div class="fiu-form-title">
|
||
<h3>Live AML Alerts (Awaiting Triage)</h3>
|
||
<p>Flagged by the monitoring engine for suspicious behavioral patterns — triage and whitelist or escalate</p>
|
||
</div>
|
||
<!-- Whitelisted is deliberately absent: cleared alerts are not STR business and have
|
||
their own register below. -->
|
||
<select class="fiu-inp" [(ngModel)]="amlAlertStatusFilter">
|
||
<option value="">All statuses</option>
|
||
<option value="RAISED">Raised</option>
|
||
<option value="UNDER_REVIEW">Under Review</option>
|
||
<option value="ESCALATED">Escalated</option>
|
||
</select>
|
||
</div>
|
||
<div class="fiu-tbl-wrap">
|
||
<table class="fiu-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Client</th>
|
||
<th>Type</th>
|
||
<th>Rule</th>
|
||
<th class="ta-r">Amount</th>
|
||
<th>Raised On</th>
|
||
<th>Status</th>
|
||
<th class="ta-c">Routes To</th>
|
||
<th>Notes</th>
|
||
<th>Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (alert of amlSuspiciousAlerts; track alert.id) {
|
||
<tr>
|
||
<td>{{ alert.id }}</td>
|
||
<td>{{ alert.clientDisplayName }}</td>
|
||
<td>{{ alert.transactionType }}</td>
|
||
<td>{{ alert.ruleCode }} — {{ alert.ruleDescription }}</td>
|
||
<td class="ta-r mono">₹{{ alert.amount | number: '1.0-0' }}</td>
|
||
<td>{{ asDate(alert.raisedOn) | date: 'medium' }}</td>
|
||
<td>
|
||
<span [class]="amlStatusClass(alert.status)">{{ alert.status }}</span>
|
||
</td>
|
||
<td class="ta-c">
|
||
<span [class]="alertRouteClass(alert.status, false)">{{ alertRouteLabel(alert.status, false) }}</span>
|
||
</td>
|
||
<td>
|
||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
||
<input
|
||
type="text"
|
||
class="fiu-inp"
|
||
placeholder="Review notes"
|
||
[(ngModel)]="amlReviewNotes[alert.id]"
|
||
/>
|
||
} @else {
|
||
<span class="muted">{{ alert.reviewNotes }}</span>
|
||
}
|
||
</td>
|
||
<td>
|
||
@if (alert.status === 'RAISED' || alert.status === 'UNDER_REVIEW') {
|
||
<div class="fiu-action-group" *mifosxHasPermission="'REVIEW_AML_ALERT'">
|
||
<button class="fiu-btn fiu-btn-g" (click)="whitelistAlert(alert)">Whitelist</button>
|
||
<button class="fiu-btn fiu-btn-xml" (click)="escalateAlert(alert)">Escalate</button>
|
||
</div>
|
||
}
|
||
</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="10" class="fiu-empty">No suspicious behavioral alerts awaiting triage.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p class="fiu-tbl-legend">
|
||
<span class="report-pill report-pending">Pending</span> awaiting Whitelist/Escalate decision ·
|
||
<span class="report-pill report-str">→ STR</span> escalated to Principal Officer as an STR case
|
||
</p>
|
||
|
||
<hr style="border: 0; border-top: 1px solid var(--f-g200); margin: 8px 0" />
|
||
|
||
<!-- Cleared register: whitelisted alerts, kept out of the STR queue but retained with the
|
||
evidence recorded at review time. -->
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>Cleared Alerts (Whitelisted)</h3>
|
||
<p>Reviewed and closed as false positives — no STR filed. Retained with the evidence relied on.</p>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-tbl-wrap">
|
||
<table class="fiu-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Client</th>
|
||
<th>Type</th>
|
||
<th>Rule</th>
|
||
<th class="ta-r">Amount</th>
|
||
<th>Raised On</th>
|
||
<th>Cleared By</th>
|
||
<th>Evidence</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (alert of amlWhitelistedAlerts; track alert.id) {
|
||
<tr>
|
||
<td>{{ alert.id }}</td>
|
||
<td>{{ alert.clientDisplayName }}</td>
|
||
<td>{{ alert.transactionType }}</td>
|
||
<td>{{ alert.ruleCode }} — {{ alert.ruleDescription }}</td>
|
||
<td class="ta-r mono">₹{{ alert.amount | number: '1.0-0' }}</td>
|
||
<td>{{ asDate(alert.raisedOn) | date: 'medium' }}</td>
|
||
<td>{{ alert.reviewedByUsername || '—' }}</td>
|
||
<td>
|
||
<span class="muted">{{ alert.reviewNotes || '—' }}</span>
|
||
</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="8" class="fiu-empty">No alerts have been cleared.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p class="fiu-tbl-legend">
|
||
<span class="report-pill report-cleared">Cleared</span> whitelisted — no report filed, evidence retained for
|
||
audit
|
||
</p>
|
||
|
||
<hr style="border: 0; border-top: 1px solid var(--f-g200); margin: 8px 0" />
|
||
|
||
<!-- LIVE: STR cases with full Principal Officer actions -->
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>Live STR Cases</h3>
|
||
<p>
|
||
Escalated by an AML Officer; Principal Officer investigates, concludes and files with FIU-IND within 7
|
||
working days
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div class="fiu-tbl-wrap">
|
||
<table class="fiu-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Client</th>
|
||
<th class="ta-c">Cumulative Cases</th>
|
||
<th>Assigned PO</th>
|
||
<th>Conclusion</th>
|
||
<th>FIU Due Date</th>
|
||
<th>Status</th>
|
||
<th>Action</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (sc of amlStrCases; track sc.id) {
|
||
<tr [class.selected-row]="selectedStrCase?.id === sc.id">
|
||
<td>{{ sc.id }}</td>
|
||
<td>{{ sc.clientDisplayName }}</td>
|
||
<td class="ta-c" [class.escalation-warning]="sc.cumulativeStrCaseCountForClient >= 3">
|
||
{{ sc.cumulativeStrCaseCountForClient }}
|
||
</td>
|
||
<td>{{ sc.assignedPoUsername || '—' }}</td>
|
||
<td>{{ sc.conclusion || '—' }}</td>
|
||
<td>{{ sc.fiuDueDate || '—' }}</td>
|
||
<td>
|
||
<span [class]="amlStatusClass(sc.status)">{{ sc.status }}</span>
|
||
</td>
|
||
<td><button class="fiu-btn fiu-btn-g" (click)="selectStrCase(sc)">Investigate</button></td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="8" class="fiu-empty">No STR cases yet.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
@if (selectedStrCase) {
|
||
<div class="str-case-detail">
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>STR Case #{{ selectedStrCase.id }} — {{ selectedStrCase.clientDisplayName }}</h3>
|
||
<p>
|
||
Alert #{{ selectedStrCase.alertId }} ·
|
||
{{ selectedStrCase.cumulativeStrCaseCountForClient }} cumulative STR case(s) for this client
|
||
@if (selectedStrCase.cumulativeStrCaseCountForClient >= 3) {
|
||
<span class="escalation-badge">Risk category auto-escalated</span>
|
||
}
|
||
</p>
|
||
</div>
|
||
<button class="fiu-btn fiu-btn-g" (click)="closeStrCaseDetail()">Close</button>
|
||
</div>
|
||
|
||
<div
|
||
class="case-outcome"
|
||
[class.case-outcome--clear]="selectedStrCase.status === 'CONCLUDED_CLEAR'"
|
||
[class.case-outcome--suspicious]="
|
||
selectedStrCase.status === 'CONCLUDED_SUSPICIOUS' || selectedStrCase.status === 'FILED'
|
||
"
|
||
>
|
||
<span [class]="amlStatusClass(selectedStrCase.status)">{{ selectedStrCase.status }}</span>
|
||
<span>{{ strCaseOutcome(selectedStrCase) }}</span>
|
||
</div>
|
||
|
||
<div class="case-form" *mifosxHasPermission="'CONCLUDE_AML_STR'">
|
||
<div class="form-row">
|
||
<label for="fiuInvestigationNotes">Investigation notes (source-of-transaction history reviewed)</label>
|
||
<textarea id="fiuInvestigationNotes" rows="3" [(ngModel)]="concludeInvestigationNotes"></textarea>
|
||
</div>
|
||
<div class="form-row">
|
||
<label for="fiuSourceOfFundsNotes">Source-of-funds notes</label>
|
||
<textarea id="fiuSourceOfFundsNotes" rows="2" [(ngModel)]="concludeSourceOfFundsNotes"></textarea>
|
||
</div>
|
||
<!-- Verification checklist — the verdict is derived, not chosen. -->
|
||
<div class="checklist">
|
||
<div class="checklist-head">
|
||
<span>Principal Officer verification — mark each point as found</span>
|
||
<span class="checklist-progress" [class.done]="allAnswered">
|
||
{{ answeredCount }} / {{ strChecklist.length }} answered
|
||
</span>
|
||
</div>
|
||
<table class="checklist-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th>Verification point</th>
|
||
<th class="ta-c col-yes">Genuine</th>
|
||
<th class="ta-c col-no">Not satisfied</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (item of strChecklist; track item.key) {
|
||
<tr
|
||
[class.row-yes]="strFindings[item.key] === true"
|
||
[class.row-no]="strFindings[item.key] === false"
|
||
[class.row-pending]="strFindings[item.key] === null"
|
||
>
|
||
<td>
|
||
<div class="chk-label">{{ item.label }}</div>
|
||
<div class="chk-hint">{{ item.hint }}</div>
|
||
</td>
|
||
<td class="ta-c">
|
||
<button
|
||
type="button"
|
||
class="chk-box chk-box--yes"
|
||
[class.on]="strFindings[item.key] === true"
|
||
[attr.aria-pressed]="strFindings[item.key] === true"
|
||
[attr.aria-label]="'Mark ' + item.label + ' as genuine'"
|
||
(click)="setFinding(item.key, true)"
|
||
>
|
||
<fa-icon icon="check-circle"></fa-icon>
|
||
</button>
|
||
</td>
|
||
<td class="ta-c">
|
||
<button
|
||
type="button"
|
||
class="chk-box chk-box--no"
|
||
[class.on]="strFindings[item.key] === false"
|
||
[attr.aria-pressed]="strFindings[item.key] === false"
|
||
[attr.aria-label]="'Mark ' + item.label + ' as not satisfied'"
|
||
(click)="setFinding(item.key, false)"
|
||
>
|
||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Derived verdict -->
|
||
<div
|
||
class="verdict"
|
||
[class.verdict--clear]="allAnswered && derivedConclusion === 'NOT_SUSPICIOUS'"
|
||
[class.verdict--suspicious]="allAnswered && derivedConclusion === 'SUSPICIOUS'"
|
||
>
|
||
@if (!allAnswered) {
|
||
<fa-icon icon="info-circle"></fa-icon>
|
||
<span>
|
||
Answer every verification point to determine the outcome —
|
||
{{ strChecklist.length - answeredCount }} still pending.
|
||
</span>
|
||
} @else if (derivedConclusion === 'NOT_SUSPICIOUS') {
|
||
<fa-icon icon="check-circle"></fa-icon>
|
||
<span>
|
||
<strong>Not suspicious.</strong> All {{ strChecklist.length }} points found genuine — the case will be
|
||
closed and nothing filed with FIU-IND.
|
||
</span>
|
||
} @else {
|
||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||
<span>
|
||
<strong>Suspicious — STR must be filed.</strong>
|
||
{{ failedFindings.length }} of {{ strChecklist.length }} points not satisfied:
|
||
{{ failedFindings[0].label }}
|
||
@if (failedFindings.length > 1) {
|
||
<span> and {{ failedFindings.length - 1 }} more</span>
|
||
}
|
||
.
|
||
</span>
|
||
}
|
||
</div>
|
||
|
||
<button class="fiu-btn fiu-btn-xml" [disabled]="!allAnswered" (click)="concludeStrCase()">
|
||
Conclude as
|
||
{{ allAnswered ? (derivedConclusion === 'SUSPICIOUS' ? 'Suspicious' : 'Not Suspicious') : '…' }}
|
||
</button>
|
||
|
||
@if (selectedStrCase.status === 'CONCLUDED_SUSPICIOUS') {
|
||
<div class="file-str-row">
|
||
<label for="fiuFileReferenceNo">FIU-IND acknowledgement / reference number</label>
|
||
<input id="fiuFileReferenceNo" class="fiu-inp" type="text" [(ngModel)]="fileReferenceNo" />
|
||
<button class="fiu-btn fiu-btn-xml" (click)="fileStrCase()">Mark STR Filed</button>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- FINnet export template (separate concern — STR XML file-format generation) -->
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>FINnet STR XML Export Template</h3>
|
||
<p>Detected by AML rules engine + branch compliance officers; filed within 7 days</p>
|
||
</div>
|
||
<button class="fiu-btn fiu-btn-xml" (click)="exportStrXml()">
|
||
<fa-icon icon="file-alt"></fa-icon> Download STR XML
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- CUSTOMER CARDS -->
|
||
@if (activeTab === 'customers') {
|
||
<div class="fiu-section">
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>Customer Cards — KC1 (Individuals) & KC2 (Entities)</h3>
|
||
<p>Customers attached to reports filed in this cycle</p>
|
||
</div>
|
||
<div class="fiu-action-group">
|
||
<button class="fiu-btn fiu-btn-csv" (click)="exportKc1()"><fa-icon icon="file-csv"></fa-icon> KC1.csv</button>
|
||
<button class="fiu-btn fiu-btn-csv" (click)="exportKc2()"><fa-icon icon="file-csv"></fa-icon> KC2.csv</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="fiu-tbl-wrap">
|
||
<div class="fiu-tbl-h">KC1 — Individuals</div>
|
||
<table class="fiu-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th class="ta-c">Ref</th>
|
||
<th>UCIC</th>
|
||
<th>Name</th>
|
||
<th class="ta-c">Gender</th>
|
||
<th>DOB</th>
|
||
<th>PAN</th>
|
||
<th>Mobile</th>
|
||
<th>City</th>
|
||
<th class="ta-r">Annual Income</th>
|
||
<th class="ta-c">Risk</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (k of kc1; track k.ucic) {
|
||
<tr>
|
||
<td class="ta-c">{{ k.reportRefNo }}</td>
|
||
<td class="mono">{{ k.ucic }}</td>
|
||
<td>{{ k.firstName }} {{ k.middleName }} {{ k.lastName }}</td>
|
||
<td class="ta-c">{{ k.gender }}</td>
|
||
<td>{{ k.dob }}</td>
|
||
<td class="mono">{{ k.pan }}</td>
|
||
<td class="mono">{{ k.mobileNumber }}</td>
|
||
<td>{{ k.city }}, {{ k.state }}</td>
|
||
<td class="ta-r mono">₹{{ k.annualIncome | number }}</td>
|
||
<td class="ta-c">
|
||
<span class="risk-tag" [class]="'rk-tag-' + k.riskLevel.toLowerCase()">{{ k.riskLevel }}</span>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="fiu-tbl-wrap">
|
||
<div class="fiu-tbl-h">KC2 — Entities</div>
|
||
<table class="fiu-tbl">
|
||
<thead>
|
||
<tr>
|
||
<th class="ta-c">Ref</th>
|
||
<th>UCIC</th>
|
||
<th>Entity Name</th>
|
||
<th>PAN</th>
|
||
<th>GSTIN</th>
|
||
<th>City</th>
|
||
<th>Type</th>
|
||
<th>Owner (if Sole Prop)</th>
|
||
<th class="ta-c">Risk</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (k of kc2; track k.ucic) {
|
||
<tr>
|
||
<td class="ta-c">{{ k.reportRefNo }}</td>
|
||
<td class="mono">{{ k.ucic }}</td>
|
||
<td>{{ k.entityName }}</td>
|
||
<td class="mono">{{ k.panNumber }}</td>
|
||
<td class="mono">{{ k.gstin }}</td>
|
||
<td>{{ k.city }}, {{ k.state }}</td>
|
||
<td>{{ k.customerType }}</td>
|
||
<td>{{ k.isSoleProprietor ? k.ownerName : '—' }}</td>
|
||
<td class="ta-c">
|
||
<span class="risk-tag" [class]="'rk-tag-' + k.riskLevel.toLowerCase()">{{ k.riskLevel }}</span>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- FINNET FILES -->
|
||
@if (activeTab === 'files') {
|
||
<div class="fiu-section">
|
||
<div class="fiu-form-meta">
|
||
<div class="fiu-form-title">
|
||
<h3>FINnet 2.0 Submission Files</h3>
|
||
<p>
|
||
6 CSV files in FIU-IND prescribed schema, plus STR XML. Counts reflect any filters set on the CTR and STR
|
||
tabs, so every file in the bundle covers the same transactions.
|
||
</p>
|
||
</div>
|
||
<button class="fiu-btn fiu-btn-xls" (click)="exportAll()">
|
||
<fa-icon icon="file-csv"></fa-icon> Download All 7
|
||
</button>
|
||
</div>
|
||
|
||
<div class="files-grid">
|
||
<div class="file-card">
|
||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>TC1.csv</div>
|
||
<p>ATM / card transaction template (header only — no card txns this cycle)</p>
|
||
<button class="fiu-btn" (click)="exportTc1()"><fa-icon icon="file-csv"></fa-icon> Download</button>
|
||
</div>
|
||
<div class="file-card">
|
||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>TC2.csv</div>
|
||
<p>Branch / cash transactions ({{ filteredCtr.length }} rows)</p>
|
||
<button class="fiu-btn fiu-btn-csv" (click)="exportTc2()">
|
||
<fa-icon icon="file-csv"></fa-icon> Download
|
||
</button>
|
||
</div>
|
||
<div class="file-card">
|
||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>KC1.csv</div>
|
||
<p>Individual customer cards ({{ kc1.length }} rows)</p>
|
||
<button class="fiu-btn fiu-btn-csv" (click)="exportKc1()">
|
||
<fa-icon icon="file-csv"></fa-icon> Download
|
||
</button>
|
||
</div>
|
||
<div class="file-card">
|
||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>KC2.csv</div>
|
||
<p>Entity customer cards ({{ kc2.length }} rows)</p>
|
||
<button class="fiu-btn fiu-btn-csv" (click)="exportKc2()">
|
||
<fa-icon icon="file-csv"></fa-icon> Download
|
||
</button>
|
||
</div>
|
||
<div class="file-card">
|
||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>Account_Detail.csv</div>
|
||
<p>Per-account 12-month aggregates derived from CTR list</p>
|
||
<button class="fiu-btn fiu-btn-csv" (click)="exportAccountDetail()">
|
||
<fa-icon icon="file-csv"></fa-icon> Download
|
||
</button>
|
||
</div>
|
||
<div class="file-card">
|
||
<div class="file-card-h"><fa-icon icon="file-csv"></fa-icon>Account_Person_Relation.csv</div>
|
||
<p>Account ↔ customer relationship mapping</p>
|
||
<button class="fiu-btn fiu-btn-csv" (click)="exportAccountPersonRelation()">
|
||
<fa-icon icon="file-csv"></fa-icon> Download
|
||
</button>
|
||
</div>
|
||
<div class="file-card file-card--xml">
|
||
<div class="file-card-h"><fa-icon icon="file-alt"></fa-icon>STR.xml</div>
|
||
<p>Suspicious Transaction Report ({{ strSuspects.length }} suspects — cases concluded clean are excluded)</p>
|
||
<button class="fiu-btn fiu-btn-xml" (click)="exportStrXml()">
|
||
<fa-icon icon="file-alt"></fa-icon> Download XML
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="fiu-note">
|
||
<fa-icon icon="info-circle"></fa-icon>
|
||
<span
|
||
><strong>Note:</strong> Files are emitted in FIU-IND FINnet 2.0 CSV schema. Submit via the FINnet portal under
|
||
{{ bank.principalOfficer }}'s digital signature. CTR is due by the 15th of the following month; STR within 7
|
||
days of detection.</span
|
||
>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- TOAST -->
|
||
<div class="fiu-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|
||
</div>
|