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
+73 -7
View File
@@ -13,9 +13,12 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { AmlService } from 'app/aml/aml.service';
import { AuthenticationService } from 'app/core/authentication/authentication.service';
import {
BRANCHES,
BranchMaster,
CtrTransaction,
CTR_TXNS,
FIU_BANK,
FIU_BRANCH_CODE_TO_CANONICAL,
KC1_INDIVIDUALS,
KC2_ENTITIES,
KycEntity,
@@ -51,6 +54,19 @@ export class FiuComponent implements OnInit {
kc1: KycIndividual[] = KC1_INDIVIDUALS;
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 */
ctrFilterMode: '' | 'Deposit' | 'Withdrawal' = '';
ctrFilterAccount = '';
@@ -223,6 +239,18 @@ export class FiuComponent implements OnInit {
}
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.loadAmlCtrReports();
this.loadAmlStrCases();
@@ -614,21 +642,21 @@ export class FiuComponent implements OnInit {
// -------------------- KPIs --------------------
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 {
return this.ctrTxns.length;
return this.scopedCtrTxns.length;
}
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 {
return this.strTxns.filter(
return this.scopedStrTxns.filter(
(s) => s.status === 'draft' || s.status === 'po-review' || s.status === 'awaiting-filing'
).length;
}
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
@@ -671,10 +699,48 @@ export class FiuComponent implements OnInit {
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 --------------------
get filteredCtr(): CtrTransaction[] {
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 (acc && !t.accountNumber.includes(acc)) return false;
if (this.ctrFilterMinAmount > 0 && t.txnAmount < this.ctrFilterMinAmount) return false;
@@ -683,7 +749,7 @@ export class FiuComponent implements OnInit {
}
get filteredStr(): StrTransaction[] {
return this.strTxns.filter((s) => {
return this.scopedStrTxns.filter((s) => {
if (this.strFilterStatus && s.status !== this.strFilterStatus) return false;
if (this.strFilterRisk && s.riskCategory !== this.strFilterRisk) return false;
return true;