feat(fiu): add Head Office / Branch-wise scoping; remove reconciliation nav dots

FIU: adds a branch selector (Head Office sees all/drills into one branch,
a branch-level login is locked to its own branch) scoping the CTR/STR tabs,
dashboard KPIs and header. A FIU_BRANCH_CODE_TO_CANONICAL map bridges the
module's internal FINnet branch codes to the app-wide BranchMaster codes.
Live backend-driven AML alerts/cases are unchanged (no branch field to scope by).

Shell: removes the "working nav" green-dot indicator from the Reconciliation
mega-menu's 17 report links per request.
This commit is contained in:
2026-08-02 16:06:03 +05:30
parent be6103e0b3
commit d7f5bfe5f5
4 changed files with 133 additions and 26 deletions
+17 -17
View File
@@ -1400,27 +1400,27 @@
<!-- Core / Books --> <!-- Core / Books -->
<div class="deposit-menu-group"> <div class="deposit-menu-group">
<div class="deposit-group-title">Core &amp; Books</div> <div class="deposit-group-title">Core &amp; Books</div>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/voucher"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/voucher">
<fa-icon icon="receipt" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="receipt" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Voucher Reconciliation Voucher Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/sundry-dr-cr"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/sundry-dr-cr">
<fa-icon icon="random" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="random" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Sundry Dr / Cr Reconciliation Sundry Dr / Cr Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/pnl-impact"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/pnl-impact">
<fa-icon icon="chart-line" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="chart-line" class="menu-item-icon" style="color: #0d9488"></fa-icon>
P&amp;L Impact Reconciliation P&amp;L Impact Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/gl-reconciliation"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/gl-reconciliation">
<fa-icon icon="book-open" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="book-open" class="menu-item-icon" style="color: #0d9488"></fa-icon>
GL Reconciliation GL Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/fixed-assets"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/fixed-assets">
<fa-icon icon="cube" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="cube" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Fixed Assets Reconciliation Fixed Assets Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/inter-company"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/inter-company">
<fa-icon icon="sitemap" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="sitemap" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Inter-Company Reconciliation Inter-Company Reconciliation
</button> </button>
@@ -1429,19 +1429,19 @@
<!-- Bank & Clearing --> <!-- Bank & Clearing -->
<div class="deposit-menu-group"> <div class="deposit-menu-group">
<div class="deposit-group-title">Bank &amp; Clearing</div> <div class="deposit-group-title">Bank &amp; Clearing</div>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/bank-reco"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/bank-reco">
<fa-icon icon="university" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="university" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Bank Reconciliation Bank Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/api-bank-reco"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/api-bank-reco">
<fa-icon icon="code-branch" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="code-branch" class="menu-item-icon" style="color: #0d9488"></fa-icon>
API Based Bank Reconciliation API Based Bank Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/neft-rtgs"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/neft-rtgs">
<fa-icon icon="exchange" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="exchange" class="menu-item-icon" style="color: #0d9488"></fa-icon>
NEFT / RTGS Reconciliation NEFT / RTGS Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/clearing-reco"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/clearing-reco">
<fa-icon icon="hand-holding-usd" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="hand-holding-usd" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Clearing Reconciliation Clearing Reconciliation
</button> </button>
@@ -1450,15 +1450,15 @@
<!-- Digital / Payments --> <!-- Digital / Payments -->
<div class="deposit-menu-group"> <div class="deposit-menu-group">
<div class="deposit-group-title">Digital &amp; Payments</div> <div class="deposit-group-title">Digital &amp; Payments</div>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/udir"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/udir">
<fa-icon icon="mobile-alt" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="mobile-alt" class="menu-item-icon" style="color: #0d9488"></fa-icon>
UDIR (IMPS / UPI / ATM) UDIR (IMPS / UPI / ATM)
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/three-way-digital"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/three-way-digital">
<fa-icon icon="project-diagram" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="project-diagram" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Three-way Digital Reconciliation Three-way Digital Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/nach-reco"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/nach-reco">
<fa-icon icon="sync-alt" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="sync-alt" class="menu-item-icon" style="color: #0d9488"></fa-icon>
NACH Reconciliation NACH Reconciliation
</button> </button>
@@ -1467,11 +1467,11 @@
<!-- Instruments & Members --> <!-- Instruments & Members -->
<div class="deposit-menu-group"> <div class="deposit-menu-group">
<div class="deposit-group-title">Instruments &amp; Members</div> <div class="deposit-group-title">Instruments &amp; Members</div>
<button mat-menu-item [routerLink]="['/sponsor-dd-reconciliation']" tabindex="0" class="menu-item-live"> <button mat-menu-item [routerLink]="['/sponsor-dd-reconciliation']" tabindex="0">
<fa-icon icon="money-check-alt" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="money-check-alt" class="menu-item-icon" style="color: #0d9488"></fa-icon>
DD / Pay Order Reconciliation DD / Pay Order Reconciliation
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/share-member"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/share-member">
<fa-icon icon="coins" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="coins" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Share Member Reconciliation Share Member Reconciliation
</button> </button>
@@ -1480,11 +1480,11 @@
<!-- Compliance & Financials --> <!-- Compliance & Financials -->
<div class="deposit-menu-group"> <div class="deposit-menu-group">
<div class="deposit-group-title">Compliance &amp; Financials</div> <div class="deposit-group-title">Compliance &amp; Financials</div>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/invoice-payment"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/invoice-payment">
<fa-icon icon="file-invoice" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="file-invoice" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Invoice Payment Invoice Payment
</button> </button>
<button mat-menu-item tabindex="0" class="menu-item-live" routerLink="/reconciliation/tax-reco"> <button mat-menu-item tabindex="0" routerLink="/reconciliation/tax-reco">
<fa-icon icon="percentage" class="menu-item-icon" style="color: #0d9488"></fa-icon> <fa-icon icon="percentage" class="menu-item-icon" style="color: #0d9488"></fa-icon>
Tax Reconciliation Tax Reconciliation
</button> </button>
+23
View File
@@ -6,6 +6,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
import { BRANCHES, BranchMaster } from '../npa/npa-data';
export { BRANCHES, BranchMaster };
/** /**
* Sample FIU-IND data — schema mirrors the FINnet 2.0 CSV submission templates * Sample FIU-IND data — schema mirrors the FINnet 2.0 CSV submission templates
* (TC1, TC2, KC1, KC2, Account_Detail, Account_Person_Relation). * (TC1, TC2, KC1, KC2, Account_Detail, Account_Person_Relation).
@@ -155,6 +159,25 @@ export const FIU_BANK = {
cbwtrThreshold: 500000 // CBWTR threshold ₹5 L cbwtrThreshold: 500000 // CBWTR threshold ₹5 L
}; };
/**
* CTR/STR records below use the bank's own internal numeric branch codes (as FINnet's TC2
* schema expects), not the app-wide BranchMaster codes (BR01, BR02, ...) every other module
* scopes Head-Office-vs-branch access by. This maps one to the other so a branch-level login's
* officeName can be matched consistently, the same way NPA/Reconciliation/Loan Origination do.
*/
export const FIU_BRANCH_CODE_TO_CANONICAL: Record<string, string> = {
'1': 'BR01',
'2': 'BR02',
'3': 'BR03',
'4': 'BR04',
'5': 'BR05',
'6': 'BR06',
'8': 'BR07',
'9': 'BR08',
'10': 'BR02',
'12': 'BR04'
};
/** /**
* ── CTR transactions (FINnet TC2-style) ── * ── CTR transactions (FINnet TC2-style) ──
* Only CASH transactions that breach the RBI/PMLA reporting limit of ₹10,00,000 * Only CASH transactions that breach the RBI/PMLA reporting limit of ₹10,00,000
+20 -2
View File
@@ -14,12 +14,25 @@
<div> <div>
<h2 class="fiu-head-t">Financial Intelligence Unit – India (FIU-IND)</h2> <h2 class="fiu-head-t">Financial Intelligence Unit – India (FIU-IND)</h2>
<div class="fiu-head-s"> <div class="fiu-head-s">
{{ bank.name }} &middot; {{ bank.branch }} &middot; Reporting Month <strong>{{ bank.reportingMonth }}</strong> {{ bank.name }} &middot; {{ scopeName }} &middot; Reporting Month <strong>{{ bank.reportingMonth }}</strong>
&middot; PMLA 2002 / FINnet 2.0 &middot; PMLA 2002 / FINnet 2.0
</div> </div>
</div> </div>
</div> </div>
<div class="fiu-head-meta"> <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>
<div> <div>
<span class="fiu-meta-l">Principal Officer</span><span class="fiu-meta-v">{{ bank.principalOfficer }}</span> <span class="fiu-meta-l">Principal Officer</span><span class="fiu-meta-v">{{ bank.principalOfficer }}</span>
</div> </div>
@@ -339,7 +352,9 @@
<td> <td>
{{ t.instrumentType }} <span class="muted">{{ t.instrumentId }}</span> {{ t.instrumentType }} <span class="muted">{{ t.instrumentId }}</span>
</td> </td>
<td class="mono">{{ t.branchCode }}</td> <td class="mono">
{{ t.branchCode }} <span class="muted">{{ branchNameFor(t.branchCode) }}</span>
</td>
<td class="ta-c"> <td class="ta-c">
<span class="rel-pill" [class.non-cust]="t.relationshipFlag === 'Non-Customer'">{{ <span class="rel-pill" [class.non-cust]="t.relationshipFlag === 'Non-Customer'">{{
t.relationshipFlag t.relationshipFlag
@@ -779,6 +794,9 @@
<div> <div>
<label>Instrument</label><span>{{ s.instrumentType }} {{ s.instrumentId }}</span> <label>Instrument</label><span>{{ s.instrumentType }} {{ s.instrumentId }}</span>
</div> </div>
<div>
<label>Branch</label><span class="mono">{{ s.branchCode }} {{ branchNameFor(s.branchCode) }}</span>
</div>
</div> </div>
<div class="str-narrative"> <div class="str-narrative">
<label>Narrative</label> <label>Narrative</label>
+73 -7
View File
@@ -13,9 +13,12 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { AmlService } from 'app/aml/aml.service'; import { AmlService } from 'app/aml/aml.service';
import { AuthenticationService } from 'app/core/authentication/authentication.service'; import { AuthenticationService } from 'app/core/authentication/authentication.service';
import { import {
BRANCHES,
BranchMaster,
CtrTransaction, CtrTransaction,
CTR_TXNS, CTR_TXNS,
FIU_BANK, FIU_BANK,
FIU_BRANCH_CODE_TO_CANONICAL,
KC1_INDIVIDUALS, KC1_INDIVIDUALS,
KC2_ENTITIES, KC2_ENTITIES,
KycEntity, KycEntity,
@@ -51,6 +54,19 @@ export class FiuComponent implements OnInit {
kc1: KycIndividual[] = KC1_INDIVIDUALS; kc1: KycIndividual[] = KC1_INDIVIDUALS;
kc2: KycEntity[] = KC2_ENTITIES; kc2: KycEntity[] = KC2_ENTITIES;
// -------------------- Head Office vs Branch scope --------------------
// CTR/STR filing itself is a bank-wide Principal-Officer function (one filing for the whole
// bank, not per branch), but the underlying cash transactions a branch's own staff work
// (evidence capture, referral) are still branch-attributable — scoped the same way every other
// report in the app splits Head Office (sees everything, can drill into one branch) vs a
// branch-level login (locked to its own branch).
readonly branches = BRANCHES;
isHeadOffice = false;
currentOfficeName = 'Branch Office';
private ownBranchCode: string | null = null;
/** '' = All Branches (Head Office consolidated view); otherwise a BranchMaster.code. */
branchFilter = '';
/** CTR filters */ /** CTR filters */
ctrFilterMode: '' | 'Deposit' | 'Withdrawal' = ''; ctrFilterMode: '' | 'Deposit' | 'Withdrawal' = '';
ctrFilterAccount = ''; ctrFilterAccount = '';
@@ -223,6 +239,18 @@ export class FiuComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
const creds = this.authenticationService.getCredentials();
this.isHeadOffice = this.authenticationService.isHeadOfficeUser();
this.currentOfficeName = creds?.officeName || 'Branch Office';
if (this.isHeadOffice) {
this.branchFilter = '';
} else {
this.ownBranchCode =
this.branches.find((b: BranchMaster) => this.currentOfficeName.toLowerCase().includes(b.name.toLowerCase()))
?.code || null;
this.branchFilter = this.ownBranchCode || this.branches[0].code;
}
this.loadAmlAlerts(); this.loadAmlAlerts();
this.loadAmlCtrReports(); this.loadAmlCtrReports();
this.loadAmlStrCases(); this.loadAmlStrCases();
@@ -614,21 +642,21 @@ export class FiuComponent implements OnInit {
// -------------------- KPIs -------------------- // -------------------- KPIs --------------------
get totalReports(): number { get totalReports(): number {
return new Set(this.ctrTxns.map((t) => t.reportRefNo)).size; return new Set(this.scopedCtrTxns.map((t) => t.reportRefNo)).size;
} }
get totalCtrTxns(): number { get totalCtrTxns(): number {
return this.ctrTxns.length; return this.scopedCtrTxns.length;
} }
get totalCtrAmount(): number { get totalCtrAmount(): number {
return this.ctrTxns.reduce((s, t) => s + t.txnAmount, 0); return this.scopedCtrTxns.reduce((s, t) => s + t.txnAmount, 0);
} }
get pendingStr(): number { get pendingStr(): number {
return this.strTxns.filter( return this.scopedStrTxns.filter(
(s) => s.status === 'draft' || s.status === 'po-review' || s.status === 'awaiting-filing' (s) => s.status === 'draft' || s.status === 'po-review' || s.status === 'awaiting-filing'
).length; ).length;
} }
get filedStr(): number { get filedStr(): number {
return this.strTxns.filter((s) => s.status === 'filed').length; return this.scopedStrTxns.filter((s) => s.status === 'filed').length;
} }
// ── Live STR case counts (backend `amlStrCases`) — these are the cases the // ── Live STR case counts (backend `amlStrCases`) — these are the cases the
@@ -671,10 +699,48 @@ export class FiuComponent implements OnInit {
return this.amlAlerts.filter((a) => !ctrRuleCodes.includes(a.ruleCode)); return this.amlAlerts.filter((a) => !ctrRuleCodes.includes(a.ruleCode));
} }
// -------------------- Head Office vs Branch scope --------------------
/** Maps a FINnet-style internal branch code to the app-wide canonical branch code. */
private canonicalBranchOf(branchCode: string): string | undefined {
return FIU_BRANCH_CODE_TO_CANONICAL[branchCode];
}
/** Branch name for a row's raw FINnet branch code, for display alongside the code itself. */
branchNameFor(branchCode: string): string {
const canonical = this.canonicalBranchOf(branchCode);
return this.branches.find((b) => b.code === canonical)?.name || '';
}
setBranch(code: string): void {
this.branchFilter = code;
}
/** Name of the branch/scope currently being viewed, for display in the header. */
get scopeName(): string {
if (!this.isHeadOffice) {
return this.currentOfficeName;
}
return this.branchFilter
? this.branches.find((b) => b.code === this.branchFilter)?.name || ''
: 'All Branches (Consolidated)';
}
get scopedCtrTxns(): CtrTransaction[] {
return this.branchFilter
? this.ctrTxns.filter((t) => this.canonicalBranchOf(t.branchCode) === this.branchFilter)
: this.ctrTxns;
}
get scopedStrTxns(): StrTransaction[] {
return this.branchFilter
? this.strTxns.filter((s) => this.canonicalBranchOf(s.branchCode) === this.branchFilter)
: this.strTxns;
}
// -------------------- Filtered lists -------------------- // -------------------- Filtered lists --------------------
get filteredCtr(): CtrTransaction[] { get filteredCtr(): CtrTransaction[] {
const acc = this.ctrFilterAccount.trim(); const acc = this.ctrFilterAccount.trim();
return this.ctrTxns.filter((t) => { return this.scopedCtrTxns.filter((t) => {
if (this.ctrFilterMode && t.depositOrWithdrawal !== this.ctrFilterMode) return false; if (this.ctrFilterMode && t.depositOrWithdrawal !== this.ctrFilterMode) return false;
if (acc && !t.accountNumber.includes(acc)) return false; if (acc && !t.accountNumber.includes(acc)) return false;
if (this.ctrFilterMinAmount > 0 && t.txnAmount < this.ctrFilterMinAmount) return false; if (this.ctrFilterMinAmount > 0 && t.txnAmount < this.ctrFilterMinAmount) return false;
@@ -683,7 +749,7 @@ export class FiuComponent implements OnInit {
} }
get filteredStr(): StrTransaction[] { get filteredStr(): StrTransaction[] {
return this.strTxns.filter((s) => { return this.scopedStrTxns.filter((s) => {
if (this.strFilterStatus && s.status !== this.strFilterStatus) return false; if (this.strFilterStatus && s.status !== this.strFilterStatus) return false;
if (this.strFilterRisk && s.riskCategory !== this.strFilterRisk) return false; if (this.strFilterRisk && s.riskCategory !== this.strFilterRisk) return false;
return true; return true;