KYC management: stage-wise notices, pagination, RD base chart
This commit is contained in:
+232
-140
@@ -223,6 +223,15 @@
|
||||
<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 (ctrPendingCount > 0) {
|
||||
<div class="fiu-note">
|
||||
<fa-icon icon="info-circle"></fa-icon>
|
||||
{{ ctrPendingCount }} cash transaction{{ ctrPendingCount > 1 ? 's' : '' }} of ₹10,00,000 or more
|
||||
{{ ctrPendingCount > 1 ? 'are' : 'is' }} listed below automatically as soon as
|
||||
{{ ctrPendingCount > 1 ? 'they are' : 'it is' }} posted.
|
||||
{{ ctrPendingCount > 1 ? 'They are' : 'It is' }} not in a filed report yet — pick the month under “Generate
|
||||
CTR” and run it to include {{ ctrPendingCount > 1 ? 'them' : 'it' }}.
|
||||
</div>
|
||||
} @else if (!ctrTxns.length) {
|
||||
<div class="fiu-note fiu-note--warn">
|
||||
<fa-icon icon="exclamation-triangle"></fa-icon>
|
||||
@@ -342,27 +351,103 @@
|
||||
<th class="ta-c">Records</th>
|
||||
<th>Status</th>
|
||||
<th>Submitted On</th>
|
||||
<th class="ta-c">Customers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (r of amlCtrReports; track r.id) {
|
||||
<tr>
|
||||
<tr style="cursor: pointer" (click)="openCtrReport(r)">
|
||||
<td>{{ r.reportMonth }}</td>
|
||||
<td>{{ r.generatedOn | date: 'medium' }}</td>
|
||||
<td>{{ r.generatedOn ? (asDate(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>
|
||||
<td>{{ r.submittedOn ? (asDate(r.submittedOn) | date: 'medium') : '—' }}</td>
|
||||
<td class="ta-c" (click)="$event.stopPropagation()">
|
||||
<button class="fiu-btn fiu-btn-g" (click)="openCtrReport(r)">View</button>
|
||||
<button class="fiu-btn fiu-btn-g" (click)="exportCtrReport(r)">Excel</button>
|
||||
</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="5" class="fiu-empty">No CTR reports generated yet.</td>
|
||||
<td colspan="6" class="fiu-empty">No CTR reports generated yet.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- The customers and transactions one CTR report names. Opened from a row above; declared here so it keeps this
|
||||
component's state and styles. -->
|
||||
<ng-template #ctrReportDialog>
|
||||
<div class="fiu-dialog">
|
||||
@if (ctrReportView; as report) {
|
||||
<div class="fiu-form-meta">
|
||||
<div class="fiu-form-title">
|
||||
<h3>CTR report — {{ report.reportMonth }}</h3>
|
||||
<p>
|
||||
{{ report.status }} · {{ ctrReportLines.length }} record{{ ctrReportLines.length === 1 ? '' : 's' }} ·
|
||||
total ₹{{ ctrReportTotal | number: '1.2-2' }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="fiu-action-group">
|
||||
<button
|
||||
class="fiu-btn fiu-btn-xml"
|
||||
[disabled]="ctrReportLinesLoading"
|
||||
(click)="exportCtrReport(report)"
|
||||
>
|
||||
Export Excel
|
||||
</button>
|
||||
<button class="fiu-btn fiu-btn-g" (click)="closeCtrReport()">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (ctrReportLinesLoading) {
|
||||
<div class="fiu-note">Loading the customers in this report…</div>
|
||||
} @else {
|
||||
<div class="fiu-tbl-wrap">
|
||||
<table class="fiu-tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer</th>
|
||||
<th>Customer ID / CKYC</th>
|
||||
<th>Account</th>
|
||||
<th>Branch</th>
|
||||
<th>Date</th>
|
||||
<th>Direction</th>
|
||||
<th>Type</th>
|
||||
<th class="ta-r">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (l of ctrReportLines; track $index) {
|
||||
<tr>
|
||||
<td>
|
||||
{{ l.clientName }}
|
||||
<div style="font-size: 11px; color: var(--f-g500)">
|
||||
{{ l.individual === false ? 'Entity' : 'Individual' }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="mono">{{ l.ckycNumber || l.clientExternalId || '—' }}</td>
|
||||
<td class="mono">{{ l.accountNo || '—' }}</td>
|
||||
<td>{{ l.branchName || '—' }}</td>
|
||||
<td>{{ l.transactionDate ? (asDate(l.transactionDate) | date: 'dd MMM yyyy') : '—' }}</td>
|
||||
<td>{{ l.direction || '—' }}</td>
|
||||
<td>{{ l.connectedGroup ? 'Connected group (month total)' : 'Single transaction' }}</td>
|
||||
<td class="ta-r">₹{{ l.amount | number: '1.2-2' }}</td>
|
||||
</tr>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="8" class="fiu-empty">This report names no customers.</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -581,146 +666,153 @@
|
||||
</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>
|
||||
<!-- The case investigation. Opened as a popup by selectStrCase(); declared here so it keeps this component's state. -->
|
||||
<ng-template #strCaseDialog>
|
||||
<div class="fiu-dialog">
|
||||
@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>
|
||||
}
|
||||
</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>
|
||||
}
|
||||
</ng-template>
|
||||
|
||||
<!-- FINnet export template (separate concern — STR XML file-format generation) -->
|
||||
<div class="fiu-form-meta">
|
||||
|
||||
Reference in New Issue
Block a user