diff --git a/src/app/core/shell/sidenav/sidenav.component.scss b/src/app/core/shell/sidenav/sidenav.component.scss
index 857c4bce5..6efbfc8eb 100644
--- a/src/app/core/shell/sidenav/sidenav.component.scss
+++ b/src/app/core/shell/sidenav/sidenav.component.scss
@@ -54,10 +54,21 @@ $hover-bg: #f1f5f9;
span {
border-radius: 2px;
- &:nth-child(1) { background: $brand; }
- &:nth-child(2) { background: #0ea5e9; }
- &:nth-child(3) { background: #10b981; }
- &:nth-child(4) { background: #cbd5e1; }
+ &:nth-child(1) {
+ background: $brand;
+ }
+
+ &:nth-child(2) {
+ background: #0ea5e9;
+ }
+
+ &:nth-child(3) {
+ background: #10b981;
+ }
+
+ &:nth-child(4) {
+ background: #cbd5e1;
+ }
}
}
diff --git a/src/app/deaf/deaf-data.ts b/src/app/deaf/deaf-data.ts
new file mode 100644
index 000000000..11cad0de2
--- /dev/null
+++ b/src/app/deaf/deaf-data.ts
@@ -0,0 +1,475 @@
+/**
+ * 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/.
+ */
+
+/**
+ * Sample DEAF data — schema and totals match the actual CIBIL/RBI return template.
+ * Bank/customer/officer/auditor names are dummy placeholders.
+ *
+ * Three RBI-prescribed reports:
+ * Form I — Monthly Return of unclaimed deposits transferred to DEAF (filed online by 15th)
+ * Form II — Same return submitted as original hardcopy
+ * Annex 4 — Refund Claim Form (when bank claims back from DEAF for paid-out customer refunds)
+ */
+
+export interface DeafFormRow {
+ /** 1=Opening, 2=Wrongly reported earlier, 3=Transferred this month, 4=Claims settled, 5=Net, 6=Total at end */
+ sl: number;
+ particulars: string;
+ /** (a) Interest Bearing Deposits */
+ ibdAccounts: number;
+ ibdAmount: number;
+ /** (b) Non-Interest Bearing Deposits */
+ nibdAccounts: number;
+ nibdAmount: number;
+ /** (c) Other Credits (Non-Interest bearing) */
+ ocAccounts: number;
+ ocAmount: number;
+ /** (d) Total = (a) + (b) + (c) */
+ totalAccounts: number;
+ totalAmount: number;
+}
+
+export interface DeafForm {
+ bankName: string;
+ bankDeafCode: string;
+ remittedThroughSponsor: 'Y' | 'N';
+ sponsorBankName: string;
+ monthYear: string;
+ dateOfTransfer: string;
+ rows: DeafFormRow[];
+ /** Authorized officer + auditor (signature block) */
+ officer: string;
+ officerDesignation: string;
+ auditor: string;
+ auditorAddress: string;
+}
+
+export interface DeafClaim {
+ slNo: number;
+ ibdPrincipal: number;
+ ibdInterest: number;
+ ibdTotal: number;
+ nibdAccounts: number;
+ nibdAmount: number;
+ ocAccounts: number;
+ ocAmount: number;
+ grandTotalAccounts: number;
+ grandTotalAmount: number;
+}
+
+export interface DeafEligibleAccount {
+ acNo: string;
+ customerName: string;
+ acType: 'SB' | 'CA' | 'FD' | 'RD' | 'BG' | 'DD' | 'PO';
+ branch: string;
+ openedDate: string;
+ lastTxnDate: string;
+ inactiveYears: number;
+ /** IBD = Interest Bearing Deposit; NIBD = Non-Interest Bearing; OC = Other Credit */
+ bucket: 'IBD' | 'NIBD' | 'OC';
+ principal: number;
+ interest: number;
+ total: number;
+ status: 'eligible-this-month' | 'transferred' | 'claim-pending' | 'refunded';
+ transferredOn?: string;
+}
+
+/** Form I — Dec 2025 cycle */
+export const FORM_I: DeafForm = {
+ bankName: 'Bharat Urban Bank Ltd, Mumbai Main Branch',
+ bankDeafCode: '1100',
+ remittedThroughSponsor: 'Y',
+ sponsorBankName: 'State Bank of India',
+ monthYear: '12/2025',
+ dateOfTransfer: '29-12-2025',
+ officer: 'Anjali Mehta',
+ officerDesignation: 'Chief Executive Officer',
+ auditor: 'Karthik Kamath',
+ auditorAddress: '12 MG Road, Worli, Mumbai 400018',
+ rows: [
+ {
+ sl: 1,
+ particulars: 'Opening balance of accounts transferred to the Fund at the beginning of the month',
+ ibdAccounts: 1208,
+ ibdAmount: 2571461.6,
+ nibdAccounts: 138,
+ nibdAmount: 177338.98,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 1346,
+ totalAmount: 2748800.58
+ },
+ {
+ sl: 2,
+ particulars: 'Accounts if any wrongly reported in past and rectified during this month',
+ ibdAccounts: 10,
+ ibdAmount: 7582.7,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 10,
+ totalAmount: 7582.7
+ },
+ {
+ sl: 3,
+ particulars:
+ 'Accounts transferred to the DEA Fund during this month (incl. inadvertently omitted in previous month)',
+ ibdAccounts: 0,
+ ibdAmount: 0,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 0,
+ totalAmount: 0
+ },
+ {
+ sl: 4,
+ particulars: 'Claims settled and refund received from the DEA Fund during this month (only the principal)',
+ ibdAccounts: 2,
+ ibdAmount: 9177.0,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 2,
+ totalAmount: 9177.0
+ },
+ {
+ sl: 5,
+ particulars: 'Net amount transferred to the Fund during the month (2 + 3 − 4)',
+ ibdAccounts: 8,
+ ibdAmount: -1594.3,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 8,
+ totalAmount: -1594.3
+ },
+ {
+ sl: 6,
+ particulars: 'Total amount with the Fund at the end of the month (1 + 5)',
+ ibdAccounts: 1216,
+ ibdAmount: 2569867.3,
+ nibdAccounts: 138,
+ nibdAmount: 177338.98,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 1354,
+ totalAmount: 2747206.28
+ }
+ ]
+};
+
+/** Form II — Jan 2026 (head office consolidated) */
+export const FORM_II: DeafForm = {
+ bankName: 'Bharat Urban Bank Ltd, Head Office',
+ bankDeafCode: '1100',
+ remittedThroughSponsor: 'Y',
+ sponsorBankName: 'State Bank of India',
+ monthYear: '01/2026',
+ dateOfTransfer: '15-01-2026',
+ officer: 'Anjali Mehta',
+ officerDesignation: 'Chief Executive Officer',
+ auditor: 'Karthik Kamath',
+ auditorAddress: '12 MG Road, Worli, Mumbai 400018',
+ rows: [
+ {
+ sl: 1,
+ particulars: 'Opening balance at the beginning of the month',
+ ibdAccounts: 699,
+ ibdAmount: 2040493.18,
+ nibdAccounts: 140,
+ nibdAmount: 187649.0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 839,
+ totalAmount: 2228142.18
+ },
+ {
+ sl: 2,
+ particulars: 'Accounts and amount inadvertently omitted in previous month return and transferred this month',
+ ibdAccounts: 0,
+ ibdAmount: 0,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 0,
+ totalAmount: 0
+ },
+ {
+ sl: 3,
+ particulars: 'Accounts and amount due and transferred to the Fund during this month',
+ ibdAccounts: 5,
+ ibdAmount: 19956.75,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 5,
+ totalAmount: 19956.75
+ },
+ {
+ sl: 4,
+ particulars: 'Accounts and amount claimed during the previous month and paid from the Fund during this month',
+ ibdAccounts: 0,
+ ibdAmount: 0,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 0,
+ totalAmount: 0
+ },
+ {
+ sl: 5,
+ particulars: 'Total amount transferred less amount paid from the Fund during the month (2 + 3 − 4)',
+ ibdAccounts: 5,
+ ibdAmount: 19956.75,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 5,
+ totalAmount: 19956.75
+ },
+ {
+ sl: 6,
+ particulars: 'Total amount with the Fund at the end of the month (1 + 5)',
+ ibdAccounts: 704,
+ ibdAmount: 2060449.93,
+ nibdAccounts: 140,
+ nibdAmount: 187649.0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ totalAccounts: 844,
+ totalAmount: 2248098.93
+ }
+ ]
+};
+
+/** Annex 4 — Refund claim form (Jan 2026) */
+export const REFUND_CLAIMS: {
+ bankName: string;
+ bankDeafCode: string;
+ currentAcWithRbi: string;
+ sponsorBank: string;
+ month: string;
+ year: string;
+ firstSignatory: string;
+ firstDesignation: string;
+ secondSignatory: string;
+ secondDesignation: string;
+ auditor: string;
+ rows: DeafClaim[];
+} = {
+ bankName: 'Bharat Urban Bank Ltd',
+ bankDeafCode: '1100',
+ currentAcWithRbi: '03010200099001',
+ sponsorBank: 'State Bank of India',
+ month: 'January',
+ year: '2026',
+ firstSignatory: 'Vikram Singh',
+ firstDesignation: 'IT Manager',
+ secondSignatory: 'Rohan Malhotra',
+ secondDesignation: 'Clerk / Cashier',
+ auditor: 'Karthik Kamath',
+ rows: [
+ {
+ slNo: 4,
+ ibdPrincipal: 5754.0,
+ ibdInterest: 457,
+ ibdTotal: 6211.0,
+ nibdAccounts: 0,
+ nibdAmount: 0,
+ ocAccounts: 0,
+ ocAmount: 0,
+ grandTotalAccounts: 4,
+ grandTotalAmount: 6211.0
+ }
+ ]
+};
+
+/** Eligible / transferred / claimed accounts list (dummy customers across multiple branches) */
+export const ELIGIBLE_ACCOUNTS: DeafEligibleAccount[] = [
+ {
+ acNo: '110001301002013',
+ customerName: 'RAVI KUMAR SHARMA',
+ acType: 'SB',
+ branch: 'Mumbai Main',
+ openedDate: '12-04-2010',
+ lastTxnDate: '15-03-2016',
+ inactiveYears: 10,
+ bucket: 'IBD',
+ principal: 4250.0,
+ interest: 312.5,
+ total: 4562.5,
+ status: 'eligible-this-month'
+ },
+ {
+ acNo: '110001301004221',
+ customerName: 'PRIYA PATEL',
+ acType: 'SB',
+ branch: 'Pune FC Road',
+ openedDate: '08-09-2009',
+ lastTxnDate: '20-01-2016',
+ inactiveYears: 10,
+ bucket: 'IBD',
+ principal: 1850.75,
+ interest: 132.0,
+ total: 1982.75,
+ status: 'eligible-this-month'
+ },
+ {
+ acNo: '110001302001145',
+ customerName: 'ARJUN DESHMUKH',
+ acType: 'FD',
+ branch: 'Nagpur Dharampeth',
+ openedDate: '01-01-2011',
+ lastTxnDate: '01-01-2016',
+ inactiveYears: 10,
+ bucket: 'IBD',
+ principal: 25000.0,
+ interest: 4180.0,
+ total: 29180.0,
+ status: 'eligible-this-month'
+ },
+ {
+ acNo: '110001301005612',
+ customerName: 'VIKRAM REDDY',
+ acType: 'SB',
+ branch: 'Bengaluru Jayanagar',
+ openedDate: '25-07-2008',
+ lastTxnDate: '12-12-2015',
+ inactiveYears: 10,
+ bucket: 'IBD',
+ principal: 8350.5,
+ interest: 654.0,
+ total: 9004.5,
+ status: 'eligible-this-month'
+ },
+ {
+ acNo: '110001303002284',
+ customerName: 'LAKSHMI IYER',
+ acType: 'RD',
+ branch: 'Bengaluru Indiranagar',
+ openedDate: '15-06-2010',
+ lastTxnDate: '15-06-2015',
+ inactiveYears: 10,
+ bucket: 'IBD',
+ principal: 12500.0,
+ interest: 1850.0,
+ total: 14350.0,
+ status: 'eligible-this-month'
+ },
+ {
+ acNo: '110001305000412',
+ customerName: 'NIRAV SHAH',
+ acType: 'DD',
+ branch: 'Ahmedabad Satellite',
+ openedDate: '03-10-2014',
+ lastTxnDate: '03-10-2014',
+ inactiveYears: 11,
+ bucket: 'NIBD',
+ principal: 3450.0,
+ interest: 0,
+ total: 3450.0,
+ status: 'eligible-this-month'
+ },
+ {
+ acNo: '110001301001780',
+ customerName: 'KAVITA VERMA',
+ acType: 'SB',
+ branch: 'Lucknow Gomti Nagar',
+ openedDate: '11-02-2008',
+ lastTxnDate: '11-09-2014',
+ inactiveYears: 11,
+ bucket: 'IBD',
+ principal: 6240.0,
+ interest: 478.0,
+ total: 6718.0,
+ status: 'transferred',
+ transferredOn: '15-12-2025'
+ },
+ {
+ acNo: '110001302002091',
+ customerName: 'KARTHIK SUBRAMANIAN',
+ acType: 'FD',
+ branch: 'Chennai Valasaravakkam',
+ openedDate: '04-08-2009',
+ lastTxnDate: '04-08-2014',
+ inactiveYears: 11,
+ bucket: 'IBD',
+ principal: 18500.0,
+ interest: 2725.0,
+ total: 21225.0,
+ status: 'transferred',
+ transferredOn: '15-11-2025'
+ },
+ {
+ acNo: '110001301008764',
+ customerName: 'SAMEER KAPOOR',
+ acType: 'SB',
+ branch: 'Delhi Greater Kailash',
+ openedDate: '17-05-2009',
+ lastTxnDate: '17-05-2014',
+ inactiveYears: 11,
+ bucket: 'IBD',
+ principal: 4587.0,
+ interest: 290.0,
+ total: 4877.0,
+ status: 'transferred',
+ transferredOn: '15-09-2024'
+ },
+ {
+ acNo: '110001302003145',
+ customerName: 'ABHISHEK BANERJEE',
+ acType: 'FD',
+ branch: 'Kolkata Salt Lake',
+ openedDate: '22-03-2010',
+ lastTxnDate: '22-03-2015',
+ inactiveYears: 10,
+ bucket: 'IBD',
+ principal: 15000.0,
+ interest: 2150.0,
+ total: 17150.0,
+ status: 'claim-pending'
+ },
+ {
+ acNo: '110001301010224',
+ customerName: 'GAURAV THAKUR',
+ acType: 'SB',
+ branch: 'Shimla Sector 4',
+ openedDate: '08-12-2008',
+ lastTxnDate: '08-06-2014',
+ inactiveYears: 11,
+ bucket: 'IBD',
+ principal: 3275.0,
+ interest: 195.0,
+ total: 3470.0,
+ status: 'claim-pending'
+ },
+ {
+ acNo: '110001302004088',
+ customerName: 'MANMOHAN SINGH GILL',
+ acType: 'FD',
+ branch: 'Amritsar Model Town',
+ openedDate: '19-09-2009',
+ lastTxnDate: '19-09-2014',
+ inactiveYears: 11,
+ bucket: 'IBD',
+ principal: 8500.0,
+ interest: 1124.0,
+ total: 9624.0,
+ status: 'refunded',
+ transferredOn: '15-04-2024'
+ }
+];
diff --git a/src/app/deaf/deaf-routing.module.ts b/src/app/deaf/deaf-routing.module.ts
new file mode 100644
index 000000000..8970cbbd9
--- /dev/null
+++ b/src/app/deaf/deaf-routing.module.ts
@@ -0,0 +1,29 @@
+/**
+ * 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/.
+ */
+
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { Route } from '../core/route/route.service';
+import { DeafComponent } from './deaf.component';
+
+const routes: Routes = [
+ Route.withShell([
+ {
+ path: 'deaf',
+ component: DeafComponent,
+ data: { title: 'DEAF', breadcrumb: 'DEAF' }
+ }
+ ])
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class DeafRoutingModule {}
diff --git a/src/app/deaf/deaf.component.html b/src/app/deaf/deaf.component.html
new file mode 100644
index 000000000..697d25b3e
--- /dev/null
+++ b/src/app/deaf/deaf.component.html
@@ -0,0 +1,416 @@
+
+
+
+
+
+
+
+
+
Depositor Education and Awareness Fund (DEAF)
+
RBI Master Direction on DEAF, 2014 · Returns & refund register
+
+
+
+
+
+
+
+
+ Dashboard
+
+
+ Form I (Transfer Return)
+
+
+ Form II (Position with Fund)
+
+
+ Annex 4 (Refund Claim)
+
+
+ Accounts Register
+
+
+
+
+ @if (activeTab === 'dashboard') {
+
+
+
+
+
+
₹{{ totalWithFund | number: '1.2-2' }}
+
Total with Fund
+
{{ totalAccountsWithFund | number }} accounts
+
+
+
+
+
+
₹{{ transferredThisMonth | number: '1.2-2' }}
+
Transferred this Month
+
{{ accountsTransferredThisMonth | number }} accounts
+
+
+
+
+
+
₹{{ refundedThisCycle | number: '1.2-2' }}
+
Claims Paid (refunded)
+
{{ refundAccountsThisCycle | number }} accounts
+
+
+
+
+
+
{{ accountsCrossing10Y | number }}
+
Crossing 10y this Cycle
+
eligible to transfer
+
+
+
+
+
+
{{ pendingClaims | number }}
+
Refund Claims Pending
+
awaiting RBI settlement
+
+
+
+
+
+
+
+
+ Form I — Online Submission
+
+
Last filed: 15 Jan 2026 · Cycle: {{ formI.monthYear }}
+
Next due: 15 Feb 2026
+
Open
+
+
+
+ Form II — Physical Hardcopy
+
+
Last filed: 15 Jan 2026 · Cycle: {{ formII.monthYear }}
+
Next due: 15 Feb 2026
+
Open
+
+
+
+ Annex 4 — Refund Claim
+
+
+ Cycle: {{ refund.month }} {{ refund.year }}
+
+
+ Pending claims: {{ pendingClaims }}
+
+
Open
+
+
+
+ }
+
+
+ @if (activeTab === 'form1' || activeTab === 'form2') {
+ @let _form = activeTab === 'form1' ? formI : formII;
+
+
+
+
+
+
+
+
+
+ Sr.No
+ Particulars
+ Interest Bearing Deposits (a)
+ Non-Interest Bearing Deposits (b)
+ Other Credits (c)
+ Total (d) = (a)+(b)+(c)
+
+
+ No. of A/c
+ Amount
+ No. of A/c
+ Amount
+ No. of A/c
+ Amount
+ No. of A/c
+ Amount
+
+
+
+ @for (r of _form.rows; track r.sl) {
+
+ {{ r.sl }}
+ {{ r.particulars }}
+ {{ r.ibdAccounts | number }}
+ {{ r.ibdAmount | number: '1.2-2' }}
+ {{ r.nibdAccounts | number }}
+ {{ r.nibdAmount | number: '1.2-2' }}
+ {{ r.ocAccounts | number }}
+ {{ r.ocAmount | number: '1.2-2' }}
+ {{ r.totalAccounts | number }}
+ {{ r.totalAmount | number: '1.2-2' }}
+
+ }
+
+
+
+
+
+
+
Authorised Officer
+
{{ _form.officer }}
+
{{ _form.officerDesignation }}
+
+
+
Concurrent Auditor
+
{{ _form.auditor }}
+
{{ _form.auditorAddress }}
+
+
+
+ }
+
+
+ @if (activeTab === 'refund') {
+
+
+
+
+
+
+
+
+
+ Sr.No
+ Interest Bearing Deposits
+ Non-Interest Bearing Deposits
+ Other Credits
+ Grand Total
+
+
+ Principal
+ Interest
+ Total
+ No. of A/c
+ Amount
+ No. of A/c
+ Amount
+ No. of A/c
+ Amount
+
+
+
+ @for (r of refund.rows; track r.slNo) {
+
+ {{ r.slNo }}
+ {{ r.ibdPrincipal | number: '1.2-2' }}
+ {{ r.ibdInterest | number: '1.2-2' }}
+ {{ r.ibdTotal | number: '1.2-2' }}
+ {{ r.nibdAccounts | number }}
+ {{ r.nibdAmount | number: '1.2-2' }}
+ {{ r.ocAccounts | number }}
+ {{ r.ocAmount | number: '1.2-2' }}
+ {{ r.grandTotalAccounts | number }}
+ {{ r.grandTotalAmount | number: '1.2-2' }}
+
+ }
+
+
+
+
+
+
+ Note: No individual customer/depositor wise details should be furnished. In case of refund
+ of part amount by the depositor whose unclaimed amount/inoperative deposit had been transferred to the Fund,
+ the bank shall claim the entire amount transferred to the Fund in respect of such depositor, along with
+ interest payable, if any.
+
+
+
+
+
First Authorised Signatory
+
{{ refund.firstSignatory }}
+
{{ refund.firstDesignation }}
+
+
+
Second Authorised Signatory
+
{{ refund.secondSignatory }}
+
{{ refund.secondDesignation }}
+
+
+
Concurrent Auditor
+
{{ refund.auditor }}
+
+
+
+ }
+
+
+ @if (activeTab === 'eligible') {
+
+
+
+
+
+ Status
+
+ All
+ Eligible (≥10y)
+ Transferred
+ Claim Pending
+ Refunded
+
+
+
+ Bucket
+
+ All
+ Interest Bearing
+ Non-Interest Bearing
+ Other Credits
+
+
+
+ Branch
+
+
+
Reset
+
+
+
+
+
+
+ A/c No
+ Customer
+ Type
+ Branch
+ Opened
+ Last Txn
+ Inactive
+ Bucket
+ Principal
+ Interest
+ Total
+ Status
+
+
+
+ @for (a of filteredAccounts; track a.acNo) {
+
+ {{ a.acNo }}
+ {{ a.customerName }}
+ {{ a.acType }}
+ {{ a.branch }}
+ {{ a.openedDate }}
+ {{ a.lastTxnDate }}
+ {{ a.inactiveYears }}y
+
+ {{ a.bucket }}
+
+ {{ a.principal | number: '1.2-2' }}
+ {{ a.interest | number: '1.2-2' }}
+ {{ a.total | number: '1.2-2' }}
+
+ {{ statusLabel(a.status) }}
+
+
+ } @empty {
+
+ No accounts match the filters.
+
+ }
+
+
+
+
+ }
+
+
+
{{ toastMsg }}
+
diff --git a/src/app/deaf/deaf.component.scss b/src/app/deaf/deaf.component.scss
new file mode 100644
index 000000000..a4009d3f8
--- /dev/null
+++ b/src/app/deaf/deaf.component.scss
@@ -0,0 +1,672 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --d-navy: #0b1e3d;
+ --d-blue: #1d4ed8;
+ --d-blue-l: #eff6ff;
+ --d-teal: #0e7e6b;
+ --d-teal-l: #e0f4f0;
+ --d-green: #15803d;
+ --d-green-l: #dcfce7;
+ --d-amber: #d97706;
+ --d-amber-l: #fef3c7;
+ --d-red: #c0392b;
+ --d-red-l: #fdecea;
+ --d-purple: #6d28d9;
+ --d-purple-l: #ede9fe;
+ --d-g50: #f8fafc;
+ --d-g100: #f1f5f9;
+ --d-g200: #e2e8f0;
+ --d-g300: #cbd5e1;
+ --d-g500: #64748b;
+ --d-g700: #334155;
+ --d-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ background: var(--d-g50);
+ color: var(--d-g900);
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+.deaf-root {
+ padding: 18px 22px;
+ max-width: 1400px;
+ margin: 0 auto;
+}
+
+/* HEADER */
+.deaf-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+ margin-bottom: 16px;
+}
+
+.deaf-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.deaf-head-ic {
+ font-size: 28px;
+ color: var(--d-purple);
+}
+
+.deaf-head-t {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--d-g900);
+}
+
+.deaf-head-s {
+ font-size: 12px;
+ color: var(--d-g500);
+}
+
+.deaf-head-meta {
+ display: flex;
+ gap: 22px;
+
+ > div {
+ display: flex;
+ flex-direction: column;
+
+ .deaf-meta-l {
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--d-g500);
+ }
+
+ .deaf-meta-v {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--d-g900);
+ }
+ }
+}
+
+/* TABS */
+.deaf-tabs {
+ display: flex;
+ gap: 4px;
+ background: #fff;
+ border: 1px solid var(--d-g200);
+ border-radius: 10px;
+ padding: 4px;
+ margin-bottom: 16px;
+ overflow-x: auto;
+}
+
+.deaf-tab {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 14px;
+ border: none;
+ background: transparent;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--d-g500);
+ cursor: pointer;
+ border-radius: 7px;
+ white-space: nowrap;
+ transition: all 0.15s;
+ font-family: inherit;
+
+ &:hover {
+ color: var(--d-g900);
+ }
+
+ &.active {
+ background: var(--d-purple);
+ color: #fff;
+ box-shadow: 0 2px 6px rgb(109 40 217 / 25%);
+ }
+}
+
+.deaf-section {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+/* KPIs */
+.deaf-kpis {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 12px;
+}
+
+.deaf-kpi {
+ background: #fff;
+ border: 1px solid var(--d-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
+ }
+}
+
+.deaf-kpi-ic {
+ width: 42px;
+ height: 42px;
+ border-radius: 11px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.deaf-kpi--blue .deaf-kpi-ic {
+ background: var(--d-blue-l);
+ color: var(--d-blue);
+}
+
+.deaf-kpi--teal .deaf-kpi-ic {
+ background: var(--d-teal-l);
+ color: var(--d-teal);
+}
+
+.deaf-kpi--green .deaf-kpi-ic {
+ background: var(--d-green-l);
+ color: var(--d-green);
+}
+
+.deaf-kpi--amber .deaf-kpi-ic {
+ background: var(--d-amber-l);
+ color: var(--d-amber);
+}
+
+.deaf-kpi--red .deaf-kpi-ic {
+ background: var(--d-red-l);
+ color: var(--d-red);
+}
+
+.deaf-kpi-n {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--d-g900);
+ line-height: 1.1;
+}
+
+.deaf-kpi-l {
+ font-size: 12px;
+ color: var(--d-g700);
+ margin-top: 2px;
+ font-weight: 500;
+}
+
+.deaf-kpi-sub {
+ font-size: 11px;
+ color: var(--d-g500);
+ margin-top: 1px;
+}
+
+/* COMPLIANCE CARDS */
+.deaf-compliance {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 12px;
+}
+
+.dc-card {
+ background: #fff;
+ border: 1px solid var(--d-g200);
+ border-radius: 12px;
+ padding: 14px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.dc-card-h {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--d-g900);
+ margin-bottom: 8px;
+}
+
+.dc-ok {
+ color: var(--d-green);
+}
+
+.dc-info {
+ color: var(--d-blue);
+}
+
+.dc-meta {
+ font-size: 12px;
+ color: var(--d-g500);
+ margin: 2px 0;
+
+ strong {
+ color: var(--d-g900);
+ font-weight: 600;
+ }
+}
+
+.dc-btn {
+ margin-top: 10px;
+ padding: 6px 12px;
+ border: 1px solid var(--d-purple);
+ background: var(--d-purple-l);
+ color: var(--d-purple);
+ border-radius: 8px;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ font-family: inherit;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+
+ &:hover {
+ background: var(--d-purple);
+ color: #fff;
+ }
+}
+
+/* FORM HEADER ROW */
+.deaf-form-meta {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 14px;
+}
+
+.deaf-form-title {
+ h3 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--d-g900);
+ }
+
+ p {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--d-g500);
+ }
+}
+
+.deaf-meta-grid {
+ background: #fff;
+ border: 1px solid var(--d-g200);
+ border-radius: 12px;
+ padding: 14px 18px;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px 24px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+
+ > div {
+ display: flex;
+ flex-direction: column;
+
+ label {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--d-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ margin-bottom: 2px;
+ }
+
+ span {
+ font-size: 13px;
+ color: var(--d-g900);
+ font-weight: 500;
+ }
+ }
+}
+
+/* BUTTONS */
+.deaf-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ border: 1px solid var(--d-g300);
+ background: #fff;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--d-g700);
+ cursor: pointer;
+ font-family: inherit;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--d-g50);
+ border-color: var(--d-g500);
+ }
+}
+
+.deaf-btn-xls {
+ background: #16a34a;
+ border-color: #16a34a;
+ color: #fff;
+
+ &:hover {
+ background: #15803d;
+ border-color: #15803d;
+ color: #fff;
+ }
+}
+
+.deaf-btn-g {
+ align-self: end;
+}
+
+/* INPUTS */
+.deaf-inp {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid var(--d-g200);
+ border-radius: 8px;
+ font-size: 13px;
+ background: #fff;
+ font-family: inherit;
+
+ &:focus {
+ outline: none;
+ border-color: var(--d-purple);
+ box-shadow: 0 0 0 3px rgb(109 40 217 / 15%);
+ }
+}
+
+/* FILTERS */
+.deaf-filters {
+ background: #fff;
+ border: 1px solid var(--d-g200);
+ border-radius: 12px;
+ padding: 14px;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr) auto;
+ gap: 12px;
+ align-items: end;
+}
+
+.deaf-filter {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ label {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--d-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+}
+
+/* TABLE */
+.deaf-tbl-wrap {
+ background: #fff;
+ border: 1px solid var(--d-g200);
+ border-radius: 12px;
+ overflow: auto;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.deaf-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 10px 12px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--d-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ background: var(--d-g50);
+ border-bottom: 1px solid var(--d-g200);
+ }
+
+ th.hd-1,
+ tr.hd-1 th {
+ background: #f0f4ff;
+ color: var(--d-blue);
+ }
+
+ td {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--d-g100);
+ font-size: 13px;
+ color: var(--d-g700);
+ vertical-align: top;
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--d-g50);
+ }
+
+ tr.amt-bold td {
+ background: #f0fdf4;
+ font-weight: 600;
+ color: var(--d-g900);
+ }
+
+ tr.amt-neg td {
+ color: var(--d-red);
+ }
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 12px;
+ letter-spacing: 0.02em;
+}
+
+.deaf-empty {
+ text-align: center;
+ padding: 30px;
+ color: var(--d-g500);
+ font-style: italic;
+}
+
+/* SIGNATURE BLOCKS */
+.deaf-sign {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 14px;
+}
+
+.deaf-sign-block {
+ background: #fff;
+ border: 1px solid var(--d-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+
+ label {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--d-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ margin-bottom: 6px;
+ display: block;
+ }
+
+ .sign-name {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--d-g900);
+ }
+
+ .sign-desg {
+ font-size: 12px;
+ color: var(--d-g500);
+ margin-top: 2px;
+ }
+}
+
+/* NOTE / INFO */
+.deaf-note {
+ background: var(--d-blue-l);
+ border: 1px solid #bfdbfe;
+ border-radius: 10px;
+ padding: 12px 16px;
+ display: flex;
+ gap: 10px;
+ font-size: 12px;
+ color: #1e3a8a;
+ line-height: 1.5;
+
+ fa-icon {
+ font-size: 14px;
+ flex-shrink: 0;
+ margin-top: 2px;
+ }
+}
+
+/* PILLS */
+.bucket-pill {
+ display: inline-block;
+ padding: 2px 9px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+
+ &.bk-ibd {
+ background: var(--d-blue-l);
+ color: var(--d-blue);
+ }
+
+ &.bk-nibd {
+ background: var(--d-teal-l);
+ color: var(--d-teal);
+ }
+
+ &.bk-oc {
+ background: var(--d-purple-l);
+ color: var(--d-purple);
+ }
+}
+
+.status-pill {
+ display: inline-block;
+ padding: 2px 10px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+
+ &.st-eligible-this-month {
+ background: var(--d-amber-l);
+ color: var(--d-amber);
+ }
+
+ &.st-transferred {
+ background: var(--d-green-l);
+ color: var(--d-green);
+ }
+
+ &.st-claim-pending {
+ background: var(--d-red-l);
+ color: var(--d-red);
+ }
+
+ &.st-refunded {
+ background: var(--d-blue-l);
+ color: var(--d-blue);
+ }
+}
+
+/* TOAST */
+.deaf-toast {
+ position: fixed;
+ bottom: 32px;
+ left: 50%;
+ transform: translateX(-50%) translateY(20px);
+ background: var(--d-navy);
+ color: #fff;
+ padding: 12px 22px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 500;
+ box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
+ opacity: 0;
+ pointer-events: none;
+ transition: all 0.25s;
+ z-index: 1500;
+
+ &.show {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+}
+
+/* RESPONSIVE */
+@media (width <= 1100px) {
+ .deaf-kpis {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .deaf-compliance {
+ grid-template-columns: 1fr;
+ }
+
+ .deaf-meta-grid {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .deaf-sign {
+ grid-template-columns: 1fr;
+ }
+
+ .deaf-filters {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+@media (width <= 720px) {
+ .deaf-kpis {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .deaf-meta-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/app/deaf/deaf.component.ts b/src/app/deaf/deaf.component.ts
new file mode 100644
index 000000000..5c647cf76
--- /dev/null
+++ b/src/app/deaf/deaf.component.ts
@@ -0,0 +1,358 @@
+/**
+ * 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/.
+ */
+
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import {
+ DeafEligibleAccount,
+ DeafForm,
+ DeafFormRow,
+ ELIGIBLE_ACCOUNTS,
+ FORM_I,
+ FORM_II,
+ REFUND_CLAIMS
+} from './deaf-data';
+
+@Component({
+ selector: 'mifosx-deaf',
+ templateUrl: './deaf.component.html',
+ styleUrls: ['./deaf.component.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ FaIconComponent,
+ FormsModule
+ ]
+})
+export class DeafComponent {
+ /** Active tab */
+ activeTab: 'dashboard' | 'form1' | 'form2' | 'refund' | 'eligible' = 'dashboard';
+
+ formI: DeafForm = FORM_I;
+ formII: DeafForm = FORM_II;
+ refund = REFUND_CLAIMS;
+ accounts: DeafEligibleAccount[] = ELIGIBLE_ACCOUNTS;
+
+ /** Eligible-list filters */
+ filterStatus: '' | DeafEligibleAccount['status'] = '';
+ filterBranch = '';
+ filterBucket: '' | DeafEligibleAccount['bucket'] = '';
+
+ /** Toast */
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: any;
+
+ // -------------------- Tabs --------------------
+ showTab(tab: 'dashboard' | 'form1' | 'form2' | 'refund' | 'eligible'): void {
+ this.activeTab = tab;
+ }
+
+ // -------------------- KPIs --------------------
+ /** Total amount currently with the DEAF Fund (closing balance from Form II row 6) */
+ get totalWithFund(): number {
+ return this.formII.rows[5]?.totalAmount || 0;
+ }
+ get totalAccountsWithFund(): number {
+ return this.formII.rows[5]?.totalAccounts || 0;
+ }
+ get transferredThisMonth(): number {
+ return this.formII.rows[2]?.totalAmount || 0;
+ }
+ get accountsTransferredThisMonth(): number {
+ return this.formII.rows[2]?.totalAccounts || 0;
+ }
+ get refundedThisCycle(): number {
+ return this.refund.rows.reduce((s, r) => s + r.grandTotalAmount, 0);
+ }
+ get refundAccountsThisCycle(): number {
+ return this.refund.rows.reduce((s, r) => s + r.grandTotalAccounts, 0);
+ }
+ get accountsCrossing10Y(): number {
+ return this.accounts.filter((a) => a.status === 'eligible-this-month').length;
+ }
+ get pendingClaims(): number {
+ return this.accounts.filter((a) => a.status === 'claim-pending').length;
+ }
+
+ // -------------------- Eligible filter --------------------
+ get filteredAccounts(): DeafEligibleAccount[] {
+ const branch = this.filterBranch.trim().toLowerCase();
+ return this.accounts.filter((a) => {
+ if (this.filterStatus && a.status !== this.filterStatus) return false;
+ if (this.filterBucket && a.bucket !== this.filterBucket) return false;
+ if (branch && !a.branch.toLowerCase().includes(branch)) return false;
+ return true;
+ });
+ }
+
+ resetFilters(): void {
+ this.filterStatus = '';
+ this.filterBranch = '';
+ this.filterBucket = '';
+ }
+
+ statusLabel(s: DeafEligibleAccount['status']): string {
+ switch (s) {
+ case 'eligible-this-month':
+ return 'Eligible';
+ case 'transferred':
+ return 'Transferred';
+ case 'claim-pending':
+ return 'Claim Pending';
+ case 'refunded':
+ return 'Refunded';
+ }
+ }
+
+ // -------------------- Excel export --------------------
+ /** Builds Form I / Form II rows as a 2-D array — same layout as the RBI return */
+ private buildFormRows(form: DeafForm, label: 'Form I' | 'Form II'): string[][] {
+ const rows: string[][] = [];
+ rows.push([
+ label,
+ 'Monthly Return of Unclaimed Deposits Transferred to DEAF Fund'
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Name of the Bank',
+ form.bankName
+ ]);
+ rows.push([
+ 'Bank DEAF Code allotted by RBI',
+ form.bankDeafCode
+ ]);
+ rows.push([
+ 'If remitted through Sponsor Bank',
+ form.remittedThroughSponsor
+ ]);
+ rows.push([
+ 'Name of the Sponsor Bank',
+ form.sponsorBankName
+ ]);
+ rows.push([
+ 'Month / Year',
+ form.monthYear
+ ]);
+ rows.push([
+ 'Date of Transfer to the Fund',
+ form.dateOfTransfer
+ ]);
+ rows.push([]);
+ rows.push(['Particulars (Amount in Rupees)']);
+ rows.push([
+ 'Sr.No',
+ 'Particulars',
+ 'IBD No. of A/c',
+ 'IBD Amount',
+ 'NIBD No. of A/c',
+ 'NIBD Amount',
+ 'OC No. of A/c',
+ 'OC Amount',
+ 'Total No. of A/c',
+ 'Total Amount'
+ ]);
+ for (const r of form.rows) {
+ rows.push([
+ r.sl.toString(),
+ r.particulars,
+ r.ibdAccounts.toString(),
+ r.ibdAmount.toFixed(2),
+ r.nibdAccounts.toString(),
+ r.nibdAmount.toFixed(2),
+ r.ocAccounts.toString(),
+ r.ocAmount.toFixed(2),
+ r.totalAccounts.toString(),
+ r.totalAmount.toFixed(2)
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ 'Authorised Officer',
+ form.officer
+ ]);
+ rows.push([
+ 'Designation',
+ form.officerDesignation
+ ]);
+ rows.push([
+ 'Concurrent Auditor',
+ form.auditor
+ ]);
+ rows.push([
+ 'Auditor Address',
+ form.auditorAddress
+ ]);
+ return rows;
+ }
+
+ private buildRefundRows(): string[][] {
+ const c = this.refund;
+ const rows: string[][] = [];
+ rows.push([
+ 'Annex 4',
+ 'Monthly Return claiming refund from DEA Fund'
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Name of the Bank',
+ c.bankName
+ ]);
+ rows.push([
+ 'Bank DEAF Code allotted by RBI',
+ c.bankDeafCode
+ ]);
+ rows.push([
+ 'Current A/c with RBI',
+ `${c.currentAcWithRbi} via ${c.sponsorBank}`
+ ]);
+ rows.push([
+ 'Month / Year',
+ `${c.month} ${c.year}`
+ ]);
+ rows.push([]);
+ rows.push(['Details of claims made during the month (Amount in Rupees)']);
+ rows.push([
+ 'Sr.No',
+ 'IBD Principal',
+ 'IBD Interest',
+ 'IBD Total',
+ 'NIBD No. of A/c',
+ 'NIBD Amount',
+ 'OC No. of A/c',
+ 'OC Amount',
+ 'Grand Total No. of A/c',
+ 'Grand Total Amount'
+ ]);
+ for (const r of c.rows) {
+ rows.push([
+ r.slNo.toString(),
+ r.ibdPrincipal.toFixed(2),
+ r.ibdInterest.toFixed(2),
+ r.ibdTotal.toFixed(2),
+ r.nibdAccounts.toString(),
+ r.nibdAmount.toFixed(2),
+ r.ocAccounts.toString(),
+ r.ocAmount.toFixed(2),
+ r.grandTotalAccounts.toString(),
+ r.grandTotalAmount.toFixed(2)
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ 'Note',
+ 'No individual customer/depositor wise details should be furnished.'
+ ]);
+ rows.push([
+ 'First Authorised Signatory',
+ `${c.firstSignatory} (${c.firstDesignation})`
+ ]);
+ rows.push([
+ 'Second Authorised Signatory',
+ `${c.secondSignatory} (${c.secondDesignation})`
+ ]);
+ rows.push([
+ 'Concurrent Auditor',
+ c.auditor
+ ]);
+ return rows;
+ }
+
+ private buildEligibleRows(): string[][] {
+ const rows: string[][] = [];
+ rows.push(['DEAF — Accounts Register']);
+ rows.push([]);
+ rows.push([
+ 'A/c No',
+ 'Customer Name',
+ 'A/c Type',
+ 'Branch',
+ 'Opened',
+ 'Last Txn',
+ 'Inactive (yrs)',
+ 'Bucket',
+ 'Principal',
+ 'Interest',
+ 'Total',
+ 'Status',
+ 'Transferred On'
+ ]);
+ for (const a of this.filteredAccounts) {
+ rows.push([
+ a.acNo,
+ a.customerName,
+ a.acType,
+ a.branch,
+ a.openedDate,
+ a.lastTxnDate,
+ a.inactiveYears.toString(),
+ a.bucket,
+ a.principal.toFixed(2),
+ a.interest.toFixed(2),
+ a.total.toFixed(2),
+ this.statusLabel(a.status),
+ a.transferredOn || ''
+ ]);
+ }
+ return rows;
+ }
+
+ exportFormI(): void {
+ this.saveTsv(this.buildFormRows(this.formI, 'Form I'), `DEAF-FormI-${this.fileTag(this.formI.monthYear)}.xls`);
+ this.toast('Form I downloaded');
+ }
+ exportFormII(): void {
+ this.saveTsv(this.buildFormRows(this.formII, 'Form II'), `DEAF-FormII-${this.fileTag(this.formII.monthYear)}.xls`);
+ this.toast('Form II downloaded');
+ }
+ exportRefund(): void {
+ this.saveTsv(this.buildRefundRows(), `DEAF-Annex4-${this.refund.month}-${this.refund.year}.xls`);
+ this.toast('Annex 4 (Refund Claim) downloaded');
+ }
+ exportEligible(): void {
+ this.saveTsv(this.buildEligibleRows(), `DEAF-Accounts-${this.dateStamp()}.xls`);
+ this.toast(`${this.filteredAccounts.length} accounts exported`);
+ }
+
+ private saveTsv(rows: string[][], filename: string): void {
+ const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
+ const blob = new Blob([tsv], { type: 'application/vnd.ms-excel;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+
+ private fileTag(monthYear: string): string {
+ return monthYear.replace('/', '-');
+ }
+
+ private dateStamp(): string {
+ const d = new Date();
+ const dd = String(d.getDate()).padStart(2, '0');
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
+ return `${dd}${mm}${d.getFullYear()}`;
+ }
+
+ /** Coerce a row's raw amounts for display — handles negatives in Form I row 5 */
+ amountClass(r: DeafFormRow): string {
+ if (r.sl === 5 && r.totalAmount < 0) return 'amt-neg';
+ if (r.sl === 6) return 'amt-bold';
+ return '';
+ }
+
+ toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) clearTimeout(this.toastTimer);
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
+ }
+}
diff --git a/src/app/deaf/deaf.module.ts b/src/app/deaf/deaf.module.ts
new file mode 100644
index 000000000..258f7f0e6
--- /dev/null
+++ b/src/app/deaf/deaf.module.ts
@@ -0,0 +1,22 @@
+/**
+ * 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/.
+ */
+
+import { NgModule } from '@angular/core';
+
+import { SharedModule } from '../shared/shared.module';
+import { DeafRoutingModule } from './deaf-routing.module';
+import { DeafComponent } from './deaf.component';
+
+@NgModule({
+ imports: [
+ SharedModule,
+ DeafRoutingModule,
+ DeafComponent
+ ]
+})
+export class DeafModule {}
diff --git a/src/app/dicgc/dicgc-data.ts b/src/app/dicgc/dicgc-data.ts
new file mode 100644
index 000000000..78566e6a9
--- /dev/null
+++ b/src/app/dicgc/dicgc-data.ts
@@ -0,0 +1,485 @@
+/**
+ * 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/.
+ */
+
+/**
+ * DICGC (Deposit Insurance and Credit Guarantee Corporation) sample data.
+ * Schema reproduces the actual report formats but bank/customer names are dummy.
+ *
+ * Two reports:
+ * Slab-wise A/C Report — accounts grouped by deposit-amount slabs
+ * (≤1L, 1L-2L, 2L-3L, >3L) for both deposits and loans
+ * SAHAY Depositor List — customer/account-wise CSV input template DICGC consumes
+ * when paying out claims after a bank failure
+ */
+
+/** Single product row in the slab-wise table */
+export interface SlabRow {
+ sl: number;
+ description: string;
+ upto1Lakh: { amount: number; accounts: number };
+ oneTo2Lakh: { amount: number; accounts: number };
+ twoTo3Lakh: { amount: number; accounts: number };
+ above3Lakh: { amount: number; accounts: number };
+ total: { amount: number; accounts: number };
+}
+
+/** SAHAY depositor input record (19 columns) */
+export interface SahayDepositor {
+ inputSerialNumber: number;
+ customerIdUcic: string;
+ accountNumber: string;
+ typeOfAccount: string;
+ natureOfAccount: string;
+ numberOfHolders: number;
+ nameFirst: string;
+ nameSecond: string;
+ nameThird: string;
+ dateOfBirthOrIncorporation: string;
+ amountOfDeposit: number;
+ insuredAmount: number;
+ loanAgainstDeposit: 'LAD' | 'LAFD' | '';
+ panNumber: string;
+ aadhaarNumber: string;
+ otherKycType: string;
+ otherKycNumber: string;
+ mobileNumber: string;
+ typeOfDeposit: string;
+ lastCustomerInducedTxnDate: string;
+}
+
+export const DICGC_BANK = {
+ name: 'Bharat Urban Bank Ltd',
+ branch: 'Head Office',
+ reportAsOn: '29-01-2026',
+ printDate: '29-01-2026 04:19 PM',
+ reportRefNo: '1290/2',
+ userCode: 'JTU',
+ insuranceLimit: 500000, // ₹5 lakh per depositor
+ premiumRatePer100: 0.12, // 12 paise per ₹100 of deposits
+ registrationNo: 'REG-CO-1290'
+};
+
+/** ── DEPOSITS — Section 1 (Credit Balance) ── */
+export const DEPOSIT_SLABS: SlabRow[] = [
+ {
+ sl: 1,
+ description: 'CURRENT DEPOSIT INDIVIDUAL A/C',
+ upto1Lakh: { amount: 4161663.56, accounts: 231 },
+ oneTo2Lakh: { amount: 1104137.57, accounts: 7 },
+ twoTo3Lakh: { amount: 953186.92, accounts: 4 },
+ above3Lakh: { amount: 8451113.76, accounts: 12 },
+ total: { amount: 14670101.81, accounts: 254 }
+ },
+ {
+ sl: 2,
+ description: 'FIX DEPOSIT DOUBLE A/C',
+ upto1Lakh: { amount: 38612382.0, accounts: 824 },
+ oneTo2Lakh: { amount: 50661835.0, accounts: 371 },
+ twoTo3Lakh: { amount: 41999156.0, accounts: 175 },
+ above3Lakh: { amount: 204955057.0, accounts: 324 },
+ total: { amount: 336228430.0, accounts: 1694 }
+ },
+ {
+ sl: 3,
+ description: 'FIXED DEPOSIT SINGLE A/C',
+ upto1Lakh: { amount: 5488157.0, accounts: 694 },
+ oneTo2Lakh: { amount: 689874.0, accounts: 6 },
+ twoTo3Lakh: { amount: 250000.0, accounts: 1 },
+ above3Lakh: { amount: 1018485.0, accounts: 2 },
+ total: { amount: 7446516.0, accounts: 703 }
+ },
+ {
+ sl: 4,
+ description: 'FIXED DEPOSIT MONTHLY',
+ upto1Lakh: { amount: 445000.0, accounts: 6 },
+ oneTo2Lakh: { amount: 400000.0, accounts: 2 },
+ twoTo3Lakh: { amount: 1400000.0, accounts: 5 },
+ above3Lakh: { amount: 14500000.0, accounts: 14 },
+ total: { amount: 16745000.0, accounts: 27 }
+ },
+ {
+ sl: 5,
+ description: 'FIXED DEPOSIT QUARTERLY',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 3250000.0, accounts: 3 },
+ total: { amount: 3250000.0, accounts: 3 }
+ },
+ {
+ sl: 6,
+ description: 'GENERAL LEDGER',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 7,
+ description: 'LINKING SHARE',
+ upto1Lakh: { amount: 11600908.0, accounts: 937 },
+ oneTo2Lakh: { amount: 8580300.0, accounts: 61 },
+ twoTo3Lakh: { amount: 3290000.0, accounts: 13 },
+ above3Lakh: { amount: 2876800.0, accounts: 7 },
+ total: { amount: 26348008.0, accounts: 1018 }
+ },
+ {
+ sl: 8,
+ description: 'MANGAL PARINAY A/C',
+ upto1Lakh: { amount: 197496.0, accounts: 2 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 197496.0, accounts: 2 }
+ },
+ {
+ sl: 9,
+ description: 'RECURRING DEPOSIT',
+ upto1Lakh: { amount: 558352.0, accounts: 22 },
+ oneTo2Lakh: { amount: 206756.0, accounts: 2 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 765108.0, accounts: 24 }
+ },
+ {
+ sl: 10,
+ description: 'SUNDRY CREDITORS A/C',
+ upto1Lakh: { amount: 46000.0, accounts: 2 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 607913.3, accounts: 1 },
+ total: { amount: 653913.3, accounts: 3 }
+ },
+ {
+ sl: 11,
+ description: 'SAVINGS DEPOSIT INDIVIDUAL A/C',
+ upto1Lakh: { amount: 37172591.62, accounts: 3954 },
+ oneTo2Lakh: { amount: 28097261.58, accounts: 199 },
+ twoTo3Lakh: { amount: 19583645.69, accounts: 84 },
+ above3Lakh: { amount: 108860739.06, accounts: 191 },
+ total: { amount: 193714237.95, accounts: 4428 }
+ }
+];
+
+/** ── LOANS — Section A (Credit Balance Loan A/c) ── */
+export const LOAN_SLABS: SlabRow[] = [
+ {
+ sl: 1,
+ description: 'CC AGAINST DEPOSIT',
+ upto1Lakh: { amount: 10214.45, accounts: 4 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 10214.45, accounts: 4 }
+ },
+ {
+ sl: 2,
+ description: 'CASH CREDIT LOAN A/C',
+ upto1Lakh: { amount: 37223.99, accounts: 5 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 257039.67, accounts: 1 },
+ above3Lakh: { amount: 3869523.24, accounts: 3 },
+ total: { amount: 4163786.9, accounts: 9 }
+ },
+ {
+ sl: 3,
+ description: 'OVER DRAFT BMC',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 4,
+ description: 'LOAN AGAINST GODOWN PLEDGE',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 5,
+ description: 'KISHAN CREDIT CARD',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 6,
+ description: 'LOAN AGAINST SECURITIES',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 7,
+ description: 'LOAN AGAINST DEPOSIT',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 8,
+ description: 'LOAN AGAINST GOLD',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 9,
+ description: 'LOAN AGAINST WAREHOUSE',
+ upto1Lakh: { amount: 13265.0, accounts: 1 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 13265.0, accounts: 1 }
+ },
+ {
+ sl: 10,
+ description: 'SUNDRY DEBTORS',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 11,
+ description: 'ADVANCE TO EMPLOYEES',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 12,
+ description: 'LONG TERM LOAN A/C',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ },
+ {
+ sl: 13,
+ description: 'PROPERTY MORTGAGE LIMIT',
+ upto1Lakh: { amount: 37564.07, accounts: 2 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 37564.07, accounts: 2 }
+ },
+ {
+ sl: 14,
+ description: 'MEDIUM TERM LOAN',
+ upto1Lakh: { amount: 0, accounts: 0 },
+ oneTo2Lakh: { amount: 0, accounts: 0 },
+ twoTo3Lakh: { amount: 0, accounts: 0 },
+ above3Lakh: { amount: 0, accounts: 0 },
+ total: { amount: 0, accounts: 0 }
+ }
+];
+
+/** Section D: Final summary */
+export const SUMMARY_D = [
+ { sl: 1, description: 'CREDIT BALANCE (1 to 16)', amount: 600018811.06, accounts: 8156 },
+ { sl: 2, description: 'CREDIT BALANCE (LOAN A/C)', amount: 4224830.42, accounts: 16 },
+ { sl: 3, description: 'OTHER LIABILITY', amount: 3436255.94, accounts: 18 }
+];
+
+/** SAHAY depositor list — sample customers (same dummy set used in CKYC + DEAF) */
+export const SAHAY_DEPOSITORS: SahayDepositor[] = [
+ {
+ inputSerialNumber: 1,
+ customerIdUcic: 'UCIC0010078',
+ accountNumber: '110001302000312',
+ typeOfAccount: 'SAVINGS',
+ natureOfAccount: 'INDIVIDUAL',
+ numberOfHolders: 1,
+ nameFirst: 'RAVI KUMAR SHARMA',
+ nameSecond: '',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '10-08-1985',
+ amountOfDeposit: 425000,
+ insuredAmount: 425000,
+ loanAgainstDeposit: '',
+ panNumber: 'AAQPS8560J',
+ aadhaarNumber: 'XXXXXXXX1234',
+ otherKycType: '',
+ otherKycNumber: '',
+ mobileNumber: '9892011001',
+ typeOfDeposit: 'SB',
+ lastCustomerInducedTxnDate: '05-04-2026'
+ },
+ {
+ inputSerialNumber: 2,
+ customerIdUcic: 'UCIC0010079',
+ accountNumber: '110001302000418',
+ typeOfAccount: 'FIXED',
+ natureOfAccount: 'INDIVIDUAL',
+ numberOfHolders: 1,
+ nameFirst: 'PRIYA PATEL',
+ nameSecond: '',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '22-03-1990',
+ amountOfDeposit: 320000,
+ insuredAmount: 320000,
+ loanAgainstDeposit: '',
+ panNumber: 'AKLPS9933P',
+ aadhaarNumber: 'XXXXXXXX2210',
+ otherKycType: '',
+ otherKycNumber: '',
+ mobileNumber: '9822055002',
+ typeOfDeposit: 'FD',
+ lastCustomerInducedTxnDate: '15-03-2026'
+ },
+ {
+ inputSerialNumber: 3,
+ customerIdUcic: 'UCIC0010080',
+ accountNumber: '110001303000522',
+ typeOfAccount: 'RECURRING',
+ natureOfAccount: 'JOINT',
+ numberOfHolders: 2,
+ nameFirst: 'ARJUN DESHMUKH',
+ nameSecond: 'REKHA DESHMUKH',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '15-01-1978',
+ amountOfDeposit: 148500,
+ insuredAmount: 148500,
+ loanAgainstDeposit: '',
+ panNumber: 'AGTPD1144K',
+ aadhaarNumber: 'XXXXXXXX5544',
+ otherKycType: '',
+ otherKycNumber: '',
+ mobileNumber: '9823033003',
+ typeOfDeposit: 'RD',
+ lastCustomerInducedTxnDate: '10-04-2026'
+ },
+ {
+ inputSerialNumber: 4,
+ customerIdUcic: 'UCIC0020101',
+ accountNumber: '110001302000711',
+ typeOfAccount: 'FIXED',
+ natureOfAccount: 'INDIVIDUAL',
+ numberOfHolders: 1,
+ nameFirst: 'VIKRAM REDDY',
+ nameSecond: '',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '07-07-1982',
+ amountOfDeposit: 600000,
+ insuredAmount: 500000,
+ loanAgainstDeposit: 'LAFD',
+ panNumber: 'AAEPR9352L',
+ aadhaarNumber: 'XXXXXXXX6622',
+ otherKycType: '',
+ otherKycNumber: '',
+ mobileNumber: '9845022004',
+ typeOfDeposit: 'FD',
+ lastCustomerInducedTxnDate: '20-02-2026'
+ },
+ {
+ inputSerialNumber: 5,
+ customerIdUcic: 'UCIC0020102',
+ accountNumber: '110001301000814',
+ typeOfAccount: 'SAVINGS',
+ natureOfAccount: 'INDIVIDUAL',
+ numberOfHolders: 1,
+ nameFirst: 'LAKSHMI IYER',
+ nameSecond: '',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '14-09-1986',
+ amountOfDeposit: 280000,
+ insuredAmount: 280000,
+ loanAgainstDeposit: '',
+ panNumber: '',
+ aadhaarNumber: 'XXXXXXXX5511',
+ otherKycType: 'VOTER_ID',
+ otherKycNumber: 'KA09KL5544',
+ mobileNumber: '9845066005',
+ typeOfDeposit: 'SB',
+ lastCustomerInducedTxnDate: '01-04-2026'
+ },
+ {
+ inputSerialNumber: 6,
+ customerIdUcic: 'UCIC0040301',
+ accountNumber: '110001302001012',
+ typeOfAccount: 'FIXED',
+ natureOfAccount: 'JOINT',
+ numberOfHolders: 2,
+ nameFirst: 'NIRAV SHAH',
+ nameSecond: 'RITA SHAH',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '18-05-1992',
+ amountOfDeposit: 750000,
+ insuredAmount: 500000,
+ loanAgainstDeposit: '',
+ panNumber: 'AHKPS5544Z',
+ aadhaarNumber: 'XXXXXXXX9933',
+ otherKycType: 'PASSPORT',
+ otherKycNumber: 'M5544321',
+ mobileNumber: '9824012010',
+ typeOfDeposit: 'FD',
+ lastCustomerInducedTxnDate: '20-12-2025'
+ },
+ {
+ inputSerialNumber: 7,
+ customerIdUcic: 'UCIC0050401',
+ accountNumber: '110001301001145',
+ typeOfAccount: 'SAVINGS',
+ natureOfAccount: 'INDIVIDUAL',
+ numberOfHolders: 1,
+ nameFirst: 'KAVITA VERMA',
+ nameSecond: '',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '01-04-1988',
+ amountOfDeposit: 95000,
+ insuredAmount: 95000,
+ loanAgainstDeposit: '',
+ panNumber: 'AKHPV2244R',
+ aadhaarNumber: 'XXXXXXXX1144',
+ otherKycType: '',
+ otherKycNumber: '',
+ mobileNumber: '9415055014',
+ typeOfDeposit: 'SB',
+ lastCustomerInducedTxnDate: '15-03-2026'
+ },
+ {
+ inputSerialNumber: 8,
+ customerIdUcic: 'UCIC0070601',
+ accountNumber: '110001304001458',
+ typeOfAccount: 'TERM_LOAN',
+ natureOfAccount: 'INDIVIDUAL',
+ numberOfHolders: 1,
+ nameFirst: 'SAMEER KAPOOR',
+ nameSecond: '',
+ nameThird: '',
+ dateOfBirthOrIncorporation: '03-02-1987',
+ amountOfDeposit: 1200000,
+ insuredAmount: 500000,
+ loanAgainstDeposit: 'LAD',
+ panNumber: 'AHKPK7788Q',
+ aadhaarNumber: 'XXXXXXXX8877',
+ otherKycType: '',
+ otherKycNumber: '',
+ mobileNumber: '9811022019',
+ typeOfDeposit: 'TL',
+ lastCustomerInducedTxnDate: '12-04-2026'
+ }
+];
diff --git a/src/app/dicgc/dicgc-routing.module.ts b/src/app/dicgc/dicgc-routing.module.ts
new file mode 100644
index 000000000..3f07fa540
--- /dev/null
+++ b/src/app/dicgc/dicgc-routing.module.ts
@@ -0,0 +1,29 @@
+/**
+ * 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/.
+ */
+
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { Route } from '../core/route/route.service';
+import { DicgcComponent } from './dicgc.component';
+
+const routes: Routes = [
+ Route.withShell([
+ {
+ path: 'dicgc',
+ component: DicgcComponent,
+ data: { title: 'DICGC', breadcrumb: 'DICGC' }
+ }
+ ])
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class DicgcRoutingModule {}
diff --git a/src/app/dicgc/dicgc.component.html b/src/app/dicgc/dicgc.component.html
new file mode 100644
index 000000000..9b716e928
--- /dev/null
+++ b/src/app/dicgc/dicgc.component.html
@@ -0,0 +1,425 @@
+
+
+
+
+
+
+
+
+
Deposit Insurance and Credit Guarantee (DICGC)
+
+ {{ bank.name }} · {{ bank.branch }} · As on {{ bank.reportAsOn }}
+
+
+
+
+
+
+
+
+
+ Dashboard
+
+
+ A/C-Wise Slab Report
+
+
+ SAHAY Depositor List
+
+
+
+
+ @if (activeTab === 'dashboard') {
+
+
+
+
+
+
₹{{ depositTotal.amount | number: '1.0-0' }}
+
Total Deposits
+
{{ depositTotal.accounts | number }} accounts
+
+
+
+
+
+
{{ fullyInsuredAccounts | number }}
+
Fully Insured A/cs
+
balance ≤ ₹{{ bank.insuranceLimit | number }}
+
+
+
+
+
+
{{ above3LakhAccounts | number }}
+
Above ₹3L A/cs
+
partial / above-cap exposure
+
+
+
+
+
+
₹{{ loanTotal.amount | number: '1.0-0' }}
+
Loans Against Deposits
+
{{ loanTotal.accounts | number }} accounts
+
+
+
+
+
+
₹{{ premiumDue | number: '1.0-0' }}
+
DICGC Premium (Half-Yearly)
+
{{ bank.premiumRatePer100 }}% of deposits
+
+
+
+
+
+
+
+
+ A/C-Wise Slab Report
+
+
+ As on: {{ bank.reportAsOn }}
+
+
+ Total deposits: ₹{{ depositTotal.amount | number: '1.0-0' }} across
+ {{ depositTotal.accounts | number }} A/cs
+
+
+ Open Report
+
+
+
+
SAHAY Depositor List
+
Format: DICGC SAHAY 2.0
+
+ Total entries: {{ depositors.length }}
+
+
+ Open Register
+
+
+
+
+ Premium Schedule
+
+
+ Half-yearly assessment as of {{ bank.reportAsOn }}
+
+
Next due: 31 May 2026
+
+
+
+ }
+
+
+ @if (activeTab === 'slab') {
+
+
+
+
+
+
CREDIT BALANCE — DEPOSITS
+
+
+
+ Sr.No
+ Description
+ Up to ₹1,00,000
+ ₹1,00,001 – 2,00,000
+ ₹2,00,001 – 3,00,000
+ Above ₹3,00,000
+ Total
+
+
+ Amount
+ #A/c
+ Amount
+ #A/c
+ Amount
+ #A/c
+ Amount
+ #A/c
+ Amount
+ #A/c
+
+
+
+ @for (r of deposits; track r.sl) {
+
+ {{ r.sl }}
+ {{ r.description }}
+ {{ r.upto1Lakh.amount | number: '1.2-2' }}
+ {{ r.upto1Lakh.accounts | number }}
+ {{ r.oneTo2Lakh.amount | number: '1.2-2' }}
+ {{ r.oneTo2Lakh.accounts | number }}
+ {{ r.twoTo3Lakh.amount | number: '1.2-2' }}
+ {{ r.twoTo3Lakh.accounts | number }}
+ {{ r.above3Lakh.amount | number: '1.2-2' }}
+ {{ r.above3Lakh.accounts | number }}
+
+ {{ r.total.amount | number: '1.2-2' }}
+
+
+ {{ r.total.accounts | number }}
+
+
+ }
+
+
+ TOTAL
+ 600018811.06
+ {{ depositTotal.accounts | number }}
+ —
+ —
+ —
+ —
+ —
+ —
+
+ {{ depositTotal.amount | number: '1.2-2' }}
+
+
+ {{ depositTotal.accounts | number }}
+
+
+
+
+
+
+
+
+
CREDIT BALANCE (LOAN A/C) — Section A
+
+
+
+ Sr.No
+ Description
+ Up to ₹1,00,000
+ ₹1,00,001 – 2,00,000
+ ₹2,00,001 – 3,00,000
+ Above ₹3,00,000
+ Total
+
+
+ Amount
+ #A/c
+ Amount
+ #A/c
+ Amount
+ #A/c
+ Amount
+ #A/c
+ Amount
+ #A/c
+
+
+
+ @for (r of loans; track r.sl) {
+
+ {{ r.sl }}
+ {{ r.description }}
+ {{ r.upto1Lakh.amount | number: '1.2-2' }}
+ {{ r.upto1Lakh.accounts | number }}
+ {{ r.oneTo2Lakh.amount | number: '1.2-2' }}
+ {{ r.oneTo2Lakh.accounts | number }}
+ {{ r.twoTo3Lakh.amount | number: '1.2-2' }}
+ {{ r.twoTo3Lakh.accounts | number }}
+ {{ r.above3Lakh.amount | number: '1.2-2' }}
+ {{ r.above3Lakh.accounts | number }}
+
+ {{ r.total.amount | number: '1.2-2' }}
+
+
+ {{ r.total.accounts | number }}
+
+
+ }
+
+
+ TOTAL
+ —
+ —
+ —
+ —
+ —
+ —
+ —
+ —
+
+ {{ loanTotal.amount | number: '1.2-2' }}
+
+
+ {{ loanTotal.accounts | number }}
+
+
+
+
+
+
+
+
+
Section D — Final Summary
+
+
+
+ Sr.No
+ Description
+ Amount
+ #A/c
+
+
+
+ @for (s of summary; track s.sl) {
+
+ {{ s.sl }}
+ {{ s.description }}
+ {{ s.amount | number: '1.2-2' }}
+ {{ s.accounts | number }}
+
+ }
+
+
+ TOTAL
+ 607,679,897.42
+ 8,190
+
+
+
+
+
+ }
+
+
+ @if (activeTab === 'sahay') {
+
+
+
+
+
+
+
+
+
+ SR
+ UCIC
+ A/c No
+ Type
+ Holders
+ Customer Name(s)
+ DOB
+ Deposit
+ Insured
+ LAD/LAFD
+ PAN
+ Aadhaar
+ Mobile
+ Last Txn
+
+
+
+ @for (d of filteredDepositors; track d.inputSerialNumber) {
+
+ {{ d.inputSerialNumber }}
+ {{ d.customerIdUcic }}
+ {{ d.accountNumber }}
+ {{ d.typeOfDeposit }}
+ {{ d.numberOfHolders }}
+
+ {{ d.nameFirst }}
+ @if (d.nameSecond) {
+ & {{ d.nameSecond }}
+ }
+ @if (d.nameThird) {
+ & {{ d.nameThird }}
+ }
+
+ {{ d.dateOfBirthOrIncorporation }}
+ {{ d.amountOfDeposit | number: '1.0-0' }}
+ {{ d.insuredAmount | number: '1.0-0' }}
+ {{ d.loanAgainstDeposit || '—' }}
+ {{ d.panNumber || '—' }}
+ {{ d.aadhaarNumber || '—' }}
+ {{ d.mobileNumber }}
+ {{ d.lastCustomerInducedTxnDate }}
+
+ } @empty {
+
+ No depositors match the filters.
+
+ }
+
+
+
+
+
+
+ SAHAY Note: Insured Amount is capped at ₹{{ bank.insuranceLimit | number }} per
+ depositor per bank. Rows highlighted are above the cap — DICGC pays the cap, residual is unsecured.
+
+
+ }
+
+
+
{{ toastMsg }}
+
diff --git a/src/app/dicgc/dicgc.component.scss b/src/app/dicgc/dicgc.component.scss
new file mode 100644
index 000000000..55c5e688e
--- /dev/null
+++ b/src/app/dicgc/dicgc.component.scss
@@ -0,0 +1,602 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --dc-navy: #0b1e3d;
+ --dc-blue: #1d4ed8;
+ --dc-blue-l: #eff6ff;
+ --dc-teal: #0e7e6b;
+ --dc-teal-l: #e0f4f0;
+ --dc-green: #15803d;
+ --dc-green-l: #dcfce7;
+ --dc-amber: #d97706;
+ --dc-amber-l: #fef3c7;
+ --dc-red: #c0392b;
+ --dc-red-l: #fdecea;
+ --dc-purple: #6d28d9;
+ --dc-purple-l: #ede9fe;
+ --dc-g50: #f8fafc;
+ --dc-g100: #f1f5f9;
+ --dc-g200: #e2e8f0;
+ --dc-g300: #cbd5e1;
+ --dc-g500: #64748b;
+ --dc-g700: #334155;
+ --dc-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ background: var(--dc-g50);
+ color: var(--dc-g900);
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+.dic-root {
+ padding: 18px 22px;
+ max-width: 1500px;
+ margin: 0 auto;
+}
+
+/* HEADER */
+.dic-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+ margin-bottom: 16px;
+}
+
+.dic-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.dic-head-ic {
+ font-size: 28px;
+ color: var(--dc-blue);
+}
+
+.dic-head-t {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--dc-g900);
+}
+
+.dic-head-s {
+ font-size: 12px;
+ color: var(--dc-g500);
+}
+
+.dic-head-meta {
+ display: flex;
+ gap: 22px;
+
+ > div {
+ display: flex;
+ flex-direction: column;
+
+ .dic-meta-l {
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--dc-g500);
+ }
+
+ .dic-meta-v {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--dc-g900);
+ }
+ }
+}
+
+/* TABS */
+.dic-tabs {
+ display: flex;
+ gap: 4px;
+ background: #fff;
+ border: 1px solid var(--dc-g200);
+ border-radius: 10px;
+ padding: 4px;
+ margin-bottom: 16px;
+ overflow-x: auto;
+}
+
+.dic-tab {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 14px;
+ border: none;
+ background: transparent;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--dc-g500);
+ cursor: pointer;
+ border-radius: 7px;
+ white-space: nowrap;
+ transition: all 0.15s;
+ font-family: inherit;
+
+ &:hover {
+ color: var(--dc-g900);
+ }
+
+ &.active {
+ background: var(--dc-blue);
+ color: #fff;
+ box-shadow: 0 2px 6px rgb(29 78 216 / 25%);
+ }
+}
+
+.dic-section {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+/* KPI CARDS */
+.dic-kpis {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 12px;
+}
+
+.dic-kpi {
+ background: #fff;
+ border: 1px solid var(--dc-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
+ }
+}
+
+.dic-kpi-ic {
+ width: 42px;
+ height: 42px;
+ border-radius: 11px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.dic-kpi--blue .dic-kpi-ic {
+ background: var(--dc-blue-l);
+ color: var(--dc-blue);
+}
+
+.dic-kpi--green .dic-kpi-ic {
+ background: var(--dc-green-l);
+ color: var(--dc-green);
+}
+
+.dic-kpi--amber .dic-kpi-ic {
+ background: var(--dc-amber-l);
+ color: var(--dc-amber);
+}
+
+.dic-kpi--teal .dic-kpi-ic {
+ background: var(--dc-teal-l);
+ color: var(--dc-teal);
+}
+
+.dic-kpi--red .dic-kpi-ic {
+ background: var(--dc-red-l);
+ color: var(--dc-red);
+}
+
+.dic-kpi-n {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--dc-g900);
+ line-height: 1.1;
+}
+
+.dic-kpi-l {
+ font-size: 12px;
+ color: var(--dc-g700);
+ font-weight: 500;
+ margin-top: 2px;
+}
+
+.dic-kpi-sub {
+ font-size: 11px;
+ color: var(--dc-g500);
+ margin-top: 1px;
+}
+
+/* COMPLIANCE CARDS */
+.dic-compliance {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 12px;
+}
+
+.dc-card {
+ background: #fff;
+ border: 1px solid var(--dc-g200);
+ border-radius: 12px;
+ padding: 14px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.dc-card-h {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--dc-g900);
+ margin-bottom: 8px;
+}
+
+.dc-ok {
+ color: var(--dc-green);
+}
+
+.dc-info {
+ color: var(--dc-blue);
+}
+
+.dc-meta {
+ font-size: 12px;
+ color: var(--dc-g500);
+ margin: 2px 0;
+
+ strong {
+ color: var(--dc-g900);
+ font-weight: 600;
+ }
+}
+
+.dc-btn {
+ margin-top: 10px;
+ padding: 6px 12px;
+ border: 1px solid var(--dc-blue);
+ background: var(--dc-blue-l);
+ color: var(--dc-blue);
+ border-radius: 8px;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ font-family: inherit;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+
+ &:hover {
+ background: var(--dc-blue);
+ color: #fff;
+ }
+}
+
+/* FORM HEADER */
+.dic-form-meta {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 14px;
+ flex-wrap: wrap;
+}
+
+.dic-form-title {
+ h3 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--dc-g900);
+ }
+
+ p {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--dc-g500);
+ }
+}
+
+.dic-action-group {
+ display: flex;
+ gap: 8px;
+}
+
+/* BUTTONS */
+.dic-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ border: 1px solid var(--dc-g300);
+ background: #fff;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--dc-g700);
+ cursor: pointer;
+ font-family: inherit;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--dc-g50);
+ border-color: var(--dc-g500);
+ }
+}
+
+.dic-btn-xls {
+ background: #16a34a;
+ border-color: #16a34a;
+ color: #fff;
+
+ &:hover {
+ background: #15803d;
+ border-color: #15803d;
+ color: #fff;
+ }
+}
+
+.dic-btn-csv {
+ background: #1d4ed8;
+ border-color: #1d4ed8;
+ color: #fff;
+
+ &:hover {
+ background: #1e40af;
+ border-color: #1e40af;
+ color: #fff;
+ }
+}
+
+.dic-btn-g {
+ align-self: end;
+}
+
+/* INPUTS */
+.dic-inp {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid var(--dc-g200);
+ border-radius: 8px;
+ font-size: 13px;
+ background: #fff;
+ font-family: inherit;
+
+ &:focus {
+ outline: none;
+ border-color: var(--dc-blue);
+ box-shadow: 0 0 0 3px rgb(29 78 216 / 15%);
+ }
+}
+
+/* FILTERS */
+.dic-filters {
+ background: #fff;
+ border: 1px solid var(--dc-g200);
+ border-radius: 12px;
+ padding: 14px;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1.4fr auto;
+ gap: 12px;
+ align-items: end;
+}
+
+.dic-filter {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ label {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--dc-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+}
+
+.dic-filter-check .check-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 13px;
+ color: var(--dc-g700);
+ cursor: pointer;
+ user-select: none;
+ margin-top: 14px;
+
+ input {
+ width: 16px;
+ height: 16px;
+ accent-color: var(--dc-blue);
+ }
+}
+
+/* TABLE */
+.dic-tbl-wrap {
+ background: #fff;
+ border: 1px solid var(--dc-g200);
+ border-radius: 12px;
+ overflow: auto;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.dic-tbl-h {
+ background: var(--dc-blue-l);
+ color: var(--dc-blue);
+ padding: 10px 16px;
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ border-bottom: 1px solid var(--dc-g200);
+}
+
+.dic-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 9px 10px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--dc-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ background: var(--dc-g50);
+ border-bottom: 1px solid var(--dc-g200);
+ }
+
+ th.hd-1,
+ tr.hd-1 th {
+ background: #f0f4ff;
+ color: var(--dc-blue);
+ }
+
+ td {
+ padding: 9px 10px;
+ border-bottom: 1px solid var(--dc-g100);
+ font-size: 12px;
+ color: var(--dc-g700);
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--dc-g50);
+ }
+
+ tr.row-total td {
+ background: #f0fdf4;
+ font-weight: 700;
+ color: var(--dc-g900);
+ border-top: 2px solid var(--dc-green);
+ }
+
+ tr.uninsured td {
+ background: #fff7ed;
+ }
+
+ tr.uninsured:hover td {
+ background: #ffedd5;
+ }
+}
+
+.dic-summary-tbl {
+ td,
+ th {
+ padding: 11px 14px;
+ font-size: 13px;
+ }
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 11px;
+ letter-spacing: 0.02em;
+}
+
+.cust-2 {
+ color: var(--dc-g500);
+ font-size: 11px;
+}
+
+.dic-empty {
+ text-align: center;
+ padding: 30px;
+ color: var(--dc-g500);
+ font-style: italic;
+}
+
+/* NOTE */
+.dic-note {
+ background: var(--dc-amber-l);
+ border: 1px solid #fed7aa;
+ border-radius: 10px;
+ padding: 12px 16px;
+ display: flex;
+ gap: 10px;
+ font-size: 12px;
+ color: #92400e;
+ line-height: 1.5;
+
+ fa-icon {
+ font-size: 14px;
+ flex-shrink: 0;
+ margin-top: 2px;
+ }
+}
+
+/* TOAST */
+.dic-toast {
+ position: fixed;
+ bottom: 32px;
+ left: 50%;
+ transform: translateX(-50%) translateY(20px);
+ background: var(--dc-navy);
+ color: #fff;
+ padding: 12px 22px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 500;
+ box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
+ opacity: 0;
+ pointer-events: none;
+ transition: all 0.25s;
+ z-index: 1500;
+
+ &.show {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+}
+
+@media (width <= 1100px) {
+ .dic-kpis {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .dic-compliance {
+ grid-template-columns: 1fr;
+ }
+
+ .dic-filters {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+@media (width <= 720px) {
+ .dic-kpis {
+ grid-template-columns: 1fr 1fr;
+ }
+}
diff --git a/src/app/dicgc/dicgc.component.ts b/src/app/dicgc/dicgc.component.ts
new file mode 100644
index 000000000..608080a6a
--- /dev/null
+++ b/src/app/dicgc/dicgc.component.ts
@@ -0,0 +1,398 @@
+/**
+ * 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/.
+ */
+
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import {
+ DEPOSIT_SLABS,
+ DICGC_BANK,
+ LOAN_SLABS,
+ SahayDepositor,
+ SAHAY_DEPOSITORS,
+ SlabRow,
+ SUMMARY_D
+} from './dicgc-data';
+
+@Component({
+ selector: 'mifosx-dicgc',
+ templateUrl: './dicgc.component.html',
+ styleUrls: ['./dicgc.component.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ FaIconComponent,
+ FormsModule
+ ]
+})
+export class DicgcComponent {
+ /** Active tab */
+ activeTab: 'dashboard' | 'slab' | 'sahay' = 'dashboard';
+
+ bank = DICGC_BANK;
+ deposits: SlabRow[] = DEPOSIT_SLABS;
+ loans: SlabRow[] = LOAN_SLABS;
+ summary = SUMMARY_D;
+ depositors: SahayDepositor[] = SAHAY_DEPOSITORS;
+
+ /** SAHAY filters */
+ filterName = '';
+ filterAccountNumber = '';
+ filterAboveLimit = false;
+
+ /** Toast */
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: any;
+
+ showTab(tab: 'dashboard' | 'slab' | 'sahay'): void {
+ this.activeTab = tab;
+ }
+
+ // -------------------- KPI helpers --------------------
+ private sumSlab(
+ rows: SlabRow[],
+ slab: keyof Pick
+ ): { amount: number; accounts: number } {
+ let amount = 0;
+ let accounts = 0;
+ for (const r of rows) {
+ amount += r[slab].amount;
+ accounts += r[slab].accounts;
+ }
+ return { amount, accounts };
+ }
+
+ get depositTotal(): { amount: number; accounts: number } {
+ return this.sumSlab(this.deposits, 'total');
+ }
+ get loanTotal(): { amount: number; accounts: number } {
+ return this.sumSlab(this.loans, 'total');
+ }
+
+ /** Insured = full amount for ≤5L, capped at 5L for >5L (sum across slabs ≤3L is fully insured here) */
+ get fullyInsuredAccounts(): number {
+ const u1 = this.sumSlab(this.deposits, 'upto1Lakh').accounts;
+ const u2 = this.sumSlab(this.deposits, 'oneTo2Lakh').accounts;
+ const u3 = this.sumSlab(this.deposits, 'twoTo3Lakh').accounts;
+ return u1 + u2 + u3;
+ }
+
+ get above3LakhAccounts(): number {
+ return this.sumSlab(this.deposits, 'above3Lakh').accounts;
+ }
+
+ get insuredAmount(): number {
+ return this.depositors.reduce((s, d) => s + d.insuredAmount, 0);
+ }
+
+ /** DICGC premium = 0.12% of total assessable deposits per half year */
+ get premiumDue(): number {
+ return this.depositTotal.amount * (this.bank.premiumRatePer100 / 100);
+ }
+
+ // SAHAY filtered list
+ get filteredDepositors(): SahayDepositor[] {
+ const name = this.filterName.trim().toLowerCase();
+ const acc = this.filterAccountNumber.trim();
+ return this.depositors.filter((d) => {
+ if (name && !`${d.nameFirst} ${d.nameSecond} ${d.nameThird}`.toLowerCase().includes(name)) return false;
+ if (acc && !d.accountNumber.includes(acc)) return false;
+ if (this.filterAboveLimit && d.amountOfDeposit <= this.bank.insuranceLimit) return false;
+ return true;
+ });
+ }
+
+ resetFilters(): void {
+ this.filterName = '';
+ this.filterAccountNumber = '';
+ this.filterAboveLimit = false;
+ }
+
+ /** True if depositor's amount exceeds the DICGC ₹5L cap */
+ isUninsured(d: SahayDepositor): boolean {
+ return d.amountOfDeposit > this.bank.insuranceLimit;
+ }
+
+ // -------------------- Excel exports --------------------
+ exportSlabReport(): void {
+ const rows: string[][] = [];
+ rows.push([
+ this.bank.name,
+ this.bank.branch
+ ]);
+ rows.push([`A/C-Wise Deposit Insurance and Credit Guarantee Report — As On ${this.bank.reportAsOn}`]);
+ rows.push([]);
+ rows.push([
+ `Report Ref: ${this.bank.reportRefNo}`,
+ `Print Date: ${this.bank.printDate}`,
+ `User Code: ${this.bank.userCode}`
+ ]);
+ rows.push([]);
+
+ // Header section
+ rows.push(['CREDIT BALANCE — DEPOSITS']);
+ rows.push([
+ 'Sr.No',
+ 'Description',
+ 'Up to 1L Amount',
+ 'Up to 1L #A/c',
+ '1L-2L Amount',
+ '1L-2L #A/c',
+ '2L-3L Amount',
+ '2L-3L #A/c',
+ 'Above 3L Amount',
+ 'Above 3L #A/c',
+ 'Total Amount',
+ 'Total #A/c'
+ ]);
+ for (const r of this.deposits) {
+ rows.push(this.flattenSlabRow(r));
+ }
+ const dt = this.depositTotal;
+ rows.push([
+ '',
+ 'TOTAL',
+ this.sumSlab(this.deposits, 'upto1Lakh').amount.toFixed(2),
+ this.sumSlab(this.deposits, 'upto1Lakh').accounts.toString(),
+ this.sumSlab(this.deposits, 'oneTo2Lakh').amount.toFixed(2),
+ this.sumSlab(this.deposits, 'oneTo2Lakh').accounts.toString(),
+ this.sumSlab(this.deposits, 'twoTo3Lakh').amount.toFixed(2),
+ this.sumSlab(this.deposits, 'twoTo3Lakh').accounts.toString(),
+ this.sumSlab(this.deposits, 'above3Lakh').amount.toFixed(2),
+ this.sumSlab(this.deposits, 'above3Lakh').accounts.toString(),
+ dt.amount.toFixed(2),
+ dt.accounts.toString()
+ ]);
+
+ rows.push([]);
+ rows.push(['CREDIT BALANCE (LOAN A/C) — Section A']);
+ rows.push([
+ 'Sr.No',
+ 'Description',
+ 'Up to 1L Amount',
+ 'Up to 1L #A/c',
+ '1L-2L Amount',
+ '1L-2L #A/c',
+ '2L-3L Amount',
+ '2L-3L #A/c',
+ 'Above 3L Amount',
+ 'Above 3L #A/c',
+ 'Total Amount',
+ 'Total #A/c'
+ ]);
+ for (const r of this.loans) {
+ rows.push(this.flattenSlabRow(r));
+ }
+ const lt = this.loanTotal;
+ rows.push([
+ '',
+ 'TOTAL',
+ this.sumSlab(this.loans, 'upto1Lakh').amount.toFixed(2),
+ this.sumSlab(this.loans, 'upto1Lakh').accounts.toString(),
+ this.sumSlab(this.loans, 'oneTo2Lakh').amount.toFixed(2),
+ this.sumSlab(this.loans, 'oneTo2Lakh').accounts.toString(),
+ this.sumSlab(this.loans, 'twoTo3Lakh').amount.toFixed(2),
+ this.sumSlab(this.loans, 'twoTo3Lakh').accounts.toString(),
+ this.sumSlab(this.loans, 'above3Lakh').amount.toFixed(2),
+ this.sumSlab(this.loans, 'above3Lakh').accounts.toString(),
+ lt.amount.toFixed(2),
+ lt.accounts.toString()
+ ]);
+
+ rows.push([]);
+ rows.push(['Section D — Final Summary']);
+ rows.push([
+ 'Sr.No',
+ 'Description',
+ 'Amount',
+ '#A/c'
+ ]);
+ for (const s of this.summary) {
+ rows.push([
+ s.sl.toString(),
+ s.description,
+ s.amount.toFixed(2),
+ s.accounts.toString()
+ ]);
+ }
+ const grandAmount = this.summary.reduce((a, x) => a + x.amount, 0);
+ const grandAcc = this.summary.reduce((a, x) => a + x.accounts, 0);
+ rows.push([
+ '',
+ 'TOTAL',
+ grandAmount.toFixed(2),
+ grandAcc.toString()
+ ]);
+
+ this.saveTsv(rows, `DICGC-SlabReport-${this.fileTag()}.xls`);
+ this.toast('DICGC slab report downloaded');
+ }
+
+ exportSahay(): void {
+ const headers = [
+ 'Input Serial Number',
+ 'Customer ID (UCIC) Number',
+ 'Account Number',
+ 'Type of account',
+ 'Nature of account',
+ 'Number of Acct. Holders (in case of Joint)',
+ 'Name Of First Customer',
+ 'Name Of Second Customer',
+ 'Name Of Third Customer',
+ 'Date of Birth (Primary Holder) or Date of incorporation in case of Entity',
+ 'Amount of Deposit',
+ 'Insured Amount',
+ 'Loan against deposits only (LAD / LAFD)',
+ 'PAN Number',
+ 'Aadhaar Number',
+ 'Any Other OVD / KYC Document Type',
+ 'Any Other OVD / KYC Number',
+ 'Mobile Number',
+ 'Type of Deposit',
+ 'Date of Last Customer inducted transaction'
+ ];
+ const rows: string[][] = [headers];
+ for (const d of this.filteredDepositors) {
+ rows.push([
+ d.inputSerialNumber.toString(),
+ d.customerIdUcic,
+ d.accountNumber,
+ d.typeOfAccount,
+ d.natureOfAccount,
+ d.numberOfHolders.toString(),
+ d.nameFirst,
+ d.nameSecond,
+ d.nameThird,
+ d.dateOfBirthOrIncorporation,
+ d.amountOfDeposit.toFixed(2),
+ d.insuredAmount.toFixed(2),
+ d.loanAgainstDeposit,
+ d.panNumber,
+ d.aadhaarNumber,
+ d.otherKycType,
+ d.otherKycNumber,
+ d.mobileNumber,
+ d.typeOfDeposit,
+ d.lastCustomerInducedTxnDate
+ ]);
+ }
+ this.saveTsv(rows, `DICGC-SAHAY-Depositors-${this.fileTag()}.xls`);
+ this.toast(`${this.filteredDepositors.length} depositors exported (SAHAY format)`);
+ }
+
+ exportSahayCsv(): void {
+ // CSV mirrors the Input File Template DICGC ships
+ const headers = [
+ 'Input Serial Number',
+ 'Customer ID (UCIC) Number',
+ 'Account Number',
+ 'Type of account',
+ 'Nature of account',
+ 'Number of Acct. Holders (in case of Joint)',
+ 'Name Of First Customer',
+ 'Name Of Second Customer',
+ 'Name Of Third Customer',
+ 'Date of Birth (Primary Holder) or Date of incorporation in case of Entity',
+ 'Amount of Deposit',
+ 'Insured Amount',
+ 'Loan against deposits only (LAD / LAFD)',
+ 'PAN Number',
+ 'Aadhaar Number',
+ 'Any Other OVD / KYC Document Type',
+ 'Any Other OVD / KYC Number',
+ 'Mobile Number',
+ 'Type of Deposit',
+ 'Date of Last Customer inducted transaction'
+ ];
+ const escape = (v: string): string => {
+ const s = (v || '').replace(/"/g, '""');
+ if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
+ return s;
+ };
+ const lines: string[] = [headers.map(escape).join(',')];
+ for (const d of this.filteredDepositors) {
+ lines.push(
+ [
+ d.inputSerialNumber.toString(),
+ d.customerIdUcic,
+ d.accountNumber,
+ d.typeOfAccount,
+ d.natureOfAccount,
+ d.numberOfHolders.toString(),
+ d.nameFirst,
+ d.nameSecond,
+ d.nameThird,
+ d.dateOfBirthOrIncorporation,
+ d.amountOfDeposit.toFixed(2),
+ d.insuredAmount.toFixed(2),
+ d.loanAgainstDeposit,
+ d.panNumber,
+ d.aadhaarNumber,
+ d.otherKycType,
+ d.otherKycNumber,
+ d.mobileNumber,
+ d.typeOfDeposit,
+ d.lastCustomerInducedTxnDate
+ ]
+ .map(escape)
+ .join(',')
+ );
+ }
+ const blob = new Blob([lines.join('\n')], { type: 'text/csv;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `DICGC-SAHAY-Depositors-${this.fileTag()}.csv`;
+ a.click();
+ URL.revokeObjectURL(url);
+ this.toast(`${this.filteredDepositors.length} depositors exported (CSV)`);
+ }
+
+ private flattenSlabRow(r: SlabRow): string[] {
+ return [
+ r.sl.toString(),
+ r.description,
+ r.upto1Lakh.amount.toFixed(2),
+ r.upto1Lakh.accounts.toString(),
+ r.oneTo2Lakh.amount.toFixed(2),
+ r.oneTo2Lakh.accounts.toString(),
+ r.twoTo3Lakh.amount.toFixed(2),
+ r.twoTo3Lakh.accounts.toString(),
+ r.above3Lakh.amount.toFixed(2),
+ r.above3Lakh.accounts.toString(),
+ r.total.amount.toFixed(2),
+ r.total.accounts.toString()
+ ];
+ }
+
+ private saveTsv(rows: string[][], filename: string): void {
+ const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
+ const blob = new Blob([tsv], { type: 'application/vnd.ms-excel;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+
+ private fileTag(): string {
+ const d = new Date();
+ const dd = String(d.getDate()).padStart(2, '0');
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
+ return `${dd}${mm}${d.getFullYear()}`;
+ }
+
+ toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) clearTimeout(this.toastTimer);
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
+ }
+}
diff --git a/src/app/dicgc/dicgc.module.ts b/src/app/dicgc/dicgc.module.ts
new file mode 100644
index 000000000..1dd709476
--- /dev/null
+++ b/src/app/dicgc/dicgc.module.ts
@@ -0,0 +1,22 @@
+/**
+ * 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/.
+ */
+
+import { NgModule } from '@angular/core';
+
+import { SharedModule } from '../shared/shared.module';
+import { DicgcRoutingModule } from './dicgc-routing.module';
+import { DicgcComponent } from './dicgc.component';
+
+@NgModule({
+ imports: [
+ SharedModule,
+ DicgcRoutingModule,
+ DicgcComponent
+ ]
+})
+export class DicgcModule {}
diff --git a/src/app/fiu/fiu-data.ts b/src/app/fiu/fiu-data.ts
new file mode 100644
index 000000000..2f6f8dcc4
--- /dev/null
+++ b/src/app/fiu/fiu-data.ts
@@ -0,0 +1,560 @@
+/**
+ * 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/.
+ */
+
+/**
+ * Sample FIU-IND data — schema mirrors the FINnet 2.0 CSV submission templates
+ * (TC1, TC2, KC1, KC2, Account_Detail, Account_Person_Relation).
+ * Bank/customer names are dummy placeholders.
+ *
+ * Reports filed with FIU-IND under PMLA 2002:
+ * CTR — Cash Transaction Report (≥ ₹10 L cash, monthly by 15th)
+ * STR — Suspicious Transaction Report (within 7 days of detection)
+ * CCR — Counterfeit Currency Report
+ * NTR — NPO Transaction Report (≥ ₹10 L, NGO/NPO clients)
+ * CBWTR — Cross-Border Wire Transfer Report (≥ ₹5 L)
+ */
+
+export type ReportType = 'CTR' | 'STR' | 'CCR' | 'NTR' | 'CBWTR';
+export type StrStatus = 'draft' | 'po-review' | 'filed' | 'rejected';
+
+/** TC2 — Branch / cash transaction record */
+export interface CtrTransaction {
+ reportType: ReportType;
+ reportRefNo: number;
+ branchCode: string;
+ accountNumber: string;
+ txnBranchCode: string;
+ depositOrWithdrawal: 'Deposit' | 'Withdrawal';
+ txnId: number;
+ txnDate: string;
+ txnTime: string;
+ txnAmount: number;
+ instrumentType: 'Cash' | 'Cheque' | 'DD' | 'NEFT' | 'RTGS' | 'Others';
+ instrumentId: string;
+ nonCustomerRef: string;
+ relationshipFlag: 'Customer' | 'Non-Customer';
+ thirdPartyPan: string;
+ declaration: string;
+ /** Customer name resolved from KC1 (UI convenience, not in actual TC2 schema) */
+ customerName?: string;
+}
+
+/** STR record — workflow extension on top of TC2 */
+export interface StrTransaction extends CtrTransaction {
+ detectedOn: string;
+ flaggedBy: string;
+ riskCategory:
+ | 'structuring'
+ | 'dormant-revival'
+ | 'high-risk-country'
+ | 'unusual-pattern'
+ | 'pep-related'
+ | 'sanctions-hit';
+ narrative: string;
+ status: StrStatus;
+ poRemarks?: string;
+}
+
+/** KC1 — Individual customer card (subset of the 70+ fields, only what UI shows) */
+export interface KycIndividual {
+ reportRefNo: number;
+ ucic: string;
+ pan: string;
+ ckycNumber: string;
+ firstName: string;
+ middleName: string;
+ lastName: string;
+ fatherName: string;
+ motherName: string;
+ gender: 'Male' | 'Female' | 'Other';
+ dob: string;
+ mobileNumber: string;
+ email: string;
+ primaryAddress: string;
+ state: string;
+ city: string;
+ pincode: string;
+ customerType: string;
+ annualIncome: number;
+ occupation: string;
+ onboardingDate: string;
+ lastKycDate: string;
+ riskLevel: 'Low' | 'Medium' | 'High';
+ reportType: ReportType;
+ otherIdentifierName?: string;
+ otherIdentifierNumber?: string;
+}
+
+/** KC2 — Entity / non-individual customer card */
+export interface KycEntity {
+ reportRefNo: number;
+ ucic: string;
+ entityName: string;
+ panNumber: string;
+ gstin: string;
+ mobileNumber: string;
+ address: string;
+ state: string;
+ city: string;
+ pincode: string;
+ dateOfIncorporation: string;
+ customerType: string;
+ riskLevel: 'Low' | 'Medium' | 'High';
+ onboardingDate: string;
+ lastKycDate: string;
+ isSoleProprietor: boolean;
+ ownerUcic: string;
+ ownerName: string;
+ reportType: ReportType;
+}
+
+export const FIU_BANK = {
+ name: 'Bharat Urban Bank Ltd',
+ branch: 'Head Office',
+ reBankingFlag: 'SCB',
+ reportingMonth: 'January 2026',
+ reBuid: 'REBANK-2031',
+ principalOfficer: 'Karthik Kamath',
+ principalOfficerEmail: 'po.fiu@bharaturbanbank.example',
+ filerUserId: 'PO-001',
+ cashThreshold: 1000000, // ₹10 L per single cash txn
+ monthlyAggThreshold: 1000000, // aggregate ≥ ₹10 L in a month
+ cbwtrThreshold: 500000 // CBWTR threshold ₹5 L
+};
+
+/** ── Sample CTR transactions (FINnet TC2-style) ── */
+export const CTR_TXNS: CtrTransaction[] = [
+ {
+ reportType: 'CTR',
+ reportRefNo: 1,
+ branchCode: '1',
+ accountNumber: '110001101000838',
+ txnBranchCode: '1',
+ depositOrWithdrawal: 'Withdrawal',
+ txnId: 1,
+ txnDate: '20-01-2026',
+ txnTime: '16:51:28',
+ txnAmount: 1500000,
+ instrumentType: 'Cheque',
+ instrumentId: '24304',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'RAVI KUMAR SHARMA'
+ },
+ {
+ reportType: 'CTR',
+ reportRefNo: 1,
+ branchCode: '1',
+ accountNumber: '110001101000838',
+ txnBranchCode: '1',
+ depositOrWithdrawal: 'Withdrawal',
+ txnId: 2,
+ txnDate: '27-01-2026',
+ txnTime: '16:14:22',
+ txnAmount: 932000,
+ instrumentType: 'Cheque',
+ instrumentId: '24305',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'RAVI KUMAR SHARMA'
+ },
+ {
+ reportType: 'CTR',
+ reportRefNo: 2,
+ branchCode: '1',
+ accountNumber: '110001352000357',
+ txnBranchCode: '1',
+ depositOrWithdrawal: 'Withdrawal',
+ txnId: 3,
+ txnDate: '02-01-2026',
+ txnTime: '15:05:36',
+ txnAmount: 600000,
+ instrumentType: 'Cheque',
+ instrumentId: '100039',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'PRIYA PATEL'
+ },
+ {
+ reportType: 'CTR',
+ reportRefNo: 2,
+ branchCode: '1',
+ accountNumber: '110001352000357',
+ txnBranchCode: '1',
+ depositOrWithdrawal: 'Withdrawal',
+ txnId: 4,
+ txnDate: '07-01-2026',
+ txnTime: '14:14:17',
+ txnAmount: 700000,
+ instrumentType: 'Cheque',
+ instrumentId: '100040',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'PRIYA PATEL'
+ },
+ {
+ reportType: 'CTR',
+ reportRefNo: 3,
+ branchCode: '7',
+ accountNumber: '110001352000420',
+ txnBranchCode: '7',
+ depositOrWithdrawal: 'Deposit',
+ txnId: 5,
+ txnDate: '12-01-2026',
+ txnTime: '11:28:50',
+ txnAmount: 1200000,
+ instrumentType: 'Cash',
+ instrumentId: 'CASH',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'VIKRAM REDDY'
+ },
+ {
+ reportType: 'CTR',
+ reportRefNo: 4,
+ branchCode: '4',
+ accountNumber: '110001352000511',
+ txnBranchCode: '4',
+ depositOrWithdrawal: 'Deposit',
+ txnId: 6,
+ txnDate: '18-01-2026',
+ txnTime: '10:14:05',
+ txnAmount: 2500000,
+ instrumentType: 'Cash',
+ instrumentId: 'CASH',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'NIRAV SHAH'
+ },
+ {
+ reportType: 'CTR',
+ reportRefNo: 5,
+ branchCode: '11',
+ accountNumber: '110001352000604',
+ txnBranchCode: '11',
+ depositOrWithdrawal: 'Withdrawal',
+ txnId: 7,
+ txnDate: '24-01-2026',
+ txnTime: '13:42:11',
+ txnAmount: 1100000,
+ instrumentType: 'Cheque',
+ instrumentId: '88123',
+ nonCustomerRef: 'NCR-001',
+ relationshipFlag: 'Non-Customer',
+ thirdPartyPan: 'AKHPV2244R',
+ declaration: 'NA',
+ customerName: 'KAVITA VERMA'
+ }
+];
+
+/** ── Sample STR transactions ── */
+export const STR_TXNS: StrTransaction[] = [
+ {
+ reportType: 'STR',
+ reportRefNo: 101,
+ branchCode: '1',
+ accountNumber: '110001101000841',
+ txnBranchCode: '1',
+ depositOrWithdrawal: 'Deposit',
+ txnId: 8,
+ txnDate: '15-01-2026',
+ txnTime: '11:32:00',
+ txnAmount: 950000,
+ instrumentType: 'Cash',
+ instrumentId: 'CASH',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'ARJUN DESHMUKH',
+ detectedOn: '16-01-2026',
+ flaggedBy: 'AML Engine — Rule R-007',
+ riskCategory: 'structuring',
+ narrative:
+ 'Three sub-₹10L cash deposits within 5 days totalling ₹28.4L; pattern indicative of structuring to avoid CTR threshold.',
+ status: 'po-review'
+ },
+ {
+ reportType: 'STR',
+ reportRefNo: 102,
+ branchCode: '7',
+ accountNumber: '110001101000922',
+ txnBranchCode: '7',
+ depositOrWithdrawal: 'Deposit',
+ txnId: 9,
+ txnDate: '21-01-2026',
+ txnTime: '14:18:30',
+ txnAmount: 4800000,
+ instrumentType: 'NEFT',
+ instrumentId: 'N1264821',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'LAKSHMI IYER',
+ detectedOn: '22-01-2026',
+ flaggedBy: 'Branch Compliance Officer',
+ riskCategory: 'dormant-revival',
+ narrative:
+ 'Account dormant for 18 months suddenly received ₹48L NEFT followed by 4 immediate cash withdrawals of ₹2L each.',
+ status: 'filed',
+ poRemarks: 'STR filed with FIU-IND. Acknowledgement no: STR-2026-9821.'
+ },
+ {
+ reportType: 'STR',
+ reportRefNo: 103,
+ branchCode: '2',
+ accountNumber: '110001302001012',
+ txnBranchCode: '2',
+ depositOrWithdrawal: 'Withdrawal',
+ txnId: 10,
+ txnDate: '23-01-2026',
+ txnTime: '15:42:18',
+ txnAmount: 1800000,
+ instrumentType: 'RTGS',
+ instrumentId: 'R889013',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'SAMEER KAPOOR',
+ detectedOn: '24-01-2026',
+ flaggedBy: 'AML Engine — Rule R-014',
+ riskCategory: 'high-risk-country',
+ narrative:
+ 'RTGS transfer to beneficiary in jurisdiction listed under FATF grey list (Cayman Islands intermediary). KYC documentation incomplete.',
+ status: 'draft'
+ },
+ {
+ reportType: 'STR',
+ reportRefNo: 104,
+ branchCode: '13',
+ accountNumber: '110001352001145',
+ txnBranchCode: '13',
+ depositOrWithdrawal: 'Deposit',
+ txnId: 11,
+ txnDate: '08-01-2026',
+ txnTime: '12:55:42',
+ txnAmount: 6500000,
+ instrumentType: 'Cash',
+ instrumentId: 'CASH',
+ nonCustomerRef: '',
+ relationshipFlag: 'Customer',
+ thirdPartyPan: '',
+ declaration: 'NA',
+ customerName: 'MANMOHAN SINGH GILL',
+ detectedOn: '09-01-2026',
+ flaggedBy: 'AML Engine — Rule R-022',
+ riskCategory: 'unusual-pattern',
+ narrative:
+ 'Account avg monthly turnover ₹1.5L; received ₹65L cash in single deposit, source-of-funds documentation pending.',
+ status: 'po-review'
+ }
+];
+
+/** ── KC1 sample individual customers ── */
+export const KC1_INDIVIDUALS: KycIndividual[] = [
+ {
+ reportRefNo: 1,
+ ucic: '10078',
+ pan: 'AAQPS8560J',
+ ckycNumber: '60011225033445',
+ firstName: 'RAVI',
+ middleName: 'KUMAR',
+ lastName: 'SHARMA',
+ fatherName: 'RAJESH KUMAR SHARMA',
+ motherName: 'SUNITA DEVI',
+ gender: 'Male',
+ dob: '10-08-1985',
+ mobileNumber: '91-9892011001',
+ email: 'ravi.sharma@example.com',
+ primaryAddress: 'FLAT 402 SUNRISE TOWERS MAROL ANDHERI EAST',
+ state: 'MH',
+ city: 'MUMBAI',
+ pincode: '400069',
+ customerType: 'Individual',
+ annualIncome: 750000,
+ occupation: 'Service',
+ onboardingDate: '15-03-2020',
+ lastKycDate: '12-04-2025',
+ riskLevel: 'Medium',
+ reportType: 'CTR',
+ otherIdentifierName: 'AADHAAR',
+ otherIdentifierNumber: 'XXXXXXXX1234'
+ },
+ {
+ reportRefNo: 2,
+ ucic: '10079',
+ pan: 'AKLPS9933P',
+ ckycNumber: '60022144558899',
+ firstName: 'PRIYA',
+ middleName: '',
+ lastName: 'PATEL',
+ fatherName: 'HARISH PATEL',
+ motherName: 'MEENA PATEL',
+ gender: 'Female',
+ dob: '22-03-1990',
+ mobileNumber: '91-9822055002',
+ email: 'priya.patel@example.com',
+ primaryAddress: 'B-12 SHIVAJI NAGAR FC ROAD',
+ state: 'MH',
+ city: 'PUNE',
+ pincode: '411005',
+ customerType: 'Individual',
+ annualIncome: 480000,
+ occupation: 'Self-Employed',
+ onboardingDate: '10-09-2023',
+ lastKycDate: '15-03-2025',
+ riskLevel: 'Low',
+ reportType: 'CTR',
+ otherIdentifierName: 'AADHAAR',
+ otherIdentifierNumber: 'XXXXXXXX2210'
+ },
+ {
+ reportRefNo: 3,
+ ucic: '20101',
+ pan: 'AAEPR9352L',
+ ckycNumber: '',
+ firstName: 'VIKRAM',
+ middleName: '',
+ lastName: 'REDDY',
+ fatherName: 'SURESH REDDY',
+ motherName: 'LAKSHMI REDDY',
+ gender: 'Male',
+ dob: '07-07-1982',
+ mobileNumber: '91-9845022004',
+ email: '',
+ primaryAddress: 'NO 23 4TH CROSS JAYANAGAR 5TH BLOCK',
+ state: 'KA',
+ city: 'BENGALURU',
+ pincode: '560041',
+ customerType: 'Individual',
+ annualIncome: 950000,
+ occupation: 'Profession',
+ onboardingDate: '12-11-2021',
+ lastKycDate: '20-02-2025',
+ riskLevel: 'Medium',
+ reportType: 'CTR',
+ otherIdentifierName: 'AADHAAR',
+ otherIdentifierNumber: 'XXXXXXXX6622'
+ },
+ {
+ reportRefNo: 4,
+ ucic: '40301',
+ pan: 'AHKPS5544Z',
+ ckycNumber: '60040301229988',
+ firstName: 'NIRAV',
+ middleName: '',
+ lastName: 'SHAH',
+ fatherName: 'JAYESH SHAH',
+ motherName: 'RITA SHAH',
+ gender: 'Male',
+ dob: '18-05-1992',
+ mobileNumber: '91-9824012010',
+ email: 'nirav.shah@example.com',
+ primaryAddress: 'A-401 ATLANTIS APARTMENTS SATELLITE ROAD',
+ state: 'GJ',
+ city: 'AHMEDABAD',
+ pincode: '380015',
+ customerType: 'Individual',
+ annualIncome: 1800000,
+ occupation: 'Service',
+ onboardingDate: '14-02-2018',
+ lastKycDate: '20-12-2024',
+ riskLevel: 'High',
+ reportType: 'CTR',
+ otherIdentifierName: 'PASSPORT',
+ otherIdentifierNumber: 'M5544321'
+ },
+ {
+ reportRefNo: 5,
+ ucic: '50402',
+ pan: 'AKHPV2244R',
+ ckycNumber: '',
+ firstName: 'KAVITA',
+ middleName: '',
+ lastName: 'VERMA',
+ fatherName: 'DEEPAK VERMA',
+ motherName: 'USHA VERMA',
+ gender: 'Female',
+ dob: '01-04-1988',
+ mobileNumber: '91-9415055014',
+ email: '',
+ primaryAddress: '45 CIVIL LINES MALL ROAD',
+ state: 'UP',
+ city: 'KANPUR',
+ pincode: '208001',
+ customerType: 'Individual',
+ annualIncome: 540000,
+ occupation: 'Service',
+ onboardingDate: '08-09-2022',
+ lastKycDate: '15-09-2024',
+ riskLevel: 'Low',
+ reportType: 'CTR',
+ otherIdentifierName: 'AADHAAR',
+ otherIdentifierNumber: 'XXXXXXXX2244'
+ }
+];
+
+/** ── KC2 sample entity customers ── */
+export const KC2_ENTITIES: KycEntity[] = [
+ {
+ reportRefNo: 11,
+ ucic: 'E10101',
+ entityName: 'BHARAT TRADING COMPANY',
+ panNumber: 'ABCFB1234N',
+ gstin: '27ABCFB1234N1ZT',
+ mobileNumber: '91-9892011010',
+ address: 'GALA 22 LOWER PAREL',
+ state: 'MH',
+ city: 'MUMBAI',
+ pincode: '400013',
+ dateOfIncorporation: '24-03-2008',
+ customerType: 'Sole Proprietor',
+ riskLevel: 'High',
+ onboardingDate: '24-03-2008',
+ lastKycDate: '03-09-2025',
+ isSoleProprietor: true,
+ ownerUcic: '10078',
+ ownerName: 'RAVI KUMAR SHARMA',
+ reportType: 'CTR'
+ },
+ {
+ reportRefNo: 12,
+ ucic: 'E10102',
+ entityName: 'NEXTGEN TECH PVT LTD',
+ panNumber: 'AABCN5566K',
+ gstin: '27AABCN5566K1Z2',
+ mobileNumber: '91-9824012011',
+ address: 'TOWER B SUNRISE BUSINESS PARK ANDHERI EAST',
+ state: 'MH',
+ city: 'MUMBAI',
+ pincode: '400093',
+ dateOfIncorporation: '18-09-2015',
+ customerType: 'Private Limited',
+ riskLevel: 'Medium',
+ onboardingDate: '18-09-2015',
+ lastKycDate: '12-08-2025',
+ isSoleProprietor: false,
+ ownerUcic: '',
+ ownerName: '',
+ reportType: 'CTR'
+ }
+];
diff --git a/src/app/fiu/fiu-routing.module.ts b/src/app/fiu/fiu-routing.module.ts
new file mode 100644
index 000000000..273536e54
--- /dev/null
+++ b/src/app/fiu/fiu-routing.module.ts
@@ -0,0 +1,29 @@
+/**
+ * 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/.
+ */
+
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { Route } from '../core/route/route.service';
+import { FiuComponent } from './fiu.component';
+
+const routes: Routes = [
+ Route.withShell([
+ {
+ path: 'fiu',
+ component: FiuComponent,
+ data: { title: 'FIU-IND', breadcrumb: 'FIU-IND' }
+ }
+ ])
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class FiuRoutingModule {}
diff --git a/src/app/fiu/fiu.component.html b/src/app/fiu/fiu.component.html
new file mode 100644
index 000000000..58ac6a00f
--- /dev/null
+++ b/src/app/fiu/fiu.component.html
@@ -0,0 +1,480 @@
+
+
+
+
+
+
+
+
+
Financial Intelligence Unit – India (FIU-IND)
+
+ {{ bank.name }} · {{ bank.branch }} · Reporting Month {{ bank.reportingMonth }}
+ · PMLA 2002 / FINnet 2.0
+
+
+
+
+
+
+
+
+
+ Dashboard
+
+
+ CTR — Cash Transactions
+
+
+ STR — Suspicious
+
+
+ Customer Cards
+
+
+ FINnet Files
+
+
+
+
+ @if (activeTab === 'dashboard') {
+
+
+
+
+
+
{{ totalReports | number }}
+
CTR Reports
+
{{ totalCtrTxns | number }} txns this cycle
+
+
+
+
+
+
₹{{ totalCtrAmount | number: '1.0-0' }}
+
Total CTR Amount
+
aggregate cash movement
+
+
+
+
+
+
{{ pendingStr | number }}
+
STR Pending
+
draft / PO review
+
+
+
+
+
+
{{ filedStr | number }}
+
STR Filed
+
acknowledged by FIU
+
+
+
+
+
+
{{ totalCustomers | number }}
+
Customers in Cycle
+
{{ kc1.length }} ind · {{ kc2.length }} entity
+
+
+
+
+
+
+
+
+ CTR — Cash Transaction Report
+
+
+ Threshold: ₹{{ bank.cashThreshold | number }} per single txn / aggregate per month
+
+
Due: 15 Feb 2026
+
Open
+
+
+
+ STR — Suspicious Transaction
+
+
Filing window: within 7 days of detection
+
+ Pending: {{ pendingStr }} · Filed: {{ filedStr }}
+
+
Open
+
+
+
FINnet 2.0 Files
+
6 CSVs: TC1 / TC2 / KC1 / KC2 / Account_Detail / Account_Person_Relation
+
+ STR XML for suspicious filings
+
Open
+
+
+
+ }
+
+
+ @if (activeTab === 'ctr') {
+
+
+
+
+
+ Type
+
+ All
+ Deposit
+ Withdrawal
+
+
+
+ Account contains
+
+
+
+ Min Amount (₹)
+
+
+
Reset
+
+
+
+
+
+
+ Ref
+ Account
+ Customer
+ Type
+ Date / Time
+ Amount
+ Instrument
+ Branch
+ Relationship
+
+
+
+ @for (t of filteredCtr; track t.txnId) {
+
+ {{ t.reportRefNo }}
+ {{ t.accountNumber }}
+ {{ t.customerName }}
+
+
+ {{ t.depositOrWithdrawal }}
+
+
+
+ {{ t.txnDate }} {{ t.txnTime }}
+
+
+ ₹{{ t.txnAmount | number: '1.0-0' }}
+
+
+ {{ t.instrumentType }} {{ t.instrumentId }}
+
+ {{ t.branchCode }}
+
+ {{
+ t.relationshipFlag
+ }}
+
+
+ } @empty {
+
+ No transactions match the filters.
+
+ }
+
+
+
+
+ }
+
+
+ @if (activeTab === 'str') {
+
+
+
+
+
+ Status
+
+ All
+ Draft
+ PO Review
+ Filed
+ Rejected
+
+
+
+ Risk Category
+
+ All
+ Structuring
+ Dormant Revival
+ High-Risk Country
+ Unusual Pattern
+ PEP Related
+ Sanctions Hit
+
+
+
Reset
+
+
+
+ @for (s of filteredStr; track s.reportRefNo) {
+
+
+
+ STR-{{ s.reportRefNo }}
+ {{ statusLabel(s.status) }}
+ {{ riskLabel(s.riskCategory) }}
+
+
+ {{ s.depositOrWithdrawal }}
+ ₹{{ s.txnAmount | number: '1.0-0' }}
+
+
+
+
+
+
Narrative
+
{{ s.narrative }}
+
+ @if (s.poRemarks) {
+
+ }
+
+
+ } @empty {
+
No STR records match the filters.
+ }
+
+
+ }
+
+
+ @if (activeTab === 'customers') {
+
+
+
+
+
KC1 — Individuals
+
+
+
+ Ref
+ UCIC
+ Name
+ Gender
+ DOB
+ PAN
+ Mobile
+ City
+ Annual Income
+ Risk
+
+
+
+ @for (k of kc1; track k.ucic) {
+
+ {{ k.reportRefNo }}
+ {{ k.ucic }}
+ {{ k.firstName }} {{ k.middleName }} {{ k.lastName }}
+ {{ k.gender }}
+ {{ k.dob }}
+ {{ k.pan }}
+ {{ k.mobileNumber }}
+ {{ k.city }}, {{ k.state }}
+ ₹{{ k.annualIncome | number }}
+
+ {{ k.riskLevel }}
+
+
+ }
+
+
+
+
+
+
KC2 — Entities
+
+
+
+ Ref
+ UCIC
+ Entity Name
+ PAN
+ GSTIN
+ City
+ Type
+ Owner (if Sole Prop)
+ Risk
+
+
+
+ @for (k of kc2; track k.ucic) {
+
+ {{ k.reportRefNo }}
+ {{ k.ucic }}
+ {{ k.entityName }}
+ {{ k.panNumber }}
+ {{ k.gstin }}
+ {{ k.city }}, {{ k.state }}
+ {{ k.customerType }}
+ {{ k.isSoleProprietor ? k.ownerName : '—' }}
+
+ {{ k.riskLevel }}
+
+
+ }
+
+
+
+
+ }
+
+
+ @if (activeTab === 'files') {
+
+
+
+
+
+
TC1.csv
+
ATM / card transaction template (header only — no card txns this cycle)
+
Download
+
+
+
TC2.csv
+
Branch / cash transactions ({{ ctrTxns.length }} rows)
+
+ Download
+
+
+
+
KC1.csv
+
Individual customer cards ({{ kc1.length }} rows)
+
+ Download
+
+
+
+
KC2.csv
+
Entity customer cards ({{ kc2.length }} rows)
+
+ Download
+
+
+
+
Account_Detail.csv
+
Per-account 12-month aggregates derived from CTR list
+
+ Download
+
+
+
+
Account_Person_Relation.csv
+
Account ↔ customer relationship mapping
+
+ Download
+
+
+
+
STR.xml
+
Suspicious Transaction Report ({{ filedStr + pendingStr }} suspects)
+
+ Download XML
+
+
+
+
+
+
+ Note: 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.
+
+
+ }
+
+
+
{{ toastMsg }}
+
diff --git a/src/app/fiu/fiu.component.scss b/src/app/fiu/fiu.component.scss
new file mode 100644
index 000000000..4770586b9
--- /dev/null
+++ b/src/app/fiu/fiu.component.scss
@@ -0,0 +1,894 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --f-navy: #0b1e3d;
+ --f-blue: #1d4ed8;
+ --f-blue-l: #eff6ff;
+ --f-teal: #0e7e6b;
+ --f-teal-l: #e0f4f0;
+ --f-green: #15803d;
+ --f-green-l: #dcfce7;
+ --f-amber: #d97706;
+ --f-amber-l: #fef3c7;
+ --f-red: #c0392b;
+ --f-red-l: #fdecea;
+ --f-purple: #6d28d9;
+ --f-purple-l: #ede9fe;
+ --f-g50: #f8fafc;
+ --f-g100: #f1f5f9;
+ --f-g200: #e2e8f0;
+ --f-g300: #cbd5e1;
+ --f-g500: #64748b;
+ --f-g700: #334155;
+ --f-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ background: var(--f-g50);
+ color: var(--f-g900);
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+.fiu-root {
+ padding: 18px 22px;
+ max-width: 1500px;
+ margin: 0 auto;
+}
+
+.fiu-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+ margin-bottom: 16px;
+}
+
+.fiu-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+}
+
+.fiu-head-ic {
+ font-size: 28px;
+ color: var(--f-red);
+}
+
+.fiu-head-t {
+ margin: 0;
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--f-g900);
+}
+
+.fiu-head-s {
+ font-size: 12px;
+ color: var(--f-g500);
+}
+
+.fiu-head-meta {
+ display: flex;
+ gap: 22px;
+
+ > div {
+ display: flex;
+ flex-direction: column;
+
+ .fiu-meta-l {
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ color: var(--f-g500);
+ }
+
+ .fiu-meta-v {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--f-g900);
+ }
+ }
+}
+
+/* TABS */
+.fiu-tabs {
+ display: flex;
+ gap: 4px;
+ background: #fff;
+ border: 1px solid var(--f-g200);
+ border-radius: 10px;
+ padding: 4px;
+ margin-bottom: 16px;
+ overflow-x: auto;
+}
+
+.fiu-tab {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 9px 14px;
+ border: none;
+ background: transparent;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--f-g500);
+ cursor: pointer;
+ border-radius: 7px;
+ white-space: nowrap;
+ transition: all 0.15s;
+ font-family: inherit;
+
+ &:hover {
+ color: var(--f-g900);
+ }
+
+ &.active {
+ background: var(--f-red);
+ color: #fff;
+ box-shadow: 0 2px 6px rgb(192 57 43 / 25%);
+ }
+}
+
+.fiu-section {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+/* KPIs */
+.fiu-kpis {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 12px;
+}
+
+.fiu-kpi {
+ background: #fff;
+ border: 1px solid var(--f-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 14px rgb(0 0 0 / 10%);
+ }
+}
+
+.fiu-kpi-ic {
+ width: 42px;
+ height: 42px;
+ border-radius: 11px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.fiu-kpi--blue .fiu-kpi-ic {
+ background: var(--f-blue-l);
+ color: var(--f-blue);
+}
+
+.fiu-kpi--teal .fiu-kpi-ic {
+ background: var(--f-teal-l);
+ color: var(--f-teal);
+}
+
+.fiu-kpi--amber .fiu-kpi-ic {
+ background: var(--f-amber-l);
+ color: var(--f-amber);
+}
+
+.fiu-kpi--green .fiu-kpi-ic {
+ background: var(--f-green-l);
+ color: var(--f-green);
+}
+
+.fiu-kpi--purple .fiu-kpi-ic {
+ background: var(--f-purple-l);
+ color: var(--f-purple);
+}
+
+.fiu-kpi-n {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--f-g900);
+ line-height: 1.1;
+}
+
+.fiu-kpi-l {
+ font-size: 12px;
+ color: var(--f-g700);
+ font-weight: 500;
+ margin-top: 2px;
+}
+
+.fiu-kpi-sub {
+ font-size: 11px;
+ color: var(--f-g500);
+ margin-top: 1px;
+}
+
+/* COMPLIANCE CARDS */
+.fiu-compliance {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 12px;
+}
+
+.dc-card {
+ background: #fff;
+ border: 1px solid var(--f-g200);
+ border-radius: 12px;
+ padding: 14px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.dc-card-h {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--f-g900);
+ margin-bottom: 8px;
+}
+
+.dc-ok {
+ color: var(--f-green);
+}
+
+.dc-warn {
+ color: var(--f-amber);
+}
+
+.dc-info {
+ color: var(--f-blue);
+}
+
+.dc-meta {
+ font-size: 12px;
+ color: var(--f-g500);
+ margin: 2px 0;
+
+ strong {
+ color: var(--f-g900);
+ font-weight: 600;
+ }
+}
+
+.dc-btn {
+ margin-top: 10px;
+ padding: 6px 12px;
+ border: 1px solid var(--f-red);
+ background: var(--f-red-l);
+ color: var(--f-red);
+ border-radius: 8px;
+ font-size: 12px;
+ font-weight: 600;
+ cursor: pointer;
+ font-family: inherit;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+
+ &:hover {
+ background: var(--f-red);
+ color: #fff;
+ }
+}
+
+/* FORM HEADER */
+.fiu-form-meta {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 14px;
+ flex-wrap: wrap;
+}
+
+.fiu-form-title {
+ h3 {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 700;
+ color: var(--f-g900);
+ }
+
+ p {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--f-g500);
+ }
+}
+
+.fiu-action-group {
+ display: flex;
+ gap: 8px;
+}
+
+/* BUTTONS */
+.fiu-btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 8px 14px;
+ border: 1px solid var(--f-g300);
+ background: #fff;
+ border-radius: 8px;
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--f-g700);
+ cursor: pointer;
+ font-family: inherit;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--f-g50);
+ border-color: var(--f-g500);
+ }
+}
+
+.fiu-btn-csv {
+ background: #1d4ed8;
+ border-color: #1d4ed8;
+ color: #fff;
+
+ &:hover {
+ background: #1e40af;
+ border-color: #1e40af;
+ color: #fff;
+ }
+}
+
+.fiu-btn-xls {
+ background: #16a34a;
+ border-color: #16a34a;
+ color: #fff;
+
+ &:hover {
+ background: #15803d;
+ border-color: #15803d;
+ color: #fff;
+ }
+}
+
+.fiu-btn-xml {
+ background: var(--f-purple);
+ border-color: var(--f-purple);
+ color: #fff;
+
+ &:hover {
+ background: #5b21b6;
+ border-color: #5b21b6;
+ color: #fff;
+ }
+}
+
+.fiu-btn-g {
+ align-self: end;
+}
+
+/* INPUTS */
+.fiu-inp {
+ width: 100%;
+ padding: 8px 12px;
+ border: 1px solid var(--f-g200);
+ border-radius: 8px;
+ font-size: 13px;
+ background: #fff;
+ font-family: inherit;
+
+ &:focus {
+ outline: none;
+ border-color: var(--f-red);
+ box-shadow: 0 0 0 3px rgb(192 57 43 / 15%);
+ }
+}
+
+/* FILTERS */
+.fiu-filters {
+ background: #fff;
+ border: 1px solid var(--f-g200);
+ border-radius: 12px;
+ padding: 14px;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr auto;
+ gap: 12px;
+ align-items: end;
+}
+
+.fiu-filter {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ label {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--f-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+}
+
+/* TABLE */
+.fiu-tbl-wrap {
+ background: #fff;
+ border: 1px solid var(--f-g200);
+ border-radius: 12px;
+ overflow: auto;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.fiu-tbl-h {
+ background: var(--f-blue-l);
+ color: var(--f-blue);
+ padding: 10px 16px;
+ font-size: 12px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ border-bottom: 1px solid var(--f-g200);
+}
+
+.fiu-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 10px 12px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--f-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ background: var(--f-g50);
+ border-bottom: 1px solid var(--f-g200);
+ }
+
+ td {
+ padding: 10px 12px;
+ border-bottom: 1px solid var(--f-g100);
+ font-size: 13px;
+ color: var(--f-g700);
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--f-g50);
+ }
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 12px;
+ letter-spacing: 0.02em;
+}
+
+.muted {
+ color: var(--f-g500);
+ font-size: 11px;
+}
+
+.fiu-empty {
+ text-align: center;
+ padding: 30px;
+ color: var(--f-g500);
+ font-style: italic;
+}
+
+.fiu-empty-card {
+ background: #fff;
+ border: 1px dashed var(--f-g300);
+ border-radius: 12px;
+ padding: 50px;
+ text-align: center;
+ color: var(--f-g500);
+ font-size: 13px;
+}
+
+/* PILLS */
+.dir-pill {
+ display: inline-block;
+ padding: 2px 9px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+ background: var(--f-g100);
+ color: var(--f-g700);
+
+ &.deposit {
+ background: var(--f-green-l);
+ color: var(--f-green);
+ }
+
+ &.withdrawal {
+ background: var(--f-red-l);
+ color: var(--f-red);
+ }
+}
+
+.rel-pill {
+ display: inline-block;
+ padding: 2px 9px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+ background: var(--f-blue-l);
+ color: var(--f-blue);
+
+ &.non-cust {
+ background: var(--f-amber-l);
+ color: var(--f-amber);
+ }
+}
+
+.status-pill {
+ display: inline-block;
+ padding: 2px 10px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+
+ &.st-draft {
+ background: var(--f-g100);
+ color: var(--f-g700);
+ }
+
+ &.st-po-review {
+ background: var(--f-amber-l);
+ color: var(--f-amber);
+ }
+
+ &.st-filed {
+ background: var(--f-green-l);
+ color: var(--f-green);
+ }
+
+ &.st-rejected {
+ background: var(--f-red-l);
+ color: var(--f-red);
+ }
+}
+
+.risk-pill {
+ display: inline-block;
+ padding: 2px 10px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+ background: var(--f-purple-l);
+ color: var(--f-purple);
+
+ &.rk-structuring {
+ background: #fff7ed;
+ color: #9a3412;
+ }
+
+ &.rk-dormant-revival {
+ background: #ecfeff;
+ color: #155e75;
+ }
+
+ &.rk-high-risk-country {
+ background: var(--f-red-l);
+ color: var(--f-red);
+ }
+
+ &.rk-unusual-pattern {
+ background: var(--f-amber-l);
+ color: var(--f-amber);
+ }
+
+ &.rk-pep-related {
+ background: var(--f-purple-l);
+ color: var(--f-purple);
+ }
+
+ &.rk-sanctions-hit {
+ background: #fee2e2;
+ color: #991b1b;
+ }
+}
+
+.risk-tag {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 6px;
+ font-size: 10px;
+ font-weight: 700;
+ text-transform: uppercase;
+
+ &.rk-tag-low {
+ background: var(--f-green-l);
+ color: var(--f-green);
+ }
+
+ &.rk-tag-medium {
+ background: var(--f-amber-l);
+ color: var(--f-amber);
+ }
+
+ &.rk-tag-high {
+ background: var(--f-red-l);
+ color: var(--f-red);
+ }
+}
+
+/* STR cards */
+.fiu-str-list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.str-card {
+ background: #fff;
+ border: 1px solid var(--f-g200);
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+
+ &--filed {
+ border-color: #86efac;
+ }
+}
+
+.str-card-h {
+ padding: 12px 16px;
+ background: var(--f-g50);
+ border-bottom: 1px solid var(--f-g200);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+}
+
+.str-card-id {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.str-ref {
+ font-family: 'DM Mono', monospace;
+ font-size: 13px;
+ font-weight: 700;
+ color: var(--f-g900);
+}
+
+.str-card-amt {
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+
+ strong {
+ font-size: 16px;
+ color: var(--f-g900);
+ font-family: 'DM Mono', monospace;
+ }
+}
+
+.str-card-body {
+ padding: 14px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.str-meta {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 8px 18px;
+
+ > div {
+ display: flex;
+ flex-direction: column;
+
+ label {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--f-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-bottom: 1px;
+ }
+
+ span {
+ font-size: 13px;
+ color: var(--f-g900);
+ }
+ }
+}
+
+.str-narrative,
+.str-po-remarks {
+ background: var(--f-g50);
+ border-radius: 8px;
+ padding: 10px 14px;
+
+ label {
+ font-size: 10px;
+ font-weight: 700;
+ color: var(--f-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ margin-bottom: 4px;
+ }
+
+ p {
+ margin: 0;
+ font-size: 13px;
+ color: var(--f-g700);
+ line-height: 1.5;
+ }
+}
+
+.str-po-remarks {
+ background: var(--f-green-l);
+
+ label {
+ color: var(--f-green);
+ }
+
+ p {
+ color: #14532d;
+ }
+}
+
+/* FILES TAB */
+.files-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px;
+}
+
+.file-card {
+ background: #fff;
+ border: 1px solid var(--f-g200);
+ border-radius: 12px;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+
+ &--xml {
+ border-color: var(--f-purple);
+ background: linear-gradient(135deg, #fff 0%, #faf5ff 100%);
+ }
+}
+
+.file-card-h {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--f-g900);
+ font-family: 'DM Mono', monospace;
+}
+
+.file-card p {
+ margin: 0;
+ font-size: 12px;
+ color: var(--f-g500);
+ flex: 1;
+}
+
+.file-card .fiu-btn {
+ align-self: flex-start;
+}
+
+/* NOTE */
+.fiu-note {
+ background: var(--f-blue-l);
+ border: 1px solid #bfdbfe;
+ border-radius: 10px;
+ padding: 12px 16px;
+ display: flex;
+ gap: 10px;
+ font-size: 12px;
+ color: #1e3a8a;
+ line-height: 1.5;
+
+ fa-icon {
+ font-size: 14px;
+ flex-shrink: 0;
+ margin-top: 2px;
+ }
+}
+
+/* TOAST */
+.fiu-toast {
+ position: fixed;
+ bottom: 32px;
+ left: 50%;
+ transform: translateX(-50%) translateY(20px);
+ background: var(--f-navy);
+ color: #fff;
+ padding: 12px 22px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 500;
+ box-shadow: 0 6px 20px rgb(0 0 0 / 25%);
+ opacity: 0;
+ pointer-events: none;
+ transition: all 0.25s;
+ z-index: 1500;
+
+ &.show {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+ }
+}
+
+@media (width <= 1100px) {
+ .fiu-kpis {
+ grid-template-columns: repeat(3, 1fr);
+ }
+
+ .fiu-compliance {
+ grid-template-columns: 1fr;
+ }
+
+ .fiu-filters {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .files-grid {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .str-meta {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+@media (width <= 720px) {
+ .fiu-kpis {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .files-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .str-meta {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/app/fiu/fiu.component.ts b/src/app/fiu/fiu.component.ts
new file mode 100644
index 000000000..e0d404ee3
--- /dev/null
+++ b/src/app/fiu/fiu.component.ts
@@ -0,0 +1,547 @@
+/**
+ * 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/.
+ */
+
+import { Component } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+import {
+ CtrTransaction,
+ CTR_TXNS,
+ FIU_BANK,
+ KC1_INDIVIDUALS,
+ KC2_ENTITIES,
+ KycEntity,
+ KycIndividual,
+ StrStatus,
+ StrTransaction,
+ STR_TXNS
+} from './fiu-data';
+
+@Component({
+ selector: 'mifosx-fiu',
+ templateUrl: './fiu.component.html',
+ styleUrls: ['./fiu.component.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ FaIconComponent,
+ FormsModule
+ ]
+})
+export class FiuComponent {
+ /** Active tab */
+ activeTab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files' = 'dashboard';
+
+ bank = FIU_BANK;
+ ctrTxns: CtrTransaction[] = CTR_TXNS;
+ strTxns: StrTransaction[] = STR_TXNS;
+ kc1: KycIndividual[] = KC1_INDIVIDUALS;
+ kc2: KycEntity[] = KC2_ENTITIES;
+
+ /** CTR filters */
+ ctrFilterMode: '' | 'Deposit' | 'Withdrawal' = '';
+ ctrFilterAccount = '';
+ ctrFilterMinAmount = 0;
+
+ /** STR filters */
+ strFilterStatus: '' | StrStatus = '';
+ strFilterRisk: string = '';
+
+ /** Toast */
+ toastMsg = '';
+ toastVisible = false;
+ private toastTimer: any;
+
+ showTab(tab: 'dashboard' | 'ctr' | 'str' | 'customers' | 'files'): void {
+ this.activeTab = tab;
+ }
+
+ // -------------------- KPIs --------------------
+ get totalReports(): number {
+ return new Set(this.ctrTxns.map((t) => t.reportRefNo)).size;
+ }
+ get totalCtrTxns(): number {
+ return this.ctrTxns.length;
+ }
+ get totalCtrAmount(): number {
+ return this.ctrTxns.reduce((s, t) => s + t.txnAmount, 0);
+ }
+ get pendingStr(): number {
+ return this.strTxns.filter((s) => s.status === 'draft' || s.status === 'po-review').length;
+ }
+ get filedStr(): number {
+ return this.strTxns.filter((s) => s.status === 'filed').length;
+ }
+ get totalCustomers(): number {
+ return this.kc1.length + this.kc2.length;
+ }
+
+ // -------------------- Filtered lists --------------------
+ get filteredCtr(): CtrTransaction[] {
+ const acc = this.ctrFilterAccount.trim();
+ return this.ctrTxns.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;
+ return true;
+ });
+ }
+
+ get filteredStr(): StrTransaction[] {
+ return this.strTxns.filter((s) => {
+ if (this.strFilterStatus && s.status !== this.strFilterStatus) return false;
+ if (this.strFilterRisk && s.riskCategory !== this.strFilterRisk) return false;
+ return true;
+ });
+ }
+
+ resetCtrFilters(): void {
+ this.ctrFilterMode = '';
+ this.ctrFilterAccount = '';
+ this.ctrFilterMinAmount = 0;
+ }
+ resetStrFilters(): void {
+ this.strFilterStatus = '';
+ this.strFilterRisk = '';
+ }
+
+ // -------------------- Helpers --------------------
+ statusLabel(s: StrStatus): string {
+ return s === 'draft' ? 'Draft' : s === 'po-review' ? 'PO Review' : s === 'filed' ? 'Filed' : 'Rejected';
+ }
+ riskLabel(c: StrTransaction['riskCategory']): string {
+ const map: Record = {
+ structuring: 'Structuring',
+ 'dormant-revival': 'Dormant Revival',
+ 'high-risk-country': 'High-Risk Country',
+ 'unusual-pattern': 'Unusual Pattern',
+ 'pep-related': 'PEP Related',
+ 'sanctions-hit': 'Sanctions Hit'
+ };
+ return map[c];
+ }
+
+ // -------------------- Exports --------------------
+ /** TC2 — Branch / cash transaction CSV */
+ exportTc2(): void {
+ const headers = [
+ 'Report Type',
+ 'Report Reference Number',
+ 'Branch Code of Account',
+ 'Account Number',
+ 'Transaction Branch Code',
+ 'Deposit/Withdrawal',
+ 'Transaction ID',
+ 'Transaction Date',
+ 'Transaction Time',
+ 'Transaction Amount',
+ 'Instrument Type',
+ 'Instrument ID (Card Number/ Cash/ Cheque/ DD/ Others)',
+ 'Non-Customer Reference Number',
+ 'Relationship Flag',
+ 'Third Party PAN (Non-Customer)',
+ 'Declaration (If Third Party PAN (Non-Customer) is not available)'
+ ];
+ const rows = [headers];
+ for (const t of this.filteredCtr) {
+ rows.push([
+ t.reportType,
+ t.reportRefNo.toString(),
+ t.branchCode,
+ t.accountNumber,
+ t.txnBranchCode,
+ t.depositOrWithdrawal,
+ t.txnId.toString(),
+ t.txnDate.replace(/-/g, '/'),
+ t.txnTime,
+ t.txnAmount.toString(),
+ t.instrumentType,
+ t.instrumentId,
+ t.nonCustomerRef,
+ t.relationshipFlag,
+ t.thirdPartyPan,
+ t.declaration
+ ]);
+ }
+ this.saveCsv(rows, 'TC2.csv');
+ this.toast(`TC2 exported (${this.filteredCtr.length} rows)`);
+ }
+
+ /** TC1 header-only template (UI doesn't track ATM/card txns) */
+ exportTc1(): void {
+ const headers = [
+ 'Relationship Flag',
+ 'Transaction Status',
+ 'Transaction ID',
+ 'Transaction Date',
+ 'Transaction Time',
+ 'Deposit/Withdrawal',
+ 'Transaction Amount',
+ 'Account Number',
+ 'Account Type',
+ 'Card Type',
+ 'Card Number',
+ 'Card Issuing Country',
+ 'Card Issuing Bank',
+ 'Report Reference Number',
+ 'Switch Provider Transaction ID',
+ 'ATM ID/ CAM ID',
+ 'Branch Code of Account',
+ 'Report Type'
+ ];
+ this.saveCsv([headers], 'TC1.csv');
+ this.toast('TC1 template exported (header only)');
+ }
+
+ /** KC1 — Individual customers */
+ exportKc1(): void {
+ const headers = [
+ 'Report Reference Number',
+ 'UCIC',
+ 'Customer ID',
+ 'PAN',
+ 'Declaration (If PAN is not available)',
+ 'CKYC Number',
+ 'Declaration (If CKYC is not available)',
+ 'Passport Number',
+ 'Voter ID',
+ 'Other Occupation',
+ 'Drivers License Number',
+ 'NREGA Card',
+ 'DIN / DPIN',
+ 'First Name',
+ 'Middle Name',
+ 'Last Name',
+ 'Declaration (If Last name is not available)',
+ 'Name of Father',
+ 'Name of Mother',
+ 'Spouse/Partner Name',
+ 'Gender',
+ 'Date of Birth',
+ 'Nationality',
+ 'Mobile Number',
+ 'Alternate Mobile Number',
+ 'Telephone Number',
+ 'Email ID',
+ 'Primary Address 1',
+ 'Primary Address Country',
+ 'Primary Address Pin Code',
+ 'Primary Address Locality',
+ 'Primary Address State',
+ 'Primary Address District',
+ 'Primary Address City',
+ 'Customer Type',
+ 'Annual Income (INR)',
+ 'Occupation',
+ 'Date of Customer On-boarding',
+ 'Date of Last KYC / re-KYC',
+ 'Customer Risk Level',
+ 'Report Type',
+ 'Other Identifier Name',
+ 'Other Identifier Number',
+ 'Primary Entity'
+ ];
+ const rows = [headers];
+ for (const k of this.kc1) {
+ rows.push([
+ k.reportRefNo.toString(),
+ k.ucic,
+ k.ucic,
+ k.pan,
+ k.pan ? '' : 'NA',
+ k.ckycNumber,
+ k.ckycNumber ? '' : 'NA',
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ k.firstName,
+ k.middleName,
+ k.lastName,
+ '',
+ k.fatherName,
+ k.motherName,
+ '',
+ k.gender,
+ k.dob.replace(/-/g, '/'),
+ '97',
+ k.mobileNumber,
+ '',
+ '',
+ k.email,
+ k.primaryAddress,
+ '95',
+ k.pincode,
+ k.primaryAddress.split(' ').slice(0, 4).join(' '),
+ k.state,
+ '501',
+ k.city,
+ k.customerType,
+ k.annualIncome.toString(),
+ k.occupation,
+ k.onboardingDate.replace(/-/g, '/'),
+ k.lastKycDate.replace(/-/g, '/'),
+ k.riskLevel,
+ k.reportType,
+ k.otherIdentifierName || '',
+ k.otherIdentifierNumber || '',
+ 'Yes'
+ ]);
+ }
+ this.saveCsv(rows, 'KC1.csv');
+ this.toast(`KC1 exported (${this.kc1.length} individuals)`);
+ }
+
+ /** KC2 — Entity customers */
+ exportKc2(): void {
+ const headers = [
+ 'Report Reference Number',
+ 'UCIC',
+ 'Entity Name',
+ 'Company ID Number',
+ 'Company ID Type',
+ 'PAN Number',
+ 'Declaration (If PAN is not available)',
+ 'TAN',
+ 'GSTIN',
+ 'IEC',
+ 'Declaration (If IEC is not available)',
+ 'No identifier available',
+ 'Telephone Number',
+ 'Mobile Number',
+ 'Email ID',
+ 'Company Website',
+ 'Country',
+ 'Address Line 1',
+ 'PIN Code',
+ 'Locality',
+ 'State',
+ 'District',
+ 'City / Village / Town',
+ 'Date Of Incorporation',
+ 'Customer Type',
+ 'Last KYC Date',
+ 'Onboarding Date',
+ 'Customer Risk Level',
+ 'Report Type',
+ 'Sole Proprietor Firm',
+ 'UCIC of Owner of Sole Proprietor Firm',
+ 'Name of Owner of Sole Proprietor Firm'
+ ];
+ const rows = [headers];
+ for (const k of this.kc2) {
+ rows.push([
+ k.reportRefNo.toString(),
+ k.ucic,
+ k.entityName,
+ '',
+ '',
+ k.panNumber,
+ '',
+ '',
+ k.gstin,
+ '',
+ 'NA',
+ '',
+ '',
+ k.mobileNumber,
+ '',
+ '',
+ '95',
+ k.address,
+ k.pincode,
+ k.address.split(' ').slice(0, 4).join(' '),
+ k.state,
+ '501',
+ k.city,
+ k.dateOfIncorporation.replace(/-/g, '/'),
+ k.customerType,
+ k.lastKycDate.replace(/-/g, '/'),
+ k.onboardingDate.replace(/-/g, '/'),
+ k.riskLevel,
+ k.reportType,
+ k.isSoleProprietor ? 'Yes' : 'No',
+ k.ownerUcic,
+ k.ownerName
+ ]);
+ }
+ this.saveCsv(rows, 'KC2.csv');
+ this.toast(`KC2 exported (${this.kc2.length} entities)`);
+ }
+
+ /** Account_Detail.csv — per-report account aggregates (12-month) */
+ exportAccountDetail(): void {
+ const headers = [
+ 'Report Type',
+ 'Report Reference Number',
+ 'Account Type',
+ 'Account Number',
+ 'Branch Code of Account',
+ 'Date of Account Opening',
+ 'Date of Account Closing',
+ 'Account Status',
+ 'Reason for Account Freeze',
+ 'No Of Debits (In last 12 months)',
+ 'Total Debit Amount (In last 12 months)',
+ 'No Of Credits (In last 12 months)',
+ 'Total Credit Amount (In last 12 months)',
+ 'No Of Cash Transaction (In last 12 months)',
+ 'Total Cash Deposit (In last 12 months)',
+ 'Total Cash Withdrawal (In last 12 months) Amount'
+ ];
+ const rows = [headers];
+ // Build one aggregate row per (reportRefNo, accountNumber) pair
+ const seen = new Set();
+ for (const t of this.ctrTxns) {
+ const key = `${t.reportRefNo}|${t.accountNumber}`;
+ if (seen.has(key)) continue;
+ seen.add(key);
+ const sameKey = this.ctrTxns.filter(
+ (x) => x.reportRefNo === t.reportRefNo && x.accountNumber === t.accountNumber
+ );
+ const debits = sameKey.filter((x) => x.depositOrWithdrawal === 'Withdrawal');
+ const credits = sameKey.filter((x) => x.depositOrWithdrawal === 'Deposit');
+ const cashTxns = sameKey.filter((x) => x.instrumentType === 'Cash');
+ rows.push([
+ t.reportType,
+ t.reportRefNo.toString(),
+ '212',
+ t.accountNumber,
+ t.branchCode,
+ '20/05/2018',
+ '',
+ 'ACTIVE',
+ '',
+ debits.length.toString(),
+ debits.reduce((s, x) => s + x.txnAmount, 0).toString(),
+ credits.length.toString(),
+ credits.reduce((s, x) => s + x.txnAmount, 0).toString(),
+ cashTxns.length.toString(),
+ cashTxns
+ .filter((x) => x.depositOrWithdrawal === 'Deposit')
+ .reduce((s, x) => s + x.txnAmount, 0)
+ .toString(),
+ cashTxns
+ .filter((x) => x.depositOrWithdrawal === 'Withdrawal')
+ .reduce((s, x) => s + x.txnAmount, 0)
+ .toString()
+ ]);
+ }
+ this.saveCsv(rows, 'Account_Detail.csv');
+ this.toast(`Account_Detail exported (${rows.length - 1} accounts)`);
+ }
+
+ /** Account_Person_Relation.csv — account ↔ customer mapping */
+ exportAccountPersonRelation(): void {
+ const headers = [
+ 'Report Type',
+ 'Report Reference Number',
+ 'Branch Code of Account',
+ 'Account Number',
+ 'Relationship Type',
+ 'Unique Reference Number',
+ 'Individual / Non-individual',
+ 'Name of Non-customer'
+ ];
+ const rows = [headers];
+ const seen = new Set();
+ for (const t of this.ctrTxns) {
+ const key = `${t.reportRefNo}|${t.accountNumber}`;
+ if (seen.has(key)) continue;
+ seen.add(key);
+ // Find matching KC1 by reportRefNo (best-effort dummy mapping)
+ const k = this.kc1.find((x) => x.reportRefNo === t.reportRefNo);
+ rows.push([
+ t.reportType,
+ t.reportRefNo.toString(),
+ t.branchCode,
+ t.accountNumber,
+ '1',
+ k ? k.ucic : '0',
+ 'Individual',
+ t.relationshipFlag === 'Non-Customer' ? t.customerName || '' : ''
+ ]);
+ }
+ this.saveCsv(rows, 'Account_Person_Relation.csv');
+ this.toast(`Account_Person_Relation exported (${rows.length - 1} rows)`);
+ }
+
+ /** STR XML stub (FINnet 2.0 STR XML) */
+ exportStrXml(): void {
+ const xml: string[] = [];
+ xml.push('');
+ xml.push(``);
+ xml.push(` `);
+ xml.push(` `);
+ for (const s of this.filteredStr) {
+ xml.push(` `);
+ xml.push(` ${s.accountNumber} `);
+ xml.push(
+ ` `
+ );
+ xml.push(` ${s.riskCategory} `);
+ xml.push(` `);
+ xml.push(` ${this.escapeXml(s.narrative)} `);
+ if (s.poRemarks) xml.push(` ${this.escapeXml(s.poRemarks)} `);
+ xml.push(' ');
+ }
+ xml.push(' ');
+ const blob = new Blob([xml.join('\n')], { type: 'application/xml;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `STR-${this.bank.reportingMonth.replace(/\s+/g, '-')}.xml`;
+ a.click();
+ URL.revokeObjectURL(url);
+ this.toast(`STR XML exported (${this.filteredStr.length} suspects)`);
+ }
+
+ /** Bundle: all 6 CSVs zipped — emitted as separate downloads sequentially */
+ exportAll(): void {
+ setTimeout(() => this.exportTc1(), 0);
+ setTimeout(() => this.exportTc2(), 200);
+ setTimeout(() => this.exportKc1(), 400);
+ setTimeout(() => this.exportKc2(), 600);
+ setTimeout(() => this.exportAccountDetail(), 800);
+ setTimeout(() => this.exportAccountPersonRelation(), 1000);
+ }
+
+ private escapeXml(s: string): string {
+ return (s || '')
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ private saveCsv(rows: string[][], filename: string): void {
+ const escape = (v: string): string => {
+ const s = (v || '').replace(/"/g, '""');
+ if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
+ return s;
+ };
+ const csv = rows.map((r) => r.map(escape).join(',')).join('\n');
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+
+ toast(msg: string): void {
+ this.toastMsg = msg;
+ this.toastVisible = true;
+ if (this.toastTimer) clearTimeout(this.toastTimer);
+ this.toastTimer = setTimeout(() => (this.toastVisible = false), 2800);
+ }
+}
diff --git a/src/app/fiu/fiu.module.ts b/src/app/fiu/fiu.module.ts
new file mode 100644
index 000000000..6dd2f5776
--- /dev/null
+++ b/src/app/fiu/fiu.module.ts
@@ -0,0 +1,22 @@
+/**
+ * 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/.
+ */
+
+import { NgModule } from '@angular/core';
+
+import { SharedModule } from '../shared/shared.module';
+import { FiuRoutingModule } from './fiu-routing.module';
+import { FiuComponent } from './fiu.component';
+
+@NgModule({
+ imports: [
+ SharedModule,
+ FiuRoutingModule,
+ FiuComponent
+ ]
+})
+export class FiuModule {}
diff --git a/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.html b/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.html
new file mode 100644
index 000000000..6d76cb648
--- /dev/null
+++ b/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.html
@@ -0,0 +1,646 @@
+
+
+
+
+
+
+
+
+
Accounting Reports — {{ currentReport.name }}
+
{{ currentReport.description }}
+
+
+
+ {{ bank.name }}
+
+ FY {{ bank.fiscalYear }}
+
+ ✕
+
+
+
+
+
+ Report:
+
+ {{ r.name }}
+
+ {{ currentReport.group }}
+
+
+
+
+
+
+
+
Total Income
+
₹ {{ totalIncome / 100 | number: '1.2-2' }} Cr
+
{{ totalIncome | number: '1.2-2' }} Lakh
+
+
+
Total Expenditure
+
₹ {{ totalExpenditure / 100 | number: '1.2-2' }} Cr
+
{{ totalExpenditure | number: '1.2-2' }} Lakh
+
+
= 0 ? 'good' : 'alert'">
+
Net Profit / (Loss)
+
+ {{ netProfit >= 0 ? '+' : '' }}₹ {{ netProfit / 100 | number: '1.2-2' }} Cr
+
+
Margin: {{ netProfitPct.toFixed(2) }}%
+
+
+
Reporting Period
+
{{ bank.period }}
+
{{ bank.branchCount }} branches consolidated
+
+
+
+
+
+
Profit & Loss A/c — FY {{ bank.fiscalYear }} (₹ in Lakhs)
+
+ Export CSV
+
+
+
+
+
+
+
Income
+
+
+
+
+
+ {{ sub }}
+ ₹ Lakh
+
+
+
+
+ {{ l.code }}
+ {{ l.label }}
+ {{ l.amount | number: '1.2-2' }}
+
+
+
+ Sub-total — {{ sub }}
+
+
+ {{ subTotal('INCOME', sub) | number: '1.2-2' }}
+
+
+
+
+
+
+ Total Income
+
+ {{ totalIncome | number: '1.2-2' }}
+
+
+
+
+
+
+
+
+
+
Expenditure
+
+
+
+
+
+ {{ sub }}
+ ₹ Lakh
+
+
+
+
+ {{ l.code }}
+ {{ l.label }}
+ {{ l.amount | number: '1.2-2' }}
+
+
+
+ Sub-total — {{ sub }}
+
+
+ {{ subTotal('EXPENDITURE', sub) | number: '1.2-2' }}
+
+
+
+
+
+
+ Total Expenditure
+
+ {{ totalExpenditure | number: '1.2-2' }}
+
+
+
+
+
+
+
+
+
+
= 0 ? 'good' : 'alert'">
+
+ = 0 ? 'arrow-up' : 'arrow-down'">
+ Net {{ netProfit >= 0 ? 'Profit' : 'Loss' }} for the Year
+
+
+ ₹ {{ netProfit | number: '1.2-2' }} Lakh
+ ({{ netProfitPct.toFixed(2) }}% of Income)
+
+
+
+
+
+
+
+
+
+
Total Capital & Liabilities
+
₹ {{ totalLiabilities / 100 | number: '1.2-2' }} Cr
+
{{ totalLiabilities | number: '1.2-2' }} Lakh
+
+
+
Total Assets
+
₹ {{ totalAssets / 100 | number: '1.2-2' }} Cr
+
{{ totalAssets | number: '1.2-2' }} Lakh
+
+
+
B/S Tally
+
+ {{ totalLiabilities === totalAssets ? 'BALANCED' : 'MISMATCH' }}
+
+
Diff: {{ totalAssets - totalLiabilities | number: '1.2-2' }}
+
+
+
As On Date
+
{{ bank.asOnDate }}
+
{{ bank.quarter }}
+
+
+
+
+
+
Balance Sheet — As on {{ bank.asOnDate }} (₹ in Lakhs)
+
+ Export CSV
+
+
+
+
+
+
+
Capital & Liabilities
+
+
+
+
+
+ {{ g }}
+ ₹ Lakh
+
+
+
+
+ {{ l.code }}
+ {{ l.label }}
+ {{ l.amount | number: '1.2-2' }}
+
+
+ Sub-total
+
+ {{ bsSubTotal('CAPITAL-LIAB', g) | number: '1.2-2' }}
+
+
+
+
+
+
+ Total Capital & Liabilities
+
+ {{ totalLiabilities | number: '1.2-2' }}
+
+
+
+
+
+
+
+
+
+
Assets
+
+
+
+
+
+ {{ g }}
+ ₹ Lakh
+
+
+
+
+ {{ l.code }}
+ {{ l.label }}
+ {{ l.amount | number: '1.2-2' }}
+
+
+ Sub-total
+
+ {{ bsSubTotal('ASSETS', g) | number: '1.2-2' }}
+
+
+
+
+
+
+ Total Assets
+
+ {{ totalAssets | number: '1.2-2' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total Liab — {{ bank.fiscalYear }}
+
₹ {{ totalLiabilities / 100 | number: '1.2-2' }} Cr
+
vs {{ totalLiabilitiesPrev / 100 | number: '1.2-2' }} Cr (PY)
+
+
+
Total Assets — {{ bank.fiscalYear }}
+
₹ {{ totalAssets / 100 | number: '1.2-2' }} Cr
+
vs {{ totalAssetsPrev / 100 | number: '1.2-2' }} Cr (PY)
+
+
+
Asset Growth %
+
+{{ variationPct(totalAssets, totalAssetsPrev).toFixed(2) }}%
+
{{ variation(totalAssets, totalAssetsPrev) | number: '1.2-2' }} Lakh
+
+
+
Deposit Growth %
+
+ +{{
+ variationPct(
+ bsSubTotal('CAPITAL-LIAB', 'Deposits'),
+ bsSubTotal('CAPITAL-LIAB', 'Deposits', true)
+ ).toFixed(2)
+ }}%
+
+
All deposit categories
+
+
+
+
+
+
+ Comparative Balance Sheet — FY {{ bank.fiscalYear }} vs FY {{ bank.prevFiscalYear }}
+
+
+ Export CSV
+
+
+
+
+
+
+
+ Code
+ Particulars
+ Side / Group
+ FY {{ bank.fiscalYear }}
+ FY {{ bank.prevFiscalYear }}
+ Variation
+ Variation %
+
+
+
+
+
+ {{ l.code }}
+ {{ l.label }}
+
+ {{
+ l.group
+ }}
+
+
+ {{ l.amount | number: '1.2-2' }}
+
+ {{ l.prevAmount | number: '1.2-2' }}
+ = 0 ? 'pos' : 'neg'">
+ {{ variation(l.amount, l.prevAmount) >= 0 ? '+' : ''
+ }}{{ variation(l.amount, l.prevAmount) | number: '1.2-2' }}
+
+ = 0 ? 'pos' : 'neg'">
+
+ {{ variationPct(l.amount, l.prevAmount) >= 0 ? '+' : ''
+ }}{{ variationPct(l.amount, l.prevAmount).toFixed(2) }}%
+
+
+
+
+
+ TOTAL CAPITAL & LIABILITIES
+
+ {{ totalLiabilities | number: '1.2-2' }}
+
+ {{ totalLiabilitiesPrev | number: '1.2-2' }}
+ = 0 ? 'pos' : 'neg'">
+ {{ variation(totalLiabilities, totalLiabilitiesPrev) | number: '1.2-2' }}
+
+ = 0 ? 'pos' : 'neg'">
+ +{{ variationPct(totalLiabilities, totalLiabilitiesPrev).toFixed(2) }}%
+
+
+
+ TOTAL ASSETS
+
+ {{ totalAssets | number: '1.2-2' }}
+
+ {{ totalAssetsPrev | number: '1.2-2' }}
+ {{ variation(totalAssets, totalAssetsPrev) | number: '1.2-2' }}
+
+ +{{ variationPct(totalAssets, totalAssetsPrev).toFixed(2) }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
GL Accounts
+
{{ glReport.length }}
+
Filtered: {{ filteredGl.length }}
+
+
+
Total Credits
+
₹ {{ glTotalCredit / 100 | number: '1.2-2' }} Cr
+
{{ glTotalCredit | number: '1.2-2' }} Lakh
+
+
+
Total Debits
+
₹ {{ glTotalDebit / 100 | number: '1.2-2' }} Cr
+
{{ glTotalDebit | number: '1.2-2' }} Lakh
+
+
+
Period
+
+ {{ bank.quarterFrom }}
+
+ {{ bank.quarterTo }}
+
+
Consolidated across {{ bank.branchCount }} branches
+
+
+
+
+
+
GL Wise Debit / Credit Report
+
+ Export CSV
+
+
+
+
+
+ Group
+
+ All Groups
+ Asset
+ Liability
+ Income
+ Expense
+
+
+
+ Search (Code or Description)
+
+
+
+
+
+
+
+
+
+
+
+ A/C Code
+ Description
+ Group
+ Opening
+ Debit
+ Credit
+ Closing
+
+
+
+
+ {{ g.acCode }}
+ {{ g.description }}
+
+ {{ g.group }}
+
+ {{ g.opening | number: '1.2-2' }}
+ 0">{{ g.debit | number: '1.2-2' }}
+ 0">{{ g.credit | number: '1.2-2' }}
+
+ {{ g.closing | number: '1.2-2' }}
+
+
+
+ TOTAL
+ —
+
+ {{ glTotalDebit | number: '1.2-2' }}
+
+
+ {{ glTotalCredit | number: '1.2-2' }}
+
+ —
+
+
+ No GL accounts match the current filters.
+
+
+
+
+
+
+
+
+
+
+
+
Total Vouchers
+
{{ miscExp.length }}
+
Filtered: {{ filteredMisc.length }}
+
+
+
Total Withdrawal
+
₹ {{ miscTotalWithdrawal | number }}
+
Filtered subset
+
+
+
Total Deposit (Reversals)
+
₹ {{ miscTotalDeposit | number }}
+
Cancelled / refunded
+
+
+
Period
+
+ {{ bank.quarterFrom }}
+
+ {{ bank.quarterTo }}
+
+
Report Ref {{ bank.reportRefNo }}
+
+
+
+
+
+
Misc Expense Ledger — {{ bank.quarterFrom }} TO {{ bank.quarterTo }}
+
+ Export CSV
+
+
+
+
+
+ Branch
+
+
+
+ Mode
+
+ All
+ Cash
+ Cheque
+ Transfer
+ NEFT
+
+
+
+
+
+
+
+
+
+
+
+ Date
+ Branch
+ Voucher
+ CHQ_NO
+ Description
+ Mode
+ Withdrawal
+ Deposit
+ Balance
+
+
+
+
+ {{ r.txnDate }}
+
+ {{ r.branch }} ({{ r.branchCode }})
+
+ {{ r.voucherNo }}
+ {{ r.chequeNo }}
+ {{ r.description }}
+
+ {{ r.mode }}
+
+ 0">
+ {{ r.withdrawal > 0 ? (r.withdrawal | number) : '—' }}
+
+ 0">
+ {{ r.deposit > 0 ? (r.deposit | number) : '—' }}
+
+
+ {{ r.balance | number }}
+
+
+
+ GRAND TOTAL
+
+ {{ miscTotalWithdrawal | number }}
+
+
+ {{ miscTotalDeposit | number }}
+
+
+
+
+ No vouchers match the current filters.
+
+
+
+
+
+
Branch-wise Net Total
+
+
+
+
+ Branch
+ Net Withdrawal (₹)
+
+
+
+
+ {{ bt.branch }}
+ {{ bt.total | number }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.scss b/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.scss
new file mode 100644
index 000000000..3e56295d9
--- /dev/null
+++ b/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.scss
@@ -0,0 +1,706 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --acr-good: #15803d;
+ --acr-good-l: #dcfce7;
+ --acr-mid: #d97706;
+ --acr-mid-l: #fef3c7;
+ --acr-low: #c0392b;
+ --acr-low-l: #fdecea;
+ --acr-amber: #b45309;
+ --acr-amber-l: #fef3c7;
+ --acr-emerald: #059669;
+ --acr-emerald-l: #d1fae5;
+ --acr-blue: #2563eb;
+ --acr-blue-l: #dbeafe;
+ --acr-purple: #6d28d9;
+ --acr-purple-l: #ede9fe;
+ --acr-pink: #be185d;
+ --acr-pink-l: #fce7f3;
+ --acr-g50: #f8fafc;
+ --acr-g100: #f1f5f9;
+ --acr-g200: #e2e8f0;
+ --acr-g300: #cbd5e1;
+ --acr-g500: #64748b;
+ --acr-g700: #334155;
+ --acr-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ color: var(--acr-g900);
+}
+
+.acr-dlg {
+ background: #fff;
+ border-radius: 14px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ max-height: 92vh;
+}
+
+/* HEADER */
+.acr-head {
+ padding: 18px 22px;
+ border-bottom: 1px solid var(--acr-g200);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-shrink: 0;
+ background: linear-gradient(135deg, #fffbeb 0%, #fff 100%);
+}
+
+.acr-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ flex: 1;
+}
+
+.acr-head-ic {
+ width: 44px;
+ height: 44px;
+ border-radius: 11px;
+ background: var(--acr-amber-l);
+ color: var(--acr-amber);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.acr-title {
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+ color: var(--acr-g900);
+}
+
+.acr-sub {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--acr-g500);
+}
+
+.acr-head-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.acr-meta {
+ display: inline-flex;
+ align-items: center;
+ font-size: 11px;
+ color: var(--acr-g500);
+ padding: 5px 10px;
+ background: #fff;
+ border: 1px solid var(--acr-g200);
+ border-radius: 6px;
+
+ fa-icon {
+ color: var(--acr-amber);
+ margin-right: 4px;
+ }
+}
+
+.acr-close {
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ border: 1px solid var(--acr-g200);
+ background: #fff;
+ font-size: 14px;
+ color: var(--acr-g500);
+ cursor: pointer;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--acr-g100);
+ color: var(--acr-g900);
+ }
+}
+
+/* SELECTOR */
+.acr-sel-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 22px;
+ background: var(--acr-g50);
+ border-bottom: 1px solid var(--acr-g200);
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.acr-sel-l {
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--acr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-right: 4px;
+}
+
+.acr-sel-pill {
+ padding: 7px 16px;
+ border-radius: 999px;
+ border: 1px solid var(--acr-g200);
+ background: #fff;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--acr-g700);
+ transition: all 0.15s;
+ display: inline-flex;
+ align-items: center;
+
+ fa-icon {
+ color: var(--acr-g500);
+ }
+
+ &:hover {
+ background: var(--acr-amber-l);
+ border-color: var(--acr-amber);
+ color: var(--acr-amber);
+
+ fa-icon {
+ color: var(--acr-amber);
+ }
+ }
+
+ &.active {
+ background: var(--acr-amber);
+ border-color: var(--acr-amber);
+ color: #fff;
+ box-shadow: 0 2px 6px rgb(180 83 9 / 25%);
+
+ fa-icon {
+ color: #fff;
+ }
+ }
+}
+
+.acr-sel-group-tag {
+ margin-left: auto;
+ font-size: 10px;
+ font-weight: 700;
+ color: var(--acr-g500);
+ padding: 4px 10px;
+ background: #fff;
+ border: 1px solid var(--acr-g200);
+ border-radius: 6px;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+/* BODY */
+.acr-body {
+ padding: 18px 22px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ overflow: auto;
+ flex: 1;
+ background: var(--acr-g50);
+}
+
+/* STATS */
+.acr-stats {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 12px;
+}
+
+.acr-stat-card {
+ background: #fff;
+ border: 1px solid var(--acr-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+
+ &.alert {
+ background: var(--acr-low-l);
+ border-color: var(--acr-low);
+
+ .acr-stat-v {
+ color: var(--acr-low);
+ }
+ }
+
+ &.warn {
+ background: var(--acr-mid-l);
+ border-color: var(--acr-mid);
+
+ .acr-stat-v {
+ color: var(--acr-mid);
+ }
+ }
+
+ &.good {
+ background: var(--acr-good-l);
+ border-color: var(--acr-good);
+
+ .acr-stat-v {
+ color: var(--acr-good);
+ }
+ }
+}
+
+.acr-stat-l {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--acr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.acr-stat-v {
+ font-size: 22px;
+ font-weight: 700;
+ color: var(--acr-g900);
+ line-height: 1.2;
+ margin-top: 2px;
+}
+
+.acr-stat-meta {
+ font-size: 11px;
+ color: var(--acr-g500);
+ margin-top: 4px;
+}
+
+/* SECTION */
+.acr-section {
+ background: #fff;
+ border: 1px solid var(--acr-g200);
+ border-radius: 12px;
+ padding: 16px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.acr-sec-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+ flex-wrap: wrap;
+}
+
+.acr-sec-t {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--acr-g700);
+}
+
+.acr-sub-tt {
+ margin: 18px 0 8px;
+ font-size: 12px;
+ font-weight: 700;
+ color: var(--acr-g700);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ padding-bottom: 4px;
+ border-bottom: 1px dashed var(--acr-g200);
+}
+
+.acr-btn-xls {
+ font-size: 12px !important;
+ height: 34px !important;
+ color: #fff !important;
+ background: var(--acr-amber) !important;
+}
+
+/* P&L / B/S TWO-COLUMN GRID */
+.acr-pnl-grid,
+.acr-bs-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 14px;
+}
+
+.acr-pnl-side {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
+
+.acr-pnl-side-t {
+ margin: 0;
+ padding: 8px 12px;
+ font-size: 13px;
+ font-weight: 700;
+ border-radius: 6px;
+
+ &.income {
+ background: var(--acr-good-l);
+ color: var(--acr-good);
+ }
+
+ &.expense {
+ background: var(--acr-low-l);
+ color: var(--acr-low);
+ }
+}
+
+.acr-pnl-net {
+ margin-top: 14px;
+ padding: 14px 18px;
+ border-radius: 10px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 14px;
+
+ &.good {
+ background: var(--acr-good-l);
+ color: var(--acr-good);
+ }
+
+ &.alert {
+ background: var(--acr-low-l);
+ color: var(--acr-low);
+ }
+}
+
+.acr-pnl-net-l {
+ display: flex;
+ align-items: center;
+ font-weight: 700;
+}
+
+.acr-pnl-net-v {
+ font-weight: 700;
+ font-size: 16px;
+
+ .acr-pnl-net-pct {
+ margin-left: 6px;
+ font-size: 12px;
+ font-weight: 600;
+ opacity: 0.8;
+ }
+}
+
+/* FILTERS */
+.acr-filters {
+ display: flex;
+ gap: 12px;
+ padding: 12px 14px;
+ background: var(--acr-g50);
+ border: 1px solid var(--acr-g200);
+ border-radius: 8px;
+ margin-bottom: 12px;
+ align-items: flex-end;
+ flex-wrap: wrap;
+}
+
+.acr-f {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 160px;
+
+ label {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--acr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+
+ select,
+ input {
+ height: 32px;
+ padding: 0 10px;
+ border: 1px solid var(--acr-g200);
+ border-radius: 6px;
+ background: #fff;
+ font-family: inherit;
+ font-size: 12px;
+ color: var(--acr-g900);
+
+ &:focus {
+ outline: none;
+ border-color: var(--acr-amber);
+ box-shadow: 0 0 0 3px rgb(180 83 9 / 12%);
+ }
+ }
+}
+
+.acr-f-reset {
+ height: 32px;
+ width: 32px;
+ border-radius: 6px;
+ border: 1px solid var(--acr-g200);
+ background: #fff;
+ color: var(--acr-g500);
+ cursor: pointer;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--acr-low-l);
+ color: var(--acr-low);
+ border-color: var(--acr-low);
+ }
+}
+
+/* TABLE */
+.acr-tbl-wrap {
+ border: 1px solid var(--acr-g200);
+ border-radius: 8px;
+ overflow: auto;
+}
+
+.acr-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 10px 14px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--acr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ background: var(--acr-g50);
+ border-bottom: 1px solid var(--acr-g200);
+ white-space: nowrap;
+ }
+
+ td {
+ padding: 8px 14px;
+ border-bottom: 1px solid var(--acr-g100);
+ font-size: 13px;
+ color: var(--acr-g700);
+ vertical-align: middle;
+
+ &.pos {
+ color: var(--acr-good);
+ font-weight: 600;
+ }
+
+ &.neg {
+ color: var(--acr-low);
+ font-weight: 600;
+ }
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--acr-g50);
+ }
+
+ .row-sub-total td {
+ background: var(--acr-g100);
+ color: var(--acr-g900);
+ }
+
+ .row-total td {
+ background: var(--acr-amber-l);
+ color: var(--acr-g900);
+ font-weight: 700;
+ border-top: 2px solid var(--acr-amber);
+ }
+
+ .row-total.income td {
+ background: var(--acr-good-l);
+ border-top-color: var(--acr-good);
+ color: var(--acr-good);
+ }
+
+ .row-total.expense td {
+ background: var(--acr-low-l);
+ border-top-color: var(--acr-low);
+ color: var(--acr-low);
+ }
+
+ .acr-sub-h {
+ font-size: 11px;
+ color: var(--acr-amber);
+ font-weight: 700;
+ background: #fff;
+ border-bottom: 1px solid var(--acr-g200);
+ }
+}
+
+.cell-acc {
+ color: var(--acr-amber);
+ font-weight: 700;
+}
+
+/* GROUP PILLS */
+.grp-pill {
+ display: inline-block;
+ padding: 2px 9px;
+ border-radius: 6px;
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+
+ &.gl-group-asset {
+ background: var(--acr-blue-l);
+ color: var(--acr-blue);
+ }
+
+ &.gl-group-liability {
+ background: var(--acr-purple-l);
+ color: var(--acr-purple);
+ }
+
+ &.gl-group-income {
+ background: var(--acr-good-l);
+ color: var(--acr-good);
+ }
+
+ &.gl-group-expense {
+ background: var(--acr-low-l);
+ color: var(--acr-low);
+ }
+}
+
+.side-pill {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 6px;
+ font-size: 10px;
+ font-weight: 600;
+
+ &.side-liab {
+ background: var(--acr-low-l);
+ color: var(--acr-low);
+ }
+
+ &.side-asset {
+ background: var(--acr-good-l);
+ color: var(--acr-good);
+ }
+}
+
+.mode-pill {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 6px;
+ font-family: 'DM Mono', monospace;
+ font-size: 10px;
+ font-weight: 700;
+
+ &.mode-cash {
+ background: var(--acr-mid-l);
+ color: var(--acr-mid);
+ }
+
+ &.mode-cheque {
+ background: var(--acr-blue-l);
+ color: var(--acr-blue);
+ }
+
+ &.mode-trf {
+ background: var(--acr-purple-l);
+ color: var(--acr-purple);
+ }
+
+ &.mode-neft {
+ background: var(--acr-emerald-l);
+ color: var(--acr-emerald);
+ }
+}
+
+.muted {
+ color: var(--acr-g300);
+ font-weight: 400;
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 12px;
+}
+
+.empty {
+ text-align: center;
+ color: var(--acr-g500);
+ padding: 24px !important;
+ font-style: italic;
+}
+
+.pos {
+ color: var(--acr-good);
+}
+
+.neg {
+ color: var(--acr-low);
+}
+
+/* DIALOG FOOTER */
+.acr-footer {
+ padding: 12px 22px;
+ border-top: 1px solid var(--acr-g200);
+ background: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.acr-footer-l {
+ font-size: 12px;
+ color: var(--acr-g500);
+ display: flex;
+ align-items: center;
+
+ fa-icon {
+ color: var(--acr-amber);
+ margin-right: 4px;
+ }
+}
+
+/* RESPONSIVE */
+@media (width <= 1100px) {
+ .acr-stats {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .acr-pnl-grid,
+ .acr-bs-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (width <= 720px) {
+ .acr-stats {
+ grid-template-columns: 1fr;
+ }
+
+ .acr-head,
+ .acr-sel-bar,
+ .acr-footer {
+ flex-wrap: wrap;
+ }
+}
diff --git a/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.ts b/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.ts
new file mode 100644
index 000000000..817c5ab5b
--- /dev/null
+++ b/src/app/home/dashboard/accounting-reports-dialog/accounting-reports-dialog.component.ts
@@ -0,0 +1,1292 @@
+/**
+ * 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/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { CommonModule, DecimalPipe } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+
+export type AcctReportCode = 'PNL' | 'BALANCE-SHEET' | 'COMPARATIVE-BS' | 'GL-DR-CR' | 'MISC-EXP';
+
+export interface AcctReportsDialogData {
+ initialReport?: AcctReportCode;
+}
+
+interface PnlLine {
+ code: string;
+ label: string;
+ amount: number;
+ group: 'INCOME' | 'EXPENDITURE';
+ subgroup: string;
+}
+
+interface BsLine {
+ code: string;
+ label: string;
+ amount: number;
+ prevAmount: number;
+ side: 'CAPITAL-LIAB' | 'ASSETS';
+ group: string;
+}
+
+interface GlLine {
+ acCode: string;
+ description: string;
+ group: 'ASSET' | 'LIABILITY' | 'INCOME' | 'EXPENSE';
+ opening: number;
+ debit: number;
+ credit: number;
+ closing: number;
+}
+
+interface MiscExpRow {
+ txnDate: string;
+ branch: string;
+ branchCode: string;
+ voucherNo: string;
+ chequeNo: string;
+ description: string;
+ mode: 'CASH' | 'CHEQUE' | 'TRF' | 'NEFT';
+ withdrawal: number;
+ deposit: number;
+ balance: number;
+}
+
+@Component({
+ selector: 'mifosx-accounting-reports-dialog',
+ standalone: true,
+ templateUrl: './accounting-reports-dialog.component.html',
+ styleUrls: ['./accounting-reports-dialog.component.scss'],
+ imports: [
+ CommonModule,
+ DecimalPipe,
+ FormsModule,
+ MatDialogModule,
+ MatButtonModule,
+ MatTooltipModule,
+ FaIconComponent
+ ]
+})
+export class AccountingReportsDialogComponent {
+ private dialogRef = inject(MatDialogRef);
+
+ activeReport: AcctReportCode;
+
+ bank = {
+ name: 'Bharat Urban Bank Ltd',
+ branch: 'Head Office',
+ asOnDate: '31-03-2026',
+ period: '01-04-2025 to 31-03-2026',
+ fiscalYear: '2025-26',
+ prevFiscalYear: '2024-25',
+ quarter: 'Q4 FY 2025-26',
+ quarterFrom: '01-10-2025',
+ quarterTo: '31-12-2025',
+ principalOfficer: 'Karthik Kamath',
+ poDesignation: 'Chief Finance Officer',
+ auditor: 'M/s Sharma & Associates, Chartered Accountants',
+ reportRefNo: '157/2',
+ userCode: 'BUBL01',
+ branchCount: 15
+ };
+
+ reports = [
+ {
+ code: 'PNL' as AcctReportCode,
+ name: 'Profit & Loss',
+ icon: 'chart-line',
+ description: 'Income, expenditure & net profit for the financial year.',
+ group: 'Financial Statement'
+ },
+ {
+ code: 'BALANCE-SHEET' as AcctReportCode,
+ name: 'Balance Sheet',
+ icon: 'file-alt',
+ description: 'Capital, liabilities & assets as on reporting date.',
+ group: 'Financial Statement'
+ },
+ {
+ code: 'COMPARATIVE-BS' as AcctReportCode,
+ name: 'Comparative B/S',
+ icon: 'balance-scale',
+ description: 'Side-by-side comparison of current vs previous fiscal year.',
+ group: 'Financial Statement'
+ },
+ {
+ code: 'GL-DR-CR' as AcctReportCode,
+ name: 'GL Wise DR/CR',
+ icon: 'project-diagram',
+ description: 'GL-account-wise debit/credit consolidated movement.',
+ group: 'GL Report'
+ },
+ {
+ code: 'MISC-EXP' as AcctReportCode,
+ name: 'Misc Expense Ledger',
+ icon: 'receipt',
+ description: 'Voucher-level register of miscellaneous expenses.',
+ group: 'Register'
+ }
+ ];
+
+ // -------------------- P&L Data (₹ in Lakhs) --------------------
+ pnl: PnlLine[] = [
+ { code: 'I-01', group: 'INCOME', subgroup: 'Interest Earned', label: 'Interest on Advances', amount: 38420 },
+ { code: 'I-02', group: 'INCOME', subgroup: 'Interest Earned', label: 'Interest on Investments', amount: 14860 },
+ {
+ code: 'I-03',
+ group: 'INCOME',
+ subgroup: 'Interest Earned',
+ label: 'Interest on Balances with RBI / Other Banks',
+ amount: 1240
+ },
+ {
+ code: 'I-04',
+ group: 'INCOME',
+ subgroup: 'Other Income',
+ label: 'Commission, Exchange & Brokerage',
+ amount: 1820
+ },
+ { code: 'I-05', group: 'INCOME', subgroup: 'Other Income', label: 'Profit on Sale of Investments', amount: 480 },
+ { code: 'I-06', group: 'INCOME', subgroup: 'Other Income', label: 'Dividend Income', amount: 142 },
+ { code: 'I-07', group: 'INCOME', subgroup: 'Other Income', label: 'Miscellaneous Income', amount: 318 },
+ { code: 'E-01', group: 'EXPENDITURE', subgroup: 'Interest Expended', label: 'Interest on Deposits', amount: 28640 },
+ {
+ code: 'E-02',
+ group: 'EXPENDITURE',
+ subgroup: 'Interest Expended',
+ label: 'Interest on RBI / Inter-Bank Borrowings',
+ amount: 980
+ },
+ {
+ code: 'E-03',
+ group: 'EXPENDITURE',
+ subgroup: 'Operating Expenses',
+ label: 'Payments to & Provisions for Employees',
+ amount: 8420
+ },
+ {
+ code: 'E-04',
+ group: 'EXPENDITURE',
+ subgroup: 'Operating Expenses',
+ label: 'Rent, Taxes & Lighting',
+ amount: 1240
+ },
+ { code: 'E-05', group: 'EXPENDITURE', subgroup: 'Operating Expenses', label: 'Printing & Stationery', amount: 184 },
+ {
+ code: 'E-06',
+ group: 'EXPENDITURE',
+ subgroup: 'Operating Expenses',
+ label: 'Depreciation on Bank Property',
+ amount: 642
+ },
+ {
+ code: 'E-07',
+ group: 'EXPENDITURE',
+ subgroup: 'Operating Expenses',
+ label: 'Auditors Fees & Expenses',
+ amount: 124
+ },
+ {
+ code: 'E-08',
+ group: 'EXPENDITURE',
+ subgroup: 'Operating Expenses',
+ label: 'Repairs, Maintenance & Insurance',
+ amount: 386
+ },
+ {
+ code: 'E-09',
+ group: 'EXPENDITURE',
+ subgroup: 'Operating Expenses',
+ label: 'Other Operating Expenses',
+ amount: 824
+ },
+ {
+ code: 'E-10',
+ group: 'EXPENDITURE',
+ subgroup: 'Provisions',
+ label: 'Provisions for NPA / Standard Assets',
+ amount: 4280
+ },
+ { code: 'E-11', group: 'EXPENDITURE', subgroup: 'Provisions', label: 'Provisions for Tax', amount: 2120 }
+ ];
+
+ // -------------------- Balance Sheet (₹ in Lakhs) --------------------
+ bs: BsLine[] = [
+ {
+ code: 'L-01',
+ side: 'CAPITAL-LIAB',
+ group: 'Capital & Reserves',
+ label: 'Capital (Paid-up Share Capital)',
+ amount: 22000,
+ prevAmount: 20800
+ },
+ {
+ code: 'L-02',
+ side: 'CAPITAL-LIAB',
+ group: 'Capital & Reserves',
+ label: 'Reserves & Surplus',
+ amount: 32400,
+ prevAmount: 28640
+ },
+ {
+ code: 'L-03',
+ side: 'CAPITAL-LIAB',
+ group: 'Deposits',
+ label: 'Demand Deposits — Current Accounts',
+ amount: 84200,
+ prevAmount: 78420
+ },
+ {
+ code: 'L-04',
+ side: 'CAPITAL-LIAB',
+ group: 'Deposits',
+ label: 'Savings Bank Deposits',
+ amount: 218600,
+ prevAmount: 198400
+ },
+ {
+ code: 'L-05',
+ side: 'CAPITAL-LIAB',
+ group: 'Deposits',
+ label: 'Term Deposits',
+ amount: 462800,
+ prevAmount: 412400
+ },
+ {
+ code: 'L-06',
+ side: 'CAPITAL-LIAB',
+ group: 'Borrowings',
+ label: 'Borrowings from RBI / NABARD',
+ amount: 14180,
+ prevAmount: 16240
+ },
+ {
+ code: 'L-07',
+ side: 'CAPITAL-LIAB',
+ group: 'Borrowings',
+ label: 'Other Borrowings',
+ amount: 10000,
+ prevAmount: 9420
+ },
+ {
+ code: 'L-08',
+ side: 'CAPITAL-LIAB',
+ group: 'Other Liabilities',
+ label: 'Bills Payable',
+ amount: 4420,
+ prevAmount: 3980
+ },
+ {
+ code: 'L-09',
+ side: 'CAPITAL-LIAB',
+ group: 'Other Liabilities',
+ label: 'Inter-Office Adjustments (Net)',
+ amount: 1820,
+ prevAmount: 1640
+ },
+ {
+ code: 'L-10',
+ side: 'CAPITAL-LIAB',
+ group: 'Other Liabilities',
+ label: 'Interest Accrued',
+ amount: 6240,
+ prevAmount: 5840
+ },
+ {
+ code: 'L-11',
+ side: 'CAPITAL-LIAB',
+ group: 'Other Liabilities',
+ label: 'Other Liabilities & Provisions',
+ amount: 5940,
+ prevAmount: 5420
+ },
+ {
+ code: 'A-01',
+ side: 'ASSETS',
+ group: 'Cash & Bank Balances',
+ label: 'Cash on Hand',
+ amount: 8420,
+ prevAmount: 7820
+ },
+ {
+ code: 'A-02',
+ side: 'ASSETS',
+ group: 'Cash & Bank Balances',
+ label: 'Balances with RBI',
+ amount: 38400,
+ prevAmount: 34200
+ },
+ {
+ code: 'A-03',
+ side: 'ASSETS',
+ group: 'Cash & Bank Balances',
+ label: 'Balances with Other Banks — Current',
+ amount: 14800,
+ prevAmount: 12400
+ },
+ {
+ code: 'A-04',
+ side: 'ASSETS',
+ group: 'Cash & Bank Balances',
+ label: 'Balances with Other Banks — Term Deposits',
+ amount: 10000,
+ prevAmount: 9200
+ },
+ {
+ code: 'A-05',
+ side: 'ASSETS',
+ group: 'Money at Call',
+ label: 'Money at Call & Short Notice',
+ amount: 12400,
+ prevAmount: 10800
+ },
+ {
+ code: 'A-06',
+ side: 'ASSETS',
+ group: 'Investments',
+ label: 'Government Securities (SLR)',
+ amount: 142400,
+ prevAmount: 128600
+ },
+ {
+ code: 'A-07',
+ side: 'ASSETS',
+ group: 'Investments',
+ label: 'Other Approved Securities',
+ amount: 28200,
+ prevAmount: 24800
+ },
+ {
+ code: 'A-08',
+ side: 'ASSETS',
+ group: 'Investments',
+ label: 'Shares / Debentures / Bonds',
+ amount: 14000,
+ prevAmount: 12200
+ },
+ {
+ code: 'A-09',
+ side: 'ASSETS',
+ group: 'Advances',
+ label: 'Cash Credit, Overdraft & Demand Loans',
+ amount: 168400,
+ prevAmount: 152800
+ },
+ {
+ code: 'A-10',
+ side: 'ASSETS',
+ group: 'Advances',
+ label: 'Term Loans (Secured)',
+ amount: 217865,
+ prevAmount: 198400
+ },
+ {
+ code: 'A-11',
+ side: 'ASSETS',
+ group: 'Advances',
+ label: 'Term Loans (Unsecured)',
+ amount: 30000,
+ prevAmount: 27200
+ },
+ {
+ code: 'A-12',
+ side: 'ASSETS',
+ group: 'Fixed Assets',
+ label: 'Premises (Net of Depreciation)',
+ amount: 9420,
+ prevAmount: 8820
+ },
+ {
+ code: 'A-13',
+ side: 'ASSETS',
+ group: 'Fixed Assets',
+ label: 'Other Fixed Assets',
+ amount: 4780,
+ prevAmount: 4620
+ },
+ {
+ code: 'A-14',
+ side: 'ASSETS',
+ group: 'Other Assets',
+ label: 'Inter-Office Adjustments (Net)',
+ amount: 0,
+ prevAmount: 0
+ },
+ {
+ code: 'A-15',
+ side: 'ASSETS',
+ group: 'Other Assets',
+ label: 'Tax Paid in Advance & TDS',
+ amount: 5420,
+ prevAmount: 4820
+ },
+ { code: 'A-16', side: 'ASSETS', group: 'Other Assets', label: 'Stationery & Stamps', amount: 184, prevAmount: 142 },
+ {
+ code: 'A-17',
+ side: 'ASSETS',
+ group: 'Other Assets',
+ label: 'Sundry Debtors & Other Assets',
+ amount: 2876,
+ prevAmount: 2540
+ }
+ ];
+
+ // -------------------- GL Wise DR/CR Data --------------------
+ glReport: GlLine[] = [
+ {
+ acCode: '110001',
+ description: 'Capital A/c — Equity Share',
+ group: 'LIABILITY',
+ opening: 20800,
+ debit: 0,
+ credit: 1200,
+ closing: 22000
+ },
+ {
+ acCode: '120020',
+ description: 'Reserves & Surplus A/c',
+ group: 'LIABILITY',
+ opening: 28640,
+ debit: 0,
+ credit: 3760,
+ closing: 32400
+ },
+ {
+ acCode: '130001',
+ description: 'Demand Deposits — Current A/c',
+ group: 'LIABILITY',
+ opening: 78420,
+ debit: 124800,
+ credit: 130580,
+ closing: 84200
+ },
+ {
+ acCode: '130020',
+ description: 'Savings Bank Deposits A/c',
+ group: 'LIABILITY',
+ opening: 198400,
+ debit: 84200,
+ credit: 104400,
+ closing: 218600
+ },
+ {
+ acCode: '130040',
+ description: 'Term Deposits A/c',
+ group: 'LIABILITY',
+ opening: 412400,
+ debit: 142800,
+ credit: 193200,
+ closing: 462800
+ },
+ {
+ acCode: '140020',
+ description: 'Borrowings — RBI / NABARD A/c',
+ group: 'LIABILITY',
+ opening: 16240,
+ debit: 4060,
+ credit: 2000,
+ closing: 14180
+ },
+ {
+ acCode: '210010',
+ description: 'Cash on Hand A/c',
+ group: 'ASSET',
+ opening: 7820,
+ debit: 248400,
+ credit: 247800,
+ closing: 8420
+ },
+ {
+ acCode: '210020',
+ description: 'Balance with RBI A/c',
+ group: 'ASSET',
+ opening: 34200,
+ debit: 84200,
+ credit: 80000,
+ closing: 38400
+ },
+ {
+ acCode: '210030',
+ description: 'Balance with Other Banks — Current A/c',
+ group: 'ASSET',
+ opening: 12400,
+ debit: 22400,
+ credit: 20000,
+ closing: 14800
+ },
+ {
+ acCode: '220010',
+ description: 'Government Securities — SLR A/c',
+ group: 'ASSET',
+ opening: 128600,
+ debit: 28000,
+ credit: 14200,
+ closing: 142400
+ },
+ {
+ acCode: '230010',
+ description: 'Cash Credit / Overdraft A/c',
+ group: 'ASSET',
+ opening: 152800,
+ debit: 84200,
+ credit: 68600,
+ closing: 168400
+ },
+ {
+ acCode: '230020',
+ description: 'Term Loans — Secured A/c',
+ group: 'ASSET',
+ opening: 198400,
+ debit: 38420,
+ credit: 18955,
+ closing: 217865
+ },
+ {
+ acCode: '240010',
+ description: 'Premises (Net) A/c',
+ group: 'ASSET',
+ opening: 8820,
+ debit: 824,
+ credit: 224,
+ closing: 9420
+ },
+ {
+ acCode: '310010',
+ description: 'Interest on Advances A/c',
+ group: 'INCOME',
+ opening: 0,
+ debit: 0,
+ credit: 38420,
+ closing: 38420
+ },
+ {
+ acCode: '310020',
+ description: 'Interest on Investments A/c',
+ group: 'INCOME',
+ opening: 0,
+ debit: 0,
+ credit: 14860,
+ closing: 14860
+ },
+ {
+ acCode: '320010',
+ description: 'Commission & Brokerage A/c',
+ group: 'INCOME',
+ opening: 0,
+ debit: 0,
+ credit: 1820,
+ closing: 1820
+ },
+ {
+ acCode: '410010',
+ description: 'Interest on Deposits A/c',
+ group: 'EXPENSE',
+ opening: 0,
+ debit: 28640,
+ credit: 0,
+ closing: 28640
+ },
+ {
+ acCode: '420010',
+ description: 'Salaries & Allowances A/c',
+ group: 'EXPENSE',
+ opening: 0,
+ debit: 8420,
+ credit: 0,
+ closing: 8420
+ },
+ {
+ acCode: '420020',
+ description: 'Rent, Taxes & Lighting A/c',
+ group: 'EXPENSE',
+ opening: 0,
+ debit: 1240,
+ credit: 0,
+ closing: 1240
+ },
+ {
+ acCode: '420030',
+ description: 'Depreciation A/c',
+ group: 'EXPENSE',
+ opening: 0,
+ debit: 642,
+ credit: 0,
+ closing: 642
+ },
+ {
+ acCode: '420090',
+ description: 'Miscellaneous Expenses A/c',
+ group: 'EXPENSE',
+ opening: 0,
+ debit: 824,
+ credit: 0,
+ closing: 824
+ }
+ ];
+
+ // -------------------- Misc Expense Ledger --------------------
+ miscExp: MiscExpRow[] = [
+ {
+ txnDate: '02-10-2025',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ voucherNo: 'VR-12001',
+ chequeNo: '142008',
+ description: 'Petrol Bill — Branch Vehicle',
+ mode: 'CHEQUE',
+ withdrawal: 4280,
+ deposit: 0,
+ balance: 4280
+ },
+ {
+ txnDate: '04-10-2025',
+ branch: 'Pune FC Road',
+ branchCode: '02',
+ voucherNo: 'VR-12012',
+ chequeNo: '—',
+ description: 'Diesel Bill — Generator',
+ mode: 'CASH',
+ withdrawal: 2840,
+ deposit: 0,
+ balance: 7120
+ },
+ {
+ txnDate: '08-10-2025',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ voucherNo: 'VR-12025',
+ chequeNo: '142014',
+ description: 'Office Stationery & Printing',
+ mode: 'CHEQUE',
+ withdrawal: 8420,
+ deposit: 0,
+ balance: 15540
+ },
+ {
+ txnDate: '12-10-2025',
+ branch: 'Bengaluru Jayanagar',
+ branchCode: '04',
+ voucherNo: 'VR-12041',
+ chequeNo: '—',
+ description: 'Tea / Refreshments — Customer Visit',
+ mode: 'CASH',
+ withdrawal: 1240,
+ deposit: 0,
+ balance: 16780
+ },
+ {
+ txnDate: '15-10-2025',
+ branch: 'Delhi Greater Kailash',
+ branchCode: '08',
+ voucherNo: 'VR-12058',
+ chequeNo: '184201',
+ description: 'Newspaper & Periodical Subscriptions',
+ mode: 'CHEQUE',
+ withdrawal: 3680,
+ deposit: 0,
+ balance: 20460
+ },
+ {
+ txnDate: '18-10-2025',
+ branch: 'Pune FC Road',
+ branchCode: '02',
+ voucherNo: 'VR-12064',
+ chequeNo: '—',
+ description: 'Petrol Bill — Officer Visit',
+ mode: 'CASH',
+ withdrawal: 1820,
+ deposit: 0,
+ balance: 22280
+ },
+ {
+ txnDate: '22-10-2025',
+ branch: 'Chennai Valasaravakkam',
+ branchCode: '09',
+ voucherNo: 'VR-12082',
+ chequeNo: 'NEFT-2210',
+ description: 'AMC — Office Equipment',
+ mode: 'NEFT',
+ withdrawal: 24800,
+ deposit: 0,
+ balance: 47080
+ },
+ {
+ txnDate: '28-10-2025',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ voucherNo: 'VR-12101',
+ chequeNo: '142098',
+ description: 'Audit Stationery & Bind Charges',
+ mode: 'CHEQUE',
+ withdrawal: 6420,
+ deposit: 0,
+ balance: 53500
+ },
+ {
+ txnDate: '02-11-2025',
+ branch: 'Ahmedabad Satellite',
+ branchCode: '06',
+ voucherNo: 'VR-12118',
+ chequeNo: '—',
+ description: 'Postage & Telegram',
+ mode: 'CASH',
+ withdrawal: 982,
+ deposit: 0,
+ balance: 54482
+ },
+ {
+ txnDate: '08-11-2025',
+ branch: 'Lucknow Gomti Nagar',
+ branchCode: '07',
+ voucherNo: 'VR-12134',
+ chequeNo: '184244',
+ description: 'Diesel Bill — Branch Generator',
+ mode: 'CHEQUE',
+ withdrawal: 3420,
+ deposit: 0,
+ balance: 57902
+ },
+ {
+ txnDate: '14-11-2025',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ voucherNo: 'VR-12152',
+ chequeNo: '142112',
+ description: 'Reverse — Cancelled Voucher',
+ mode: 'CHEQUE',
+ withdrawal: 0,
+ deposit: 1240,
+ balance: 56662
+ },
+ {
+ txnDate: '20-11-2025',
+ branch: 'Kolkata Salt Lake',
+ branchCode: '10',
+ voucherNo: 'VR-12168',
+ chequeNo: '—',
+ description: 'Petrol Bill — Field Officer',
+ mode: 'CASH',
+ withdrawal: 1640,
+ deposit: 0,
+ balance: 58302
+ },
+ {
+ txnDate: '28-11-2025',
+ branch: 'Bengaluru Jayanagar',
+ branchCode: '04',
+ voucherNo: 'VR-12184',
+ chequeNo: 'TRF-2811',
+ description: 'Internet & Telephone Charges',
+ mode: 'TRF',
+ withdrawal: 18420,
+ deposit: 0,
+ balance: 76722
+ },
+ {
+ txnDate: '06-12-2025',
+ branch: 'Delhi Greater Kailash',
+ branchCode: '08',
+ voucherNo: 'VR-12211',
+ chequeNo: '184272',
+ description: 'Office Cleaning & Housekeeping',
+ mode: 'CHEQUE',
+ withdrawal: 12400,
+ deposit: 0,
+ balance: 89122
+ },
+ {
+ txnDate: '15-12-2025',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ voucherNo: 'VR-12238',
+ chequeNo: '—',
+ description: 'Local Conveyance — Officers',
+ mode: 'CASH',
+ withdrawal: 2480,
+ deposit: 0,
+ balance: 91602
+ },
+ {
+ txnDate: '22-12-2025',
+ branch: 'Indore Vijay Nagar',
+ branchCode: '14',
+ voucherNo: 'VR-12262',
+ chequeNo: 'NEFT-2212',
+ description: 'Pest Control — Annual Contract',
+ mode: 'NEFT',
+ withdrawal: 8420,
+ deposit: 0,
+ balance: 100022
+ },
+ {
+ txnDate: '28-12-2025',
+ branch: 'Pune FC Road',
+ branchCode: '02',
+ voucherNo: 'VR-12281',
+ chequeNo: '142158',
+ description: 'Misc Office Supplies',
+ mode: 'CHEQUE',
+ withdrawal: 4620,
+ deposit: 0,
+ balance: 104642
+ },
+ {
+ txnDate: '31-12-2025',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ voucherNo: 'VR-12298',
+ chequeNo: 'TRF-3112',
+ description: 'Newspaper Bill (Q3)',
+ mode: 'TRF',
+ withdrawal: 1820,
+ deposit: 0,
+ balance: 106462
+ }
+ ];
+
+ // -------------------- Filters --------------------
+ miscFilterBranch = '';
+ miscFilterMode: '' | 'CASH' | 'CHEQUE' | 'TRF' | 'NEFT' = '';
+ glFilterGroup: '' | 'ASSET' | 'LIABILITY' | 'INCOME' | 'EXPENSE' = '';
+ glFilterSearch = '';
+
+ data = inject(MAT_DIALOG_DATA, { optional: true });
+
+ constructor() {
+ this.activeReport = this.data?.initialReport ?? 'PNL';
+ }
+
+ setReport(code: AcctReportCode): void {
+ this.activeReport = code;
+ }
+ close(): void {
+ this.dialogRef.close();
+ }
+
+ get currentReport() {
+ return this.reports.find((r) => r.code === this.activeReport)!;
+ }
+
+ // -------------------- P&L computed --------------------
+ get pnlIncome(): PnlLine[] {
+ return this.pnl.filter((l) => l.group === 'INCOME');
+ }
+ get pnlExpenditure(): PnlLine[] {
+ return this.pnl.filter((l) => l.group === 'EXPENDITURE');
+ }
+ get pnlIncomeSubgroups(): string[] {
+ const s: string[] = [];
+ for (const l of this.pnlIncome) if (!s.includes(l.subgroup)) s.push(l.subgroup);
+ return s;
+ }
+ get pnlExpSubgroups(): string[] {
+ const s: string[] = [];
+ for (const l of this.pnlExpenditure) if (!s.includes(l.subgroup)) s.push(l.subgroup);
+ return s;
+ }
+ pnlLinesIn(group: 'INCOME' | 'EXPENDITURE', sub: string): PnlLine[] {
+ return this.pnl.filter((l) => l.group === group && l.subgroup === sub);
+ }
+ subTotal(group: 'INCOME' | 'EXPENDITURE', sub: string): number {
+ return this.pnlLinesIn(group, sub).reduce((s, l) => s + l.amount, 0);
+ }
+ get totalIncome(): number {
+ return this.pnlIncome.reduce((s, l) => s + l.amount, 0);
+ }
+ get totalExpenditure(): number {
+ return this.pnlExpenditure.reduce((s, l) => s + l.amount, 0);
+ }
+ get netProfit(): number {
+ return this.totalIncome - this.totalExpenditure;
+ }
+ get netProfitPct(): number {
+ return this.totalIncome === 0 ? 0 : +((this.netProfit / this.totalIncome) * 100).toFixed(2);
+ }
+
+ // -------------------- Balance Sheet computed --------------------
+ get bsLiabilities(): BsLine[] {
+ return this.bs.filter((l) => l.side === 'CAPITAL-LIAB');
+ }
+ get bsAssets(): BsLine[] {
+ return this.bs.filter((l) => l.side === 'ASSETS');
+ }
+ bsGroupsBySide(side: 'CAPITAL-LIAB' | 'ASSETS'): string[] {
+ const s: string[] = [];
+ for (const l of this.bs) if (l.side === side && !s.includes(l.group)) s.push(l.group);
+ return s;
+ }
+ bsLinesInGroup(side: 'CAPITAL-LIAB' | 'ASSETS', group: string): BsLine[] {
+ return this.bs.filter((l) => l.side === side && l.group === group);
+ }
+ bsSubTotal(side: 'CAPITAL-LIAB' | 'ASSETS', group: string, prev = false): number {
+ return this.bsLinesInGroup(side, group).reduce((s, l) => s + (prev ? l.prevAmount : l.amount), 0);
+ }
+ get totalLiabilities(): number {
+ return this.bsLiabilities.reduce((s, l) => s + l.amount, 0);
+ }
+ get totalLiabilitiesPrev(): number {
+ return this.bsLiabilities.reduce((s, l) => s + l.prevAmount, 0);
+ }
+ get totalAssets(): number {
+ return this.bsAssets.reduce((s, l) => s + l.amount, 0);
+ }
+ get totalAssetsPrev(): number {
+ return this.bsAssets.reduce((s, l) => s + l.prevAmount, 0);
+ }
+ variation(curr: number, prev: number): number {
+ return curr - prev;
+ }
+ variationPct(curr: number, prev: number): number {
+ return prev === 0 ? 0 : +(((curr - prev) / prev) * 100).toFixed(2);
+ }
+
+ // -------------------- GL DR/CR computed --------------------
+ get filteredGl(): GlLine[] {
+ const search = this.glFilterSearch.trim().toLowerCase();
+ return this.glReport.filter((g) => {
+ if (this.glFilterGroup && g.group !== this.glFilterGroup) return false;
+ if (search && !g.description.toLowerCase().includes(search) && !g.acCode.includes(search)) return false;
+ return true;
+ });
+ }
+ resetGlFilters(): void {
+ this.glFilterGroup = '';
+ this.glFilterSearch = '';
+ }
+ get glTotalDebit(): number {
+ return this.filteredGl.reduce((s, g) => s + g.debit, 0);
+ }
+ get glTotalCredit(): number {
+ return this.filteredGl.reduce((s, g) => s + g.credit, 0);
+ }
+ glGroupClass(group: 'ASSET' | 'LIABILITY' | 'INCOME' | 'EXPENSE'): string {
+ return 'gl-group-' + group.toLowerCase();
+ }
+
+ // -------------------- Misc Exp computed --------------------
+ get filteredMisc(): MiscExpRow[] {
+ const branch = this.miscFilterBranch.trim().toLowerCase();
+ return this.miscExp.filter((r) => {
+ if (branch && !r.branch.toLowerCase().includes(branch)) return false;
+ if (this.miscFilterMode && r.mode !== this.miscFilterMode) return false;
+ return true;
+ });
+ }
+ resetMiscFilters(): void {
+ this.miscFilterBranch = '';
+ this.miscFilterMode = '';
+ }
+ get miscTotalWithdrawal(): number {
+ return this.filteredMisc.reduce((s, r) => s + r.withdrawal, 0);
+ }
+ get miscTotalDeposit(): number {
+ return this.filteredMisc.reduce((s, r) => s + r.deposit, 0);
+ }
+ get miscBranchTotals(): { branch: string; total: number }[] {
+ const map: Record = {};
+ for (const r of this.filteredMisc) {
+ map[r.branch] = (map[r.branch] || 0) + r.withdrawal - r.deposit;
+ }
+ return Object.entries(map).map(
+ ([
+ branch,
+ total
+ ]) => ({ branch, total })
+ );
+ }
+
+ // -------------------- Exports --------------------
+ exportCurrentCsv(): void {
+ if (this.activeReport === 'PNL') return this.exportPnlCsv();
+ if (this.activeReport === 'BALANCE-SHEET') return this.exportBsCsv(false);
+ if (this.activeReport === 'COMPARATIVE-BS') return this.exportBsCsv(true);
+ if (this.activeReport === 'GL-DR-CR') return this.exportGlCsv();
+ if (this.activeReport === 'MISC-EXP') return this.exportMiscCsv();
+ }
+
+ exportPnlCsv(): void {
+ const rows: string[][] = [];
+ rows.push([
+ this.bank.name,
+ this.bank.branch
+ ]);
+ rows.push([
+ 'PROFIT & LOSS A/C',
+ `For the year ended ${this.bank.asOnDate}`
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Code',
+ 'Group',
+ 'Sub-Group',
+ 'Particulars',
+ 'Amount (₹ Lakh)'
+ ]);
+ for (const l of this.pnl) {
+ rows.push([
+ l.code,
+ l.group,
+ l.subgroup,
+ l.label,
+ l.amount.toFixed(2)
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ '',
+ '',
+ '',
+ 'Total Income',
+ this.totalIncome.toFixed(2)
+ ]);
+ rows.push([
+ '',
+ '',
+ '',
+ 'Total Expenditure',
+ this.totalExpenditure.toFixed(2)
+ ]);
+ rows.push([
+ '',
+ '',
+ '',
+ 'Net Profit / (Loss)',
+ this.netProfit.toFixed(2)
+ ]);
+ rows.push([
+ '',
+ '',
+ '',
+ 'Net Profit Margin %',
+ this.netProfitPct.toFixed(2) + '%'
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Certified by',
+ this.bank.principalOfficer + ' — ' + this.bank.poDesignation
+ ]);
+ rows.push([
+ 'Auditor',
+ this.bank.auditor
+ ]);
+ this.saveCsv(rows, `Profit-Loss-${this.dateTag()}.csv`);
+ }
+
+ exportBsCsv(comparative: boolean): void {
+ const rows: string[][] = [];
+ rows.push([
+ this.bank.name,
+ this.bank.branch
+ ]);
+ rows.push([
+ comparative ? 'COMPARATIVE BALANCE SHEET' : 'BALANCE SHEET',
+ `As on ${this.bank.asOnDate}`
+ ]);
+ rows.push([]);
+ if (comparative) {
+ rows.push([
+ 'Code',
+ 'Side',
+ 'Group',
+ 'Particulars',
+ `${this.bank.fiscalYear} (₹ Lakh)`,
+ `${this.bank.prevFiscalYear} (₹ Lakh)`,
+ 'Variation',
+ 'Variation %'
+ ]);
+ for (const l of this.bs) {
+ rows.push([
+ l.code,
+ l.side,
+ l.group,
+ l.label,
+ l.amount.toFixed(2),
+ l.prevAmount.toFixed(2),
+ this.variation(l.amount, l.prevAmount).toFixed(2),
+ this.variationPct(l.amount, l.prevAmount).toFixed(2) + '%'
+ ]);
+ }
+ } else {
+ rows.push([
+ 'Code',
+ 'Side',
+ 'Group',
+ 'Particulars',
+ 'Amount (₹ Lakh)'
+ ]);
+ for (const l of this.bs) {
+ rows.push([
+ l.code,
+ l.side,
+ l.group,
+ l.label,
+ l.amount.toFixed(2)
+ ]);
+ }
+ }
+ rows.push([]);
+ rows.push([
+ '',
+ '',
+ '',
+ 'Total Capital & Liabilities',
+ this.totalLiabilities.toFixed(2)
+ ]);
+ rows.push([
+ '',
+ '',
+ '',
+ 'Total Assets',
+ this.totalAssets.toFixed(2)
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Certified by',
+ this.bank.principalOfficer + ' — ' + this.bank.poDesignation
+ ]);
+ this.saveCsv(rows, `${comparative ? 'Comparative-' : ''}Balance-Sheet-${this.dateTag()}.csv`);
+ }
+
+ exportGlCsv(): void {
+ const rows: string[][] = [];
+ rows.push([
+ this.bank.name,
+ this.bank.branch
+ ]);
+ rows.push([
+ 'GL WISE DEBIT / CREDIT REPORT',
+ `Period ${this.bank.quarterFrom} TO ${this.bank.quarterTo}`
+ ]);
+ rows.push([]);
+ rows.push([
+ 'A/C Code',
+ 'Description',
+ 'Group',
+ 'Opening (₹ Lakh)',
+ 'Debit',
+ 'Credit',
+ 'Closing (₹ Lakh)'
+ ]);
+ for (const g of this.filteredGl) {
+ rows.push([
+ g.acCode,
+ g.description,
+ g.group,
+ g.opening.toFixed(2),
+ g.debit.toFixed(2),
+ g.credit.toFixed(2),
+ g.closing.toFixed(2)
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ '',
+ 'TOTAL',
+ '',
+ '',
+ this.glTotalDebit.toFixed(2),
+ this.glTotalCredit.toFixed(2),
+ ''
+ ]);
+ this.saveCsv(rows, `GL-DrCr-${this.dateTag()}.csv`);
+ }
+
+ exportMiscCsv(): void {
+ const rows: string[][] = [];
+ rows.push([
+ this.bank.name,
+ this.bank.branch
+ ]);
+ rows.push([
+ 'MISCELLANEOUS EXPENSE LEDGER',
+ `Period ${this.bank.quarterFrom} TO ${this.bank.quarterTo}`
+ ]);
+ rows.push([
+ `Report Ref: ${this.bank.reportRefNo}`,
+ `User Code: ${this.bank.userCode}`
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Date',
+ 'Branch',
+ 'Branch Code',
+ 'Voucher No',
+ 'Cheque No',
+ 'Description',
+ 'Mode',
+ 'Withdrawal',
+ 'Deposit',
+ 'Balance'
+ ]);
+ for (const r of this.filteredMisc) {
+ rows.push([
+ r.txnDate,
+ r.branch,
+ r.branchCode,
+ r.voucherNo,
+ r.chequeNo,
+ r.description,
+ r.mode,
+ r.withdrawal.toString(),
+ r.deposit.toString(),
+ r.balance.toString()
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ 'TOTAL',
+ this.miscTotalWithdrawal.toString(),
+ this.miscTotalDeposit.toString(),
+ ''
+ ]);
+ rows.push([]);
+ rows.push(['Branch Wise Total']);
+ for (const bt of this.miscBranchTotals) {
+ rows.push([
+ bt.branch,
+ bt.total.toString()
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ 'Print Date',
+ new Date().toLocaleString('en-IN')
+ ]);
+ this.saveCsv(rows, `Misc-Exp-Ledger-${this.dateTag()}.csv`);
+ }
+
+ // ---- helpers ----
+ private dateTag(): string {
+ const d = new Date();
+ return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
+ }
+ private saveCsv(rows: string[][], filename: string): void {
+ const escape = (v: string): string => {
+ const s = (v || '').replace(/"/g, '""');
+ if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
+ return s;
+ };
+ const csv = rows.map((r) => r.map(escape).join(',')).join('\n') + '\n';
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+}
diff --git a/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.html b/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.html
new file mode 100644
index 000000000..4528ff587
--- /dev/null
+++ b/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.html
@@ -0,0 +1,478 @@
+
+
+
+
+
+
+
+
+
Asset Liability Management — Structural Liquidity
+
+ {{ bank.name }} · {{ bank.formCode }} · {{ bank.quarter }} · As on
+ {{ bank.asOnDate }}
+
+
+
+
+
+ 14d Tolerance
+ {{ gap14dPct.toFixed(2) }}%
+ / -{{ bank.tolerance14d }}%
+
+ ✕
+
+
+
+
+
+
+ Dashboard
+
+
+ Maturity Ladder
+
+
+ Monthly Snapshot
+
+
+ Form ALM-1
+
+
+
+
+
+
+
+
+
0 ? (totalGapPct > 100 ? 100 : totalGapPct) : 0"
+ [ngClass]="totalGap >= 0 ? 'on-target' : 'below'"
+ >
+
+
{{ totalGapPct.toFixed(1) }}%
+
Net Gap / Outflow
+
+
+
+
+
NDTL
+
₹ {{ bank.ndtl / 100 | number: '1.2-2' }} Cr
+
Net Demand & Time Liabilities
+
+
+
Total Outflows
+
₹ {{ totalOutflow / 100 | number: '1.2-2' }} Cr
+
All maturity buckets
+
+
+
Total Inflows
+
₹ {{ totalInflow / 100 | number: '1.2-2' }} Cr
+
= 0 ? 'pos' : 'neg'">
+ Net: {{ totalGap >= 0 ? '+' : '' }}{{ totalGap / 100 | number: '1.2-2' }} Cr
+
+
+
+
+
+
+
+
RBI Tolerance Limits — Cumulative Gap
+ RBI MD: {{ bank.rbiCircular }}
+
+
+
+
+ Cumulative Gap up to 14 days
+
+ {{ gap14dPct.toFixed(2) }}%
+ limit -{{ bank.tolerance14d }}%
+ {{ gap14dWithinTolerance ? 'WITHIN' : 'BREACH' }}
+
+
+
+
= 0 ? 100 : 100 + (gap14dPct / bank.tolerance14d) * 100"
+ >
+
+
+
+
+
+ Cumulative Gap up to 30 days
+
+ {{ gap28dPct.toFixed(2) }}%
+ limit -{{ bank.tolerance28d }}%
+ {{ gap28dWithinTolerance ? 'WITHIN' : 'BREACH' }}
+
+
+
+
= 0 ? 100 : 100 + (gap28dPct / bank.tolerance28d) * 100"
+ >
+
+
+
+
+
+
+
+
+
+
Bucket-wise Mismatch Heatmap
+
+
+
+
{{ b.code }}
+
{{ bucketGapPct(b).toFixed(1) }}%
+
{{ b.label }}
+
+
+
+
+
+
+
Reporting Snapshot
+
+
+
+
Reporting Bank
+
{{ bank.name }}
+
+
+
Branch
+
{{ bank.branch }}
+
+
+
Principal Officer
+
{{ bank.principalOfficer }}
+
+
+
Form Code
+
{{ bank.formCode }}
+
+
+
Report Ref No
+
{{ bank.reportRefNo }}
+
+
+
User Code
+
{{ bank.userCode }}
+
+
+
+
+
+
+
+
+
+
Statement of Structural Liquidity — Maturity Ladder
+
+
+ All Buckets
+ Short Term (≤ 30 days)
+ Medium Term (1–12 months)
+ Long Term (> 1 year)
+
+
+ Export CSV
+
+
+
+
+
+
+
+ Bucket
+ Time Period
+ Outflow (Liab)
+ Inflow (Assets)
+ Gap
+ Gap %
+ Status
+
+
+
+
+ {{ b.code }}
+ {{ b.label }}
+ {{ b.outflow | number: '1.2-2' }}
+ {{ b.inflow | number: '1.2-2' }}
+ = 0 ? 'pos' : 'neg'">
+ {{ bucketGap(b) >= 0 ? '+' : '' }}{{ bucketGap(b) | number: '1.2-2' }}
+
+ = 0 ? 'pos' : 'neg'">
+ {{ bucketGapPct(b) >= 0 ? '+' : '' }}{{ bucketGapPct(b).toFixed(2) }}%
+
+
+
+ SURPLUS
+ DEFICIT
+ BALANCED
+
+
+
+
+ TOTAL
+
+ {{ totalOutflow | number: '1.2-2' }}
+
+
+ {{ totalInflow | number: '1.2-2' }}
+
+ = 0 ? 'pos' : 'neg'">
+ {{ totalGap >= 0 ? '+' : '' }}{{ totalGap | number: '1.2-2' }}
+
+ = 0 ? 'pos' : 'neg'">
+ {{ totalGapPct >= 0 ? '+' : '' }}{{ totalGapPct.toFixed(2) }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Through Bucket
+ Cum. Outflow
+ Cum. Inflow
+ Cum. Gap
+ Cum. Gap %
+
+
+
+
+ {{ b.code }} — {{ b.label }}
+ {{ cumulativeOutflow(i) | number: '1.2-2' }}
+ {{ cumulativeInflow(i) | number: '1.2-2' }}
+ = 0 ? 'pos' : 'neg'">
+ {{ cumulativeGap(i) >= 0 ? '+' : '' }}{{ cumulativeGap(i) | number: '1.2-2' }}
+
+ = 0 ? 'pos' : 'neg'">
+ {{ cumulativeGapPct(i) >= 0 ? '+' : '' }}{{ cumulativeGapPct(i).toFixed(2) }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
Monthly Asset / Liability Snapshot — FY {{ bank.fiscalYear }}
+
+ Export Excel
+
+
+
+
+
+
+ Month
+ Liabilities (₹ Lakh)
+ Assets (₹ Lakh)
+
+
+ Reserve/Prov
+ Borrowings
+ FDs
+ Total Liab
+ Cash
+ Investments
+ Other Assets
+ Total Assets
+
+
+
+
+ {{ m.month }}
+ {{ m.reserveProv | number: '1.2-2' }}
+ {{ m.borrowings | number: '1.2-2' }}
+ {{ m.fds | number: '1.2-2' }}
+
+ {{ monthTotalLiab(m) | number: '1.2-2' }}
+
+ {{ m.cashOnHand | number: '1.2-2' }}
+ {{ m.investments | number: '1.2-2' }}
+ {{ m.otherAssets | number: '1.2-2' }}
+
+ {{ monthTotalAssets(m) | number: '1.2-2' }}
+
+
+
+ AVERAGE
+ {{ avg('reserveProv') | number: '1.2-2' }}
+ {{ avg('borrowings') | number: '1.2-2' }}
+ {{ avg('fds') | number: '1.2-2' }}
+
+ {{ avg('reserveProv') + avg('borrowings') + avg('fds') | number: '1.2-2' }}
+
+ {{ avg('cashOnHand') | number: '1.2-2' }}
+ {{ avg('investments') | number: '1.2-2' }}
+ {{ avg('otherAssets') | number: '1.2-2' }}
+
+ {{ avg('cashOnHand') + avg('investments') + avg('otherAssets') | number: '1.2-2' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ bank.formCode }} — Statement of Structural Liquidity
+
+ Generate {{ bank.formCode }}
+
+
+
+
+
+
+
+
diff --git a/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.scss b/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.scss
new file mode 100644
index 000000000..c27fdcf76
--- /dev/null
+++ b/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.scss
@@ -0,0 +1,772 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --alm-good: #15803d;
+ --alm-good-l: #dcfce7;
+ --alm-mid: #d97706;
+ --alm-mid-l: #fef3c7;
+ --alm-low: #c0392b;
+ --alm-low-l: #fdecea;
+ --alm-blue: #2563eb;
+ --alm-blue-l: #dbeafe;
+ --alm-indigo: #4f46e5;
+ --alm-indigo-l: #e0e7ff;
+ --alm-g50: #f8fafc;
+ --alm-g100: #f1f5f9;
+ --alm-g200: #e2e8f0;
+ --alm-g300: #cbd5e1;
+ --alm-g500: #64748b;
+ --alm-g700: #334155;
+ --alm-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ color: var(--alm-g900);
+}
+
+.alm-dlg {
+ background: #fff;
+ border-radius: 14px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ max-height: 90vh;
+}
+
+/* HEADER */
+.alm-head {
+ padding: 18px 22px;
+ border-bottom: 1px solid var(--alm-g200);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-shrink: 0;
+ background: linear-gradient(135deg, #eef2ff 0%, #fff 100%);
+}
+
+.alm-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ flex: 1;
+}
+
+.alm-head-ic {
+ width: 44px;
+ height: 44px;
+ border-radius: 11px;
+ background: var(--alm-indigo-l);
+ color: var(--alm-indigo);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.alm-title {
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+ color: var(--alm-g900);
+}
+
+.alm-sub {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--alm-g500);
+}
+
+.alm-head-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.alm-overall {
+ display: flex;
+ align-items: baseline;
+ gap: 6px;
+ padding: 6px 14px;
+ border-radius: 999px;
+ font-weight: 700;
+
+ &.on-target {
+ background: var(--alm-good-l);
+ color: var(--alm-good);
+ }
+
+ &.below {
+ background: var(--alm-low-l);
+ color: var(--alm-low);
+ }
+
+ .alm-overall-l {
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-weight: 600;
+ }
+
+ .alm-overall-v {
+ font-size: 16px;
+ }
+
+ .alm-overall-t {
+ font-size: 11px;
+ font-weight: 600;
+ opacity: 0.8;
+ }
+}
+
+.alm-close {
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ border: 1px solid var(--alm-g200);
+ background: #fff;
+ font-size: 14px;
+ color: var(--alm-g500);
+ cursor: pointer;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--alm-g100);
+ color: var(--alm-g900);
+ }
+}
+
+/* TABS */
+.alm-tabs {
+ display: flex;
+ background: var(--alm-g50);
+ border-bottom: 1px solid var(--alm-g200);
+ padding: 0 22px;
+ overflow-x: auto;
+ flex-shrink: 0;
+}
+
+.alm-tab {
+ display: flex;
+ align-items: center;
+ padding: 12px 18px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--alm-g500);
+ border-bottom: 2px solid transparent;
+ transition: all 0.15s;
+ white-space: nowrap;
+
+ &:hover {
+ color: var(--alm-g700);
+ background: rgb(255 255 255 / 50%);
+ }
+
+ &.active {
+ background: #fff;
+ color: var(--alm-indigo);
+ border-bottom-color: var(--alm-indigo);
+ }
+}
+
+/* BODY */
+.alm-body {
+ padding: 18px 22px;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+ overflow: auto;
+ flex: 1;
+}
+
+/* KPI STATS */
+.alm-stats {
+ display: grid;
+ grid-template-columns: 220px 1fr 1fr 1fr;
+ gap: 14px;
+}
+
+.alm-stat-card {
+ background: #fff;
+ border: 1px solid var(--alm-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.alm-stat-l {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--alm-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.alm-stat-v {
+ font-size: 20px;
+ font-weight: 700;
+ color: var(--alm-g900);
+ line-height: 1.2;
+}
+
+.alm-stat-meta {
+ font-size: 11px;
+ color: var(--alm-g500);
+ margin-top: 4px;
+
+ &.pos {
+ color: var(--alm-good);
+ font-weight: 600;
+ }
+
+ &.neg {
+ color: var(--alm-low);
+ font-weight: 600;
+ }
+}
+
+.alm-stat-gauge {
+ align-items: center;
+ justify-content: center;
+}
+
+/* GAUGE */
+.gauge {
+ width: 140px;
+ height: 140px;
+ border-radius: 50%;
+ background: conic-gradient(currentcolor calc(var(--pct) * 1%), var(--alm-g100) 0);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &.on-target {
+ color: var(--alm-good);
+ }
+
+ &.below {
+ color: var(--alm-low);
+ }
+}
+
+.gauge-inner {
+ width: 108px;
+ height: 108px;
+ border-radius: 50%;
+ background: #fff;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ box-shadow: inset 0 0 0 1px var(--alm-g200);
+}
+
+.gauge-pct {
+ font-size: 20px;
+ font-weight: 700;
+ color: var(--alm-g900);
+ line-height: 1;
+}
+
+.gauge-label {
+ font-size: 10px;
+ color: var(--alm-g500);
+ margin-top: 4px;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-weight: 600;
+}
+
+/* SECTIONS */
+.alm-section {
+ background: #fff;
+ border: 1px solid var(--alm-g200);
+ border-radius: 12px;
+ padding: 16px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.alm-sec-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 14px;
+ flex-wrap: wrap;
+}
+
+.alm-sec-t {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--alm-g700);
+}
+
+.alm-sec-actions {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+}
+
+.alm-sel {
+ height: 34px;
+ padding: 0 10px;
+ border: 1px solid var(--alm-g200);
+ border-radius: 6px;
+ background: #fff;
+ font-family: inherit;
+ font-size: 12px;
+ color: var(--alm-g900);
+
+ &:focus {
+ outline: none;
+ border-color: var(--alm-indigo);
+ box-shadow: 0 0 0 3px rgb(79 70 229 / 12%);
+ }
+}
+
+.alm-circular {
+ font-family: 'DM Mono', monospace;
+ font-size: 11px;
+ color: var(--alm-g500);
+ padding: 4px 10px;
+ background: var(--alm-g50);
+ border-radius: 6px;
+ border: 1px solid var(--alm-g200);
+}
+
+.alm-btn-xls {
+ font-size: 12px !important;
+ height: 34px !important;
+ color: #fff !important;
+ background: var(--alm-indigo) !important;
+}
+
+/* TOLERANCE BARS */
+.alm-tolerance {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.alm-tol-row {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+
+ &.good .alm-tol-fill {
+ background: var(--alm-good);
+ }
+
+ &.bad .alm-tol-fill {
+ background: var(--alm-low);
+ }
+
+ &.good .alm-tol-st {
+ background: var(--alm-good-l);
+ color: var(--alm-good);
+ }
+
+ &.bad .alm-tol-st {
+ background: var(--alm-low-l);
+ color: var(--alm-low);
+ }
+}
+
+.alm-tol-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.alm-tol-l {
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--alm-g700);
+
+ fa-icon {
+ color: var(--alm-indigo);
+ }
+}
+
+.alm-tol-p {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 13px;
+
+ strong {
+ font-size: 14px;
+ color: var(--alm-g900);
+ }
+
+ .alm-tol-lim {
+ font-size: 11px;
+ color: var(--alm-g500);
+ }
+
+ .alm-tol-st {
+ font-size: 10px;
+ font-weight: 700;
+ padding: 2px 8px;
+ border-radius: 999px;
+ }
+}
+
+.alm-tol-track {
+ position: relative;
+ height: 8px;
+ background: var(--alm-g100);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.alm-tol-fill {
+ height: 100%;
+ border-radius: 4px;
+ transition: width 0.4s ease-out;
+}
+
+.alm-tol-marker {
+ position: absolute;
+ top: -2px;
+ bottom: -2px;
+ left: 50%;
+ width: 2px;
+ background: var(--alm-g500);
+}
+
+/* HEATMAP */
+.alm-heatmap {
+ display: grid;
+ grid-template-columns: repeat(11, 1fr);
+ gap: 6px;
+}
+
+.heat-cell {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 10px 6px;
+ border-radius: 8px;
+ text-align: center;
+ cursor: default;
+ transition: transform 0.15s;
+
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 8px rgb(0 0 0 / 8%);
+ }
+
+ &.surplus {
+ background: var(--alm-good-l);
+ color: var(--alm-good);
+ }
+
+ &.deficit {
+ background: var(--alm-low-l);
+ color: var(--alm-low);
+ }
+
+ &.balanced {
+ background: var(--alm-g100);
+ color: var(--alm-g500);
+ }
+}
+
+.heat-code {
+ font-family: 'DM Mono', monospace;
+ font-size: 10px;
+ font-weight: 700;
+ opacity: 0.7;
+}
+
+.heat-pct {
+ font-size: 14px;
+ font-weight: 800;
+ margin-top: 2px;
+}
+
+.heat-lbl {
+ font-size: 9px;
+ margin-top: 4px;
+ opacity: 0.85;
+ line-height: 1.1;
+}
+
+/* INFO GRID */
+.alm-info-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px 24px;
+}
+
+.alm-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.alm-info-l {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--alm-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.alm-info-v {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--alm-g900);
+}
+
+/* TABLE */
+.alm-tbl-wrap {
+ border: 1px solid var(--alm-g200);
+ border-radius: 8px;
+ overflow: auto;
+ margin-bottom: 12px;
+}
+
+.alm-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 10px 14px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--alm-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ background: var(--alm-g50);
+ border-bottom: 1px solid var(--alm-g200);
+ white-space: nowrap;
+ }
+
+ td {
+ padding: 10px 14px;
+ border-bottom: 1px solid var(--alm-g100);
+ font-size: 13px;
+ color: var(--alm-g700);
+ vertical-align: middle;
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--alm-g50);
+ }
+
+ .row-total td {
+ background: var(--alm-indigo-l);
+ color: var(--alm-g900);
+ font-weight: 700;
+ border-top: 2px solid var(--alm-indigo);
+ }
+
+ .grp-liab {
+ background: var(--alm-low-l) !important;
+ color: var(--alm-low) !important;
+ }
+
+ .grp-asset {
+ background: var(--alm-good-l) !important;
+ color: var(--alm-good) !important;
+ }
+}
+
+.status-pill {
+ display: inline-block;
+ padding: 3px 10px;
+ font-size: 11px;
+ font-weight: 600;
+ border-radius: 999px;
+
+ &.on-target,
+ &.surplus {
+ background: var(--alm-good-l);
+ color: var(--alm-good);
+ }
+
+ &.below,
+ &.deficit {
+ background: var(--alm-low-l);
+ color: var(--alm-low);
+ }
+
+ &.balanced {
+ background: var(--alm-g100);
+ color: var(--alm-g500);
+ }
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 12px;
+}
+
+.pos {
+ color: var(--alm-good);
+}
+
+.neg {
+ color: var(--alm-low);
+}
+
+/* FORM ALM-1 */
+.form-iii {
+ background: var(--alm-g50);
+ border: 1px solid var(--alm-g200);
+ border-radius: 10px;
+ padding: 18px;
+}
+
+.form-iii-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding-bottom: 12px;
+ margin-bottom: 14px;
+ border-bottom: 2px solid var(--alm-indigo);
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.form-iii-bank {
+ font-size: 14px;
+ color: var(--alm-g900);
+}
+
+.form-iii-meta {
+ display: flex;
+ gap: 18px;
+ font-size: 12px;
+ color: var(--alm-g500);
+ flex-wrap: wrap;
+
+ fa-icon {
+ color: var(--alm-indigo);
+ }
+}
+
+.form-iii-t {
+ font-size: 13px;
+ font-weight: 700;
+ color: var(--alm-g700);
+ margin: 16px 0 8px;
+ padding-bottom: 4px;
+ border-bottom: 1px dashed var(--alm-g200);
+}
+
+.form-tbl {
+ background: #fff;
+ border: 1px solid var(--alm-g200);
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+.form-total td {
+ background: var(--alm-indigo-l) !important;
+ font-weight: 700;
+ color: var(--alm-g900);
+}
+
+.form-iii-foot {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 18px;
+ margin-top: 18px;
+ padding-top: 16px;
+ border-top: 1px dashed var(--alm-g300);
+}
+
+.cert {
+ background: #fff;
+ border: 1px solid var(--alm-g200);
+ border-radius: 8px;
+ padding: 12px 14px;
+}
+
+.cert-l {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--alm-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.cert-v {
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--alm-g900);
+ margin: 4px 0;
+}
+
+.cert-r {
+ font-size: 11px;
+ color: var(--alm-g500);
+}
+
+/* RESPONSIVE */
+@media (width <= 1100px) {
+ .alm-stats {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .alm-info-grid {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .form-iii-foot {
+ grid-template-columns: 1fr;
+ }
+
+ .alm-heatmap {
+ grid-template-columns: repeat(6, 1fr);
+ }
+}
+
+@media (width <= 720px) {
+ .alm-stats,
+ .alm-info-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .alm-head {
+ flex-wrap: wrap;
+ }
+
+ .alm-heatmap {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
diff --git a/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.ts b/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.ts
new file mode 100644
index 000000000..c15e6b559
--- /dev/null
+++ b/src/app/home/dashboard/alm-report-dialog/alm-report-dialog.component.ts
@@ -0,0 +1,468 @@
+/**
+ * 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/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { CommonModule, DecimalPipe } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+
+interface MaturityBucket {
+ code: string;
+ label: string;
+ /** Outflows / Liabilities (₹ Lakhs) */
+ outflow: number;
+ /** Inflows / Assets (₹ Lakhs) */
+ inflow: number;
+}
+
+interface MonthSnapshot {
+ month: string;
+ reserveProv: number;
+ borrowings: number;
+ fds: number;
+ cashOnHand: number;
+ investments: number;
+ otherAssets: number;
+}
+
+@Component({
+ selector: 'mifosx-alm-report-dialog',
+ standalone: true,
+ templateUrl: './alm-report-dialog.component.html',
+ styleUrls: ['./alm-report-dialog.component.scss'],
+ imports: [
+ CommonModule,
+ DecimalPipe,
+ FormsModule,
+ MatDialogModule,
+ MatButtonModule,
+ MatTooltipModule,
+ FaIconComponent
+ ]
+})
+export class AlmReportDialogComponent {
+ private dialogRef = inject(MatDialogRef);
+
+ activeTab: 'dashboard' | 'ladder' | 'monthly' | 'returns' = 'dashboard';
+
+ bank = {
+ name: 'Bharat Urban Bank Ltd',
+ branch: 'Head Office',
+ asOnDate: '31-03-2026',
+ quarter: 'Q4 FY 2025-26',
+ fiscalYear: '2025-26',
+ rbiCircular: 'DCBR.BPD.(PCB).Cir.No.4/12.05.001/2018-19',
+ principalOfficer: 'Karthik Kamath',
+ formCode: 'Form ALM-1',
+ reportRefNo: '1036',
+ userCode: 'BUBL01',
+ /** Net Demand & Time Liabilities — denominator for ratios */
+ ndtl: 740000, // ₹7,400 Cr in Lakhs
+ /** RBI tolerance: cumulative negative gap up to 14 days ≤ 5%, 28 days ≤ 10% */
+ tolerance14d: 5,
+ tolerance28d: 10
+ };
+
+ /** Standard RBI ALM time buckets per Master Direction */
+ buckets: MaturityBucket[] = [
+ { code: 'B01', label: 'Day 1', outflow: 1840, inflow: 2120 },
+ { code: 'B02', label: '2 to 7 days', outflow: 6420, inflow: 5880 },
+ { code: 'B03', label: '8 to 14 days', outflow: 4180, inflow: 4360 },
+ { code: 'B04', label: '15 to 30 days', outflow: 8240, inflow: 7920 },
+ { code: 'B05', label: '31 days to 2 months', outflow: 12480, inflow: 13160 },
+ { code: 'B06', label: '> 2 months to 3 months', outflow: 9620, inflow: 10240 },
+ { code: 'B07', label: '> 3 months to 6 months', outflow: 28640, inflow: 32180 },
+ { code: 'B08', label: '> 6 months to 1 year', outflow: 64280, inflow: 68420 },
+ { code: 'B09', label: '> 1 year to 3 years', outflow: 142800, inflow: 156400 },
+ { code: 'B10', label: '> 3 years to 5 years', outflow: 96400, inflow: 108200 },
+ { code: 'B11', label: 'Over 5 years', outflow: 124800, inflow: 138400 }
+ ];
+
+ /** Monthly snapshot — mirrors the format of the legacy ALM Excel */
+ monthly: MonthSnapshot[] = [
+ {
+ month: 'APR-2025',
+ reserveProv: 18420,
+ borrowings: 24180,
+ fds: 462800,
+ cashOnHand: 8420,
+ investments: 184600,
+ otherAssets: 12480
+ },
+ {
+ month: 'MAY-2025',
+ reserveProv: 18640,
+ borrowings: 23980,
+ fds: 468200,
+ cashOnHand: 8680,
+ investments: 186400,
+ otherAssets: 12620
+ },
+ {
+ month: 'JUN-2025',
+ reserveProv: 18820,
+ borrowings: 23640,
+ fds: 472400,
+ cashOnHand: 9120,
+ investments: 188200,
+ otherAssets: 12840
+ },
+ {
+ month: 'JUL-2025',
+ reserveProv: 19080,
+ borrowings: 23420,
+ fds: 478600,
+ cashOnHand: 9280,
+ investments: 190400,
+ otherAssets: 13020
+ },
+ {
+ month: 'AUG-2025',
+ reserveProv: 19280,
+ borrowings: 23180,
+ fds: 482400,
+ cashOnHand: 9420,
+ investments: 192800,
+ otherAssets: 13280
+ },
+ {
+ month: 'SEP-2025',
+ reserveProv: 19460,
+ borrowings: 22940,
+ fds: 488200,
+ cashOnHand: 9640,
+ investments: 195200,
+ otherAssets: 13420
+ },
+ {
+ month: 'OCT-2025',
+ reserveProv: 19680,
+ borrowings: 22680,
+ fds: 492600,
+ cashOnHand: 9820,
+ investments: 197600,
+ otherAssets: 13620
+ },
+ {
+ month: 'NOV-2025',
+ reserveProv: 19880,
+ borrowings: 22420,
+ fds: 498400,
+ cashOnHand: 9980,
+ investments: 200200,
+ otherAssets: 13820
+ },
+ {
+ month: 'DEC-2025',
+ reserveProv: 20120,
+ borrowings: 22180,
+ fds: 504200,
+ cashOnHand: 10240,
+ investments: 202800,
+ otherAssets: 14040
+ },
+ {
+ month: 'JAN-2026',
+ reserveProv: 20340,
+ borrowings: 21940,
+ fds: 508800,
+ cashOnHand: 10420,
+ investments: 205200,
+ otherAssets: 14240
+ },
+ {
+ month: 'FEB-2026',
+ reserveProv: 20580,
+ borrowings: 21680,
+ fds: 514600,
+ cashOnHand: 10620,
+ investments: 207800,
+ otherAssets: 14460
+ },
+ {
+ month: 'MAR-2026',
+ reserveProv: 20840,
+ borrowings: 21420,
+ fds: 520400,
+ cashOnHand: 10820,
+ investments: 210400,
+ otherAssets: 14680
+ }
+ ];
+
+ filterBucket = '';
+
+ // -------------------- Computed --------------------
+ bucketGap(b: MaturityBucket): number {
+ return b.inflow - b.outflow;
+ }
+ bucketGapPct(b: MaturityBucket): number {
+ return b.outflow === 0 ? 0 : +(((b.inflow - b.outflow) / b.outflow) * 100).toFixed(2);
+ }
+ cumulativeOutflow(idx: number): number {
+ return this.buckets.slice(0, idx + 1).reduce((s, b) => s + b.outflow, 0);
+ }
+ cumulativeInflow(idx: number): number {
+ return this.buckets.slice(0, idx + 1).reduce((s, b) => s + b.inflow, 0);
+ }
+ cumulativeGap(idx: number): number {
+ return this.cumulativeInflow(idx) - this.cumulativeOutflow(idx);
+ }
+ cumulativeGapPct(idx: number): number {
+ const out = this.cumulativeOutflow(idx);
+ return out === 0 ? 0 : +((this.cumulativeGap(idx) / out) * 100).toFixed(2);
+ }
+
+ get totalOutflow(): number {
+ return this.buckets.reduce((s, b) => s + b.outflow, 0);
+ }
+ get totalInflow(): number {
+ return this.buckets.reduce((s, b) => s + b.inflow, 0);
+ }
+ get totalGap(): number {
+ return this.totalInflow - this.totalOutflow;
+ }
+ get totalGapPct(): number {
+ return this.totalOutflow === 0 ? 0 : +((this.totalGap / this.totalOutflow) * 100).toFixed(2);
+ }
+
+ /** RBI tolerance test: cumulative negative gap up to bucket 3 (14 days) ≤ 5% of cumulative outflow */
+ get gap14dPct(): number {
+ return this.cumulativeGapPct(2);
+ }
+ /** Up to bucket 4 (30 days) ≤ 10% */
+ get gap28dPct(): number {
+ return this.cumulativeGapPct(3);
+ }
+ get gap14dWithinTolerance(): boolean {
+ return this.gap14dPct >= -this.bank.tolerance14d;
+ }
+ get gap28dWithinTolerance(): boolean {
+ return this.gap28dPct >= -this.bank.tolerance28d;
+ }
+
+ bucketStatus(b: MaturityBucket): 'surplus' | 'deficit' | 'balanced' {
+ const gap = this.bucketGap(b);
+ if (gap > 0) return 'surplus';
+ if (gap < 0) return 'deficit';
+ return 'balanced';
+ }
+
+ /** Monthly totals helpers */
+ monthTotalLiab(m: MonthSnapshot): number {
+ return m.reserveProv + m.borrowings + m.fds;
+ }
+ monthTotalAssets(m: MonthSnapshot): number {
+ return m.cashOnHand + m.investments + m.otherAssets;
+ }
+ /** Average across months */
+ avg(key: K): number {
+ if (typeof this.monthly[0][key] !== 'number') return 0;
+ const total = this.monthly.reduce((s, m) => s + (m[key] as number), 0);
+ return +(total / this.monthly.length).toFixed(2);
+ }
+
+ // -------------------- Filter --------------------
+ get filteredBuckets(): MaturityBucket[] {
+ if (!this.filterBucket) return this.buckets;
+ if (this.filterBucket === 'short') return this.buckets.slice(0, 4);
+ if (this.filterBucket === 'medium') return this.buckets.slice(4, 8);
+ if (this.filterBucket === 'long') return this.buckets.slice(8);
+ return this.buckets;
+ }
+
+ // -------------------- Excel Exports --------------------
+
+ /** Form ALM-1 — Statement of Structural Liquidity */
+ exportFormAlm(): void {
+ const rows: string[][] = [];
+ rows.push([
+ this.bank.name,
+ this.bank.branch
+ ]);
+ rows.push([
+ `STATEMENT OF STRUCTURAL LIQUIDITY (${this.bank.formCode})`,
+ `As On ${this.bank.asOnDate}`
+ ]);
+ rows.push([
+ `RBI Circular: ${this.bank.rbiCircular}`,
+ `Quarter: ${this.bank.quarter}`
+ ]);
+ rows.push([
+ `Report Ref: ${this.bank.reportRefNo}`,
+ `User Code: ${this.bank.userCode}`
+ ]);
+ rows.push([]);
+ rows.push([
+ 'NDTL (₹ Lakhs)',
+ this.bank.ndtl.toString()
+ ]);
+ rows.push([]);
+ rows.push(['Maturity Ladder (₹ in Lakhs)']);
+ rows.push([
+ 'Bucket',
+ 'Time Period',
+ 'Outflows (Liabilities)',
+ 'Inflows (Assets)',
+ 'Mismatch (Gap)',
+ 'Cum. Outflows',
+ 'Cum. Inflows',
+ 'Cum. Gap',
+ 'Cum. Gap % of Outflow'
+ ]);
+ this.buckets.forEach((b, i) => {
+ rows.push([
+ b.code,
+ b.label,
+ b.outflow.toFixed(2),
+ b.inflow.toFixed(2),
+ this.bucketGap(b).toFixed(2),
+ this.cumulativeOutflow(i).toFixed(2),
+ this.cumulativeInflow(i).toFixed(2),
+ this.cumulativeGap(i).toFixed(2),
+ this.cumulativeGapPct(i).toFixed(2) + '%'
+ ]);
+ });
+ rows.push([]);
+ rows.push([
+ 'TOTAL',
+ '',
+ this.totalOutflow.toFixed(2),
+ this.totalInflow.toFixed(2),
+ this.totalGap.toFixed(2)
+ ]);
+ rows.push([]);
+ rows.push(['RBI Tolerance Compliance']);
+ rows.push([
+ 'Cumulative Gap up to 14 days',
+ this.gap14dPct.toFixed(2) + '%',
+ `Limit: -${this.bank.tolerance14d}%`,
+ this.gap14dWithinTolerance ? 'WITHIN' : 'BREACH'
+ ]);
+ rows.push([
+ 'Cumulative Gap up to 30 days',
+ this.gap28dPct.toFixed(2) + '%',
+ `Limit: -${this.bank.tolerance28d}%`,
+ this.gap28dWithinTolerance ? 'WITHIN' : 'BREACH'
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Certified by',
+ this.bank.principalOfficer + ' — Principal Officer'
+ ]);
+ this.saveTsv(rows, `ALM-StructuralLiquidity-${this.dateTag()}.xls`);
+ }
+
+ /** Monthly snapshot export */
+ exportMonthlyXls(): void {
+ const rows: string[][] = [];
+ rows.push([
+ `${this.bank.name} — ${this.bank.branch}`,
+ `Monthly ALM Snapshot — FY ${this.bank.fiscalYear}`
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Month',
+ 'Reserve/Prov (Excl. OIR)',
+ 'Borrowings',
+ 'FDs',
+ 'Total Liabilities',
+ 'Cash on Hand',
+ 'Investments incl. Bal/Repo',
+ 'Other Assets (Excl. IRA)',
+ 'Total Assets'
+ ]);
+ for (const m of this.monthly) {
+ rows.push([
+ m.month,
+ m.reserveProv.toFixed(2),
+ m.borrowings.toFixed(2),
+ m.fds.toFixed(2),
+ this.monthTotalLiab(m).toFixed(2),
+ m.cashOnHand.toFixed(2),
+ m.investments.toFixed(2),
+ m.otherAssets.toFixed(2),
+ this.monthTotalAssets(m).toFixed(2)
+ ]);
+ }
+ rows.push([
+ 'AVERAGE',
+ this.avg('reserveProv').toFixed(2),
+ this.avg('borrowings').toFixed(2),
+ this.avg('fds').toFixed(2),
+ (this.avg('reserveProv') + this.avg('borrowings') + this.avg('fds')).toFixed(2),
+ this.avg('cashOnHand').toFixed(2),
+ this.avg('investments').toFixed(2),
+ this.avg('otherAssets').toFixed(2),
+ (this.avg('cashOnHand') + this.avg('investments') + this.avg('otherAssets')).toFixed(2)
+ ]);
+ this.saveTsv(rows, `ALM-Monthly-${this.dateTag()}.xls`);
+ }
+
+ /** Maturity ladder CSV */
+ exportLadderCsv(): void {
+ const headers = [
+ 'Bucket',
+ 'Time Period',
+ 'Outflow (₹ Lakh)',
+ 'Inflow (₹ Lakh)',
+ 'Gap',
+ 'Gap %'
+ ];
+ const rows = [headers];
+ for (const b of this.buckets) {
+ rows.push([
+ b.code,
+ b.label,
+ b.outflow.toString(),
+ b.inflow.toString(),
+ this.bucketGap(b).toString(),
+ this.bucketGapPct(b).toFixed(2) + '%'
+ ]);
+ }
+ this.saveCsv(rows, `ALM-Ladder-${this.dateTag()}.csv`);
+ }
+
+ setTab(tab: 'dashboard' | 'ladder' | 'monthly' | 'returns'): void {
+ this.activeTab = tab;
+ }
+ close(): void {
+ this.dialogRef.close();
+ }
+
+ // ---- helpers ----
+ private dateTag(): string {
+ const d = new Date();
+ return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
+ }
+ private saveTsv(rows: string[][], filename: string): void {
+ const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
+ this.saveBlob(tsv, filename, 'application/vnd.ms-excel');
+ }
+ private saveCsv(rows: string[][], filename: string): void {
+ const escape = (v: string): string => {
+ const s = (v || '').replace(/"/g, '""');
+ if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
+ return s;
+ };
+ const csv = rows.map((r) => r.map(escape).join(',')).join('\n') + '\n';
+ this.saveBlob(csv, filename, 'text/csv');
+ }
+ private saveBlob(content: string, filename: string, mime: string): void {
+ const blob = new Blob([content], { type: `${mime};charset=utf-8` });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+}
diff --git a/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.html b/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.html
new file mode 100644
index 000000000..1c797103e
--- /dev/null
+++ b/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.html
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
Credit Information Companies — Data Quality Report
+
+ Reporting period: {{ reportingPeriod }} · as per RBI Master Direction on Credit
+ Information Reporting
+
+
+
+
+
+ Download All
+
+ ✕
+
+
+
+
+
+
+
+ {{ r.name }}
+ {{ r.dqrPct.toFixed(1) }}%
+
+
+
+
+
+
+
+
+
+
{{ current.dqrPct.toFixed(1) }}%
+
DQR Score
+
+
+
+
+
Status
+
+ {{ current.status }}
+
+
+ File: {{ current.fileRef }}
+
+
+
+
Last Submitted
+
{{ current.lastSubmitted }}
+
Next due: {{ current.nextDue }}
+
+
+
Records This Cycle
+
{{ current.totalRecords | number }}
+
+ {{ current.accepted | number }} accepted
+ {{ current.warnings | number }} warnings
+ {{ current.rejected | number }} rejected
+
+
+
+
+
+
+
Sub-DQR Breakdown
+
+
+
+ {{ m.label }}
+ {{ m.pct.toFixed(1) }}%
+
+
+
+
+
+
+
+
+
+
Error & Warning Details — Last Submission
+
+ Download Excel
+
+
+
+
+
+
+ Code
+ Segment
+ Description
+ Severity
+ Records
+
+
+
+
+ {{ e.code }}
+ {{ e.segment }}
+ {{ e.description }}
+
+ {{ e.severity }}
+
+ {{ e.count | number }}
+
+
+
+
+
+
+
diff --git a/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.scss b/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.scss
new file mode 100644
index 000000000..b0c648294
--- /dev/null
+++ b/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.scss
@@ -0,0 +1,528 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --cic-good: #15803d;
+ --cic-good-l: #dcfce7;
+ --cic-mid: #d97706;
+ --cic-mid-l: #fef3c7;
+ --cic-low: #c0392b;
+ --cic-low-l: #fdecea;
+ --cic-purple: #6d28d9;
+ --cic-purple-l: #ede9fe;
+ --cic-g50: #f8fafc;
+ --cic-g100: #f1f5f9;
+ --cic-g200: #e2e8f0;
+ --cic-g300: #cbd5e1;
+ --cic-g500: #64748b;
+ --cic-g700: #334155;
+ --cic-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ color: var(--cic-g900);
+}
+
+.cic-dlg {
+ background: #fff;
+ border-radius: 14px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ max-height: 90vh;
+}
+
+/* HEADER */
+.cic-head {
+ padding: 18px 22px;
+ border-bottom: 1px solid var(--cic-g200);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-shrink: 0;
+ background: linear-gradient(135deg, #f9fafb 0%, #fff 100%);
+}
+
+.cic-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ flex: 1;
+}
+
+.cic-head-ic {
+ width: 44px;
+ height: 44px;
+ border-radius: 11px;
+ background: var(--cic-purple-l);
+ color: var(--cic-purple);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.cic-title {
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+ color: var(--cic-g900);
+}
+
+.cic-sub {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--cic-g500);
+}
+
+.cic-head-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.cic-btn-ghost {
+ font-size: 12px !important;
+ height: 36px !important;
+}
+
+.cic-close {
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ border: 1px solid var(--cic-g200);
+ background: #fff;
+ font-size: 14px;
+ color: var(--cic-g500);
+ cursor: pointer;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--cic-g100);
+ color: var(--cic-g900);
+ }
+}
+
+/* TABS */
+.cic-tabs {
+ display: flex;
+ gap: 0;
+ background: var(--cic-g50);
+ border-bottom: 1px solid var(--cic-g200);
+ padding: 0 22px;
+ overflow-x: auto;
+ flex-shrink: 0;
+}
+
+.cic-tab {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 16px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-family: inherit;
+ border-bottom: 2px solid transparent;
+ transition: all 0.15s;
+ white-space: nowrap;
+
+ &:hover {
+ background: rgb(255 255 255 / 50%);
+ }
+
+ &.active {
+ background: #fff;
+ border-bottom-color: var(--cic-purple);
+ }
+}
+
+.cic-tab-logo {
+ width: 28px;
+ height: 28px;
+ border-radius: 6px;
+ flex-shrink: 0;
+}
+
+.cic-tab-name {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--cic-g700);
+}
+
+.cic-tab-pct {
+ font-size: 11px;
+ font-weight: 700;
+ padding: 2px 8px;
+ border-radius: 999px;
+
+ &.good {
+ background: var(--cic-good-l);
+ color: var(--cic-good);
+ }
+
+ &.moderate {
+ background: var(--cic-mid-l);
+ color: var(--cic-mid);
+ }
+
+ &.low {
+ background: var(--cic-low-l);
+ color: var(--cic-low);
+ }
+}
+
+/* BODY */
+.cic-body {
+ padding: 18px 22px;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+ overflow: auto;
+ flex: 1;
+}
+
+/* TOP STAT CARDS */
+.cic-stats {
+ display: grid;
+ grid-template-columns: 220px 1fr 1fr 1.4fr;
+ gap: 14px;
+}
+
+.cic-stat-card {
+ background: #fff;
+ border: 1px solid var(--cic-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.cic-stat-l {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--cic-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.cic-stat-v {
+ font-size: 18px;
+ font-weight: 700;
+ color: var(--cic-g900);
+ line-height: 1.2;
+}
+
+.cic-stat-meta {
+ font-size: 11px;
+ color: var(--cic-g500);
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ margin-top: 4px;
+}
+
+.cic-stat-gauge {
+ align-items: center;
+ justify-content: center;
+}
+
+/* GAUGE */
+.gauge {
+ width: 140px;
+ height: 140px;
+ border-radius: 50%;
+ background: conic-gradient(currentcolor calc(var(--pct) * 1%), var(--cic-g100) 0);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &.good {
+ color: var(--cic-good);
+ }
+
+ &.moderate {
+ color: var(--cic-mid);
+ }
+
+ &.low {
+ color: var(--cic-low);
+ }
+}
+
+.gauge-inner {
+ width: 108px;
+ height: 108px;
+ border-radius: 50%;
+ background: #fff;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ box-shadow: inset 0 0 0 1px var(--cic-g200);
+}
+
+.gauge-pct {
+ font-size: 22px;
+ font-weight: 700;
+ color: var(--cic-g900);
+ line-height: 1;
+}
+
+.gauge-label {
+ font-size: 10px;
+ color: var(--cic-g500);
+ margin-top: 2px;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-weight: 600;
+}
+
+/* STATUS PILL */
+.status-pill {
+ display: inline-block;
+ padding: 3px 10px;
+ font-size: 11px;
+ font-weight: 600;
+ border-radius: 999px;
+
+ &.st-good {
+ background: var(--cic-good-l);
+ color: var(--cic-good);
+ }
+
+ &.st-mid {
+ background: var(--cic-mid-l);
+ color: var(--cic-mid);
+ }
+
+ &.st-low {
+ background: var(--cic-low-l);
+ color: var(--cic-low);
+ }
+}
+
+/* MINI BADGES IN STAT META */
+.badge {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 999px;
+ font-size: 10px;
+ font-weight: 600;
+
+ &.ok {
+ background: var(--cic-good-l);
+ color: var(--cic-good);
+ }
+
+ &.warn {
+ background: var(--cic-mid-l);
+ color: var(--cic-mid);
+ }
+
+ &.err {
+ background: var(--cic-low-l);
+ color: var(--cic-low);
+ }
+}
+
+/* SECTIONS */
+.cic-section {
+ background: #fff;
+ border: 1px solid var(--cic-g200);
+ border-radius: 12px;
+ padding: 16px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.cic-sec-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+}
+
+.cic-sec-t {
+ margin: 0 0 12px;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--cic-g700);
+}
+
+.cic-sec-h .cic-sec-t {
+ margin-bottom: 0;
+}
+
+.cic-btn-xls {
+ font-size: 12px !important;
+ height: 34px !important;
+ color: #fff !important;
+ background: #16a34a !important;
+}
+
+/* SUB-DQR BARS */
+.cic-bars {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 14px 24px;
+}
+
+.cic-bar-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 5px;
+}
+
+.cic-bar-l {
+ font-size: 12px;
+ color: var(--cic-g700);
+ font-weight: 500;
+}
+
+.cic-bar-p {
+ font-size: 12px;
+ font-weight: 700;
+
+ &.good {
+ color: var(--cic-good);
+ }
+
+ &.moderate {
+ color: var(--cic-mid);
+ }
+
+ &.low {
+ color: var(--cic-low);
+ }
+}
+
+.cic-bar-track {
+ height: 7px;
+ background: var(--cic-g100);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.cic-bar-fill {
+ height: 100%;
+ border-radius: 4px;
+ transition: width 0.3s ease-out;
+
+ &.good {
+ background: var(--cic-good);
+ }
+
+ &.moderate {
+ background: var(--cic-mid);
+ }
+
+ &.low {
+ background: var(--cic-low);
+ }
+}
+
+/* ERRORS TABLE */
+.cic-tbl-wrap {
+ border: 1px solid var(--cic-g200);
+ border-radius: 8px;
+ overflow: hidden;
+}
+
+.cic-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 10px 14px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--cic-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ background: var(--cic-g50);
+ border-bottom: 1px solid var(--cic-g200);
+ }
+
+ td {
+ padding: 10px 14px;
+ border-bottom: 1px solid var(--cic-g100);
+ font-size: 13px;
+ color: var(--cic-g700);
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--cic-g50);
+ }
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 12px;
+}
+
+.sev {
+ display: inline-block;
+ padding: 2px 9px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 600;
+
+ &.sev-critical {
+ background: var(--cic-low-l);
+ color: var(--cic-low);
+ }
+
+ &.sev-warning {
+ background: var(--cic-mid-l);
+ color: var(--cic-mid);
+ }
+
+ &.sev-info {
+ background: #e0e7ff;
+ color: #4338ca;
+ }
+}
+
+/* RESPONSIVE */
+@media (width <= 1100px) {
+ .cic-stats {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .cic-bars {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (width <= 700px) {
+ .cic-stats {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.ts b/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.ts
new file mode 100644
index 000000000..2ba64760e
--- /dev/null
+++ b/src/app/home/dashboard/cic-dqr-dialog/cic-dqr-dialog.component.ts
@@ -0,0 +1,1032 @@
+/**
+ * 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/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { CommonModule, DecimalPipe } from '@angular/common';
+import { MatDialogRef, MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+
+interface DqrErrorRow {
+ code: string;
+ segment: string;
+ description: string;
+ severity: 'Critical' | 'Warning' | 'Info';
+ count: number;
+}
+
+interface DqrSubMetric {
+ label: string;
+ pct: number;
+}
+
+interface CicReport {
+ key: string;
+ name: string;
+ full: string;
+ logo: string;
+ dqrPct: number;
+ status: 'Submitted' | 'In Progress' | 'Pending';
+ lastSubmitted: string;
+ nextDue: string;
+ totalRecords: number;
+ accepted: number;
+ rejected: number;
+ warnings: number;
+ fileRef: string;
+ subMetrics: DqrSubMetric[];
+ errors: DqrErrorRow[];
+}
+
+/**
+ * CIBIL TUDF consumer + tradeline record (one row per loan account submitted to a CIC).
+ * Schema mirrors the real 72-column CIBIL Consumer file (e.g. COBBNS1059-Consumer-DDMMYYYY-HHMMSS.xls).
+ * NOTE: ASSEST_CLASSIFICATION typo is intentional — preserved as-is from the published CIBIL spec.
+ */
+interface ConsumerRecord {
+ CONSUMER_NAME: string;
+ DATE_OF_BIRTH: string;
+ GENDER: string;
+ INCOME_TAX_ID: string;
+ PASSPORT: string;
+ PASSPORT_ISSUE_DATE: string;
+ PASSPORT_EXPIRY_DATE: string;
+ VOTER_ID: string;
+ DRIVING_LICENSE_NO: string;
+ DRIVING_LICENSE_ISSUE_DATE: string;
+ DRIVING_LICENSE_EXPIRY_DATE: string;
+ RATION_CARD_NO: string;
+ UNIVERSAL_ID: string;
+ ADDITIONAL_ID_1: string;
+ ADDITIONAL_ID_2: string;
+ TELEPHONE_NO_MOBILE: string;
+ TELEPHONE_NO_RESIDENCE: string;
+ TELEPHONE_NO_OFFICE: string;
+ EXTENSION_OFFICE: string;
+ TELEPHONE_NO_OTHER: string;
+ EXTENSION_OTHER: string;
+ EMAIL_ID_1: string;
+ EMAIL_ID_2: string;
+ ADDRESS_1: string;
+ STATE_CODE_1: string;
+ PINCODE_1: string;
+ ADDRESS_CATEGORY_1: string;
+ RESIDENCE_CODE_1: string;
+ ADDRESS_2: string;
+ STATE_CODE_2: string;
+ PINCODE_2: string;
+ ADDRESS_CATEGORY_2: string;
+ RESIDENCE_CODE_2: string;
+ MEMBER_CODE: string;
+ MEMBER_SHORT_NAME: string;
+ AC_NUMBER: string;
+ AC_TYPE: string;
+ OWNERSHIP_IND: string;
+ OPENED_DATE: string;
+ LASTPAYMENTDATE: string;
+ DATE_CLOSED: string;
+ DATE_REPORTED: string;
+ SANCTIONED_AMT: string;
+ CURRENT_BALANCE: string;
+ AMOUNT_OVERDUE: string;
+ NO_OF_DAYS_PAST_DUE: string;
+ OLD_MEMBER_CODE: string;
+ OLD_MEMBER_NAME: string;
+ OLD_ACC_NO: string;
+ OLD_ACC_TYPE: string;
+ OLD_OWNERSHIP_INDICATOR: string;
+ SUIT_FILED: string;
+ CREDIT_FACILITY_STATUS: string;
+ ASSEST_CLASSIFICATION: string;
+ VALUE_OF_COLLATERAL: string;
+ TYPE_OF_COLLATERAL: string;
+ CREDIT_LIMIT: string;
+ CASH_LIMIT: string;
+ RATE_OF_INTEREST: string;
+ REPAYMENT_TENURE: string;
+ EMI_AMOUNT: string;
+ WRITTEN_OFF_AMOUNT_TOTAL: string;
+ WRITTEN_OFF_PRINCIPAL_AMOUNT: string;
+ SETTLEMENT_AMT: string;
+ PAYMENT_FREQUENCY: string;
+ ACTUAL_PAY_AMT: string;
+ OCCU_CODE: string;
+ INCOME: string;
+ NET_GROSS_INCOME_ID: string;
+ MONTHLY_ANNUAL_INCOME_ID: string;
+ CKYC_ID: string;
+ NREGA_CARD: string;
+}
+
+@Component({
+ selector: 'mifosx-cic-dqr-dialog',
+ standalone: true,
+ templateUrl: './cic-dqr-dialog.component.html',
+ styleUrls: ['./cic-dqr-dialog.component.scss'],
+ imports: [
+ CommonModule,
+ DecimalPipe,
+ MatDialogModule,
+ MatButtonModule,
+ MatTabsModule,
+ MatTooltipModule,
+ FaIconComponent
+ ]
+})
+export class CicDqrDialogComponent {
+ private dialogRef = inject(MatDialogRef);
+
+ activeTab: 'cibil' | 'experian' | 'equifax' | 'crif' = 'cibil';
+
+ /** Reporting period — current month */
+ today = new Date();
+ reportingPeriod: string;
+
+ reports: CicReport[] = [
+ {
+ key: 'cibil',
+ name: 'TransUnion CIBIL',
+ full: 'Credit Information Bureau India Limited',
+ logo: 'assets/images/cic-cibil.svg',
+ dqrPct: 98.2,
+ status: 'Submitted',
+ lastSubmitted: '07 Apr 2026',
+ nextDue: '07 May 2026',
+ totalRecords: 12420,
+ accepted: 12197,
+ rejected: 47,
+ warnings: 176,
+ fileRef: 'CIBIL-CIR-202604-INC4711.txt',
+ subMetrics: [
+ { label: 'Personal Data', pct: 99.4 },
+ { label: 'Identifier (PAN/UID)', pct: 98.8 },
+ { label: 'Account Data', pct: 98.1 },
+ { label: 'Address', pct: 96.7 },
+ { label: 'Phone / Email', pct: 97.9 }
+ ],
+ errors: [
+ {
+ code: 'CR-001',
+ segment: 'Personal',
+ description: 'Date of birth missing or invalid format',
+ severity: 'Critical',
+ count: 12
+ },
+ {
+ code: 'CR-014',
+ segment: 'Identifier',
+ description: 'PAN format mismatch (10-char alphanumeric)',
+ severity: 'Critical',
+ count: 6
+ },
+ {
+ code: 'CR-027',
+ segment: 'Account',
+ description: 'Reported amount > sanctioned amount',
+ severity: 'Critical',
+ count: 9
+ },
+ {
+ code: 'WN-103',
+ segment: 'Address',
+ description: 'PIN code not in 6-digit numeric format',
+ severity: 'Warning',
+ count: 64
+ },
+ {
+ code: 'WN-118',
+ segment: 'Phone',
+ description: 'Mobile starts with 0 (should be 6-9)',
+ severity: 'Warning',
+ count: 38
+ },
+ {
+ code: 'WN-141',
+ segment: 'Account',
+ description: 'Closed-date earlier than open-date',
+ severity: 'Warning',
+ count: 11
+ },
+ {
+ code: 'IN-204',
+ segment: 'Personal',
+ description: 'Father name contains numeric characters',
+ severity: 'Info',
+ count: 22
+ },
+ {
+ code: 'IN-211',
+ segment: 'Address',
+ description: 'Address line 1 exceeds 40 chars (truncated)',
+ severity: 'Info',
+ count: 41
+ }
+ ]
+ },
+ {
+ key: 'experian',
+ name: 'Experian',
+ full: 'Experian Credit Information Company of India Pvt. Ltd.',
+ logo: 'assets/images/cic-experian.svg',
+ dqrPct: 97.5,
+ status: 'Submitted',
+ lastSubmitted: '05 Apr 2026',
+ nextDue: '05 May 2026',
+ totalRecords: 12420,
+ accepted: 12108,
+ rejected: 78,
+ warnings: 234,
+ fileRef: 'EXP-CIR-202604-INC4711.xml',
+ subMetrics: [
+ { label: 'Personal Data', pct: 98.7 },
+ { label: 'Identifier (PAN/UID)', pct: 98.2 },
+ { label: 'Account Data', pct: 97.4 },
+ { label: 'Address', pct: 95.9 },
+ { label: 'Phone / Email', pct: 97.1 }
+ ],
+ errors: [
+ {
+ code: 'EX-CR-002',
+ segment: 'Personal',
+ description: 'Gender code not in [M/F/O]',
+ severity: 'Critical',
+ count: 18
+ },
+ {
+ code: 'EX-CR-019',
+ segment: 'Identifier',
+ description: 'Aadhaar (UID) checksum failed',
+ severity: 'Critical',
+ count: 8
+ },
+ {
+ code: 'EX-CR-031',
+ segment: 'Account',
+ description: 'EMI amount missing for term loan',
+ severity: 'Critical',
+ count: 14
+ },
+ {
+ code: 'EX-WN-105',
+ segment: 'Address',
+ description: 'State code outside valid ISO 3166-2',
+ severity: 'Warning',
+ count: 89
+ },
+ {
+ code: 'EX-WN-122',
+ segment: 'Phone',
+ description: 'Phone country code mismatch',
+ severity: 'Warning',
+ count: 27
+ },
+ {
+ code: 'EX-WN-148',
+ segment: 'Account',
+ description: 'Days-Past-Due reported negative',
+ severity: 'Warning',
+ count: 18
+ },
+ { code: 'EX-IN-202', segment: 'Personal', description: 'Marital status missing', severity: 'Info', count: 56 },
+ {
+ code: 'EX-IN-219',
+ segment: 'Address',
+ description: 'Permanent and current address identical',
+ severity: 'Info',
+ count: 44
+ }
+ ]
+ },
+ {
+ key: 'equifax',
+ name: 'Equifax',
+ full: 'Equifax Credit Information Services Pvt. Ltd.',
+ logo: 'assets/images/cic-equifax.svg',
+ dqrPct: 94.8,
+ status: 'In Progress',
+ lastSubmitted: '06 Apr 2026',
+ nextDue: '06 May 2026',
+ totalRecords: 12420,
+ accepted: 11774,
+ rejected: 142,
+ warnings: 504,
+ fileRef: 'EFX-CIR-202604-INC4711.txt',
+ subMetrics: [
+ { label: 'Personal Data', pct: 96.8 },
+ { label: 'Identifier (PAN/UID)', pct: 95.4 },
+ { label: 'Account Data', pct: 94.2 },
+ { label: 'Address', pct: 91.8 },
+ { label: 'Phone / Email', pct: 95.1 }
+ ],
+ errors: [
+ {
+ code: 'EQ-CR-008',
+ segment: 'Identifier',
+ description: 'PAN starts with non-letter',
+ severity: 'Critical',
+ count: 24
+ },
+ {
+ code: 'EQ-CR-022',
+ segment: 'Account',
+ description: 'Loan tenure < 1 month',
+ severity: 'Critical',
+ count: 19
+ },
+ { code: 'EQ-CR-035', segment: 'Personal', description: 'DOB > today', severity: 'Critical', count: 7 },
+ {
+ code: 'EQ-CR-048',
+ segment: 'Account',
+ description: 'Account holder type code unknown',
+ severity: 'Critical',
+ count: 12
+ },
+ {
+ code: 'EQ-WN-110',
+ segment: 'Address',
+ description: 'City not matched against pincode master',
+ severity: 'Warning',
+ count: 142
+ },
+ {
+ code: 'EQ-WN-127',
+ segment: 'Phone',
+ description: 'Landline missing area code',
+ severity: 'Warning',
+ count: 88
+ },
+ {
+ code: 'EQ-WN-156',
+ segment: 'Personal',
+ description: 'Father name same as customer name',
+ severity: 'Warning',
+ count: 31
+ },
+ {
+ code: 'EQ-IN-208',
+ segment: 'Address',
+ description: 'Country code defaulted to IN',
+ severity: 'Info',
+ count: 178
+ }
+ ]
+ },
+ {
+ key: 'crif',
+ name: 'CRIF High Mark',
+ full: 'CRIF High Mark Credit Information Services Pvt. Ltd.',
+ logo: 'assets/images/cic-crif.svg',
+ dqrPct: 96.1,
+ status: 'Submitted',
+ lastSubmitted: '04 Apr 2026',
+ nextDue: '04 May 2026',
+ totalRecords: 12420,
+ accepted: 11942,
+ rejected: 96,
+ warnings: 312,
+ fileRef: 'CRIF-CIR-202604-INC4711.txt',
+ subMetrics: [
+ { label: 'Personal Data', pct: 97.9 },
+ { label: 'Identifier (PAN/UID)', pct: 97.1 },
+ { label: 'Account Data', pct: 95.8 },
+ { label: 'Address', pct: 94.2 },
+ { label: 'Phone / Email', pct: 96.4 }
+ ],
+ errors: [
+ {
+ code: 'CF-CR-005',
+ segment: 'Personal',
+ description: 'Name contains special characters',
+ severity: 'Critical',
+ count: 11
+ },
+ {
+ code: 'CF-CR-018',
+ segment: 'Identifier',
+ description: 'Voter ID format invalid',
+ severity: 'Critical',
+ count: 9
+ },
+ {
+ code: 'CF-CR-029',
+ segment: 'Account',
+ description: 'Closure reason missing for closed account',
+ severity: 'Critical',
+ count: 16
+ },
+ {
+ code: 'CF-WN-107',
+ segment: 'Address',
+ description: 'Address change not flagged',
+ severity: 'Warning',
+ count: 78
+ },
+ {
+ code: 'CF-WN-124',
+ segment: 'Phone',
+ description: 'Mobile flagged DND but reported',
+ severity: 'Warning',
+ count: 42
+ },
+ {
+ code: 'CF-WN-152',
+ segment: 'Account',
+ description: 'Overdue amount but DPD = 0',
+ severity: 'Warning',
+ count: 19
+ },
+ {
+ code: 'CF-IN-205',
+ segment: 'Personal',
+ description: 'Email domain unverifiable',
+ severity: 'Info',
+ count: 88
+ },
+ {
+ code: 'CF-IN-217',
+ segment: 'Address',
+ description: 'Address quality score below threshold',
+ severity: 'Info',
+ count: 64
+ }
+ ]
+ }
+ ];
+
+ /**
+ * Sample consumer/tradeline rows mirroring the real CIBIL TUDF Consumer file
+ * (e.g. COBBNS1059-Consumer-DDMMYYYY.xls). Date format = DDMMYYYY (no separator);
+ * GENDER = '1' (Female) | '2' (Male); STATE_CODE = numeric (23=MP, 27=MH, 29=KA, 24=GJ, 09=UP, 33=TN, 07=DL).
+ * Codes used: AC_TYPE 02=HL, 03=AL, 04=TL, 05=PL, 07=GL, 10=CC, 15=Agri, 50=Property; OWNERSHIP_IND 1=Single,3=Guarantor,4=Joint;
+ * SUIT_FILED 00=No; CREDIT_FACILITY_STATUS 00=NoSuit/Active, 13=Closed; ASSEST_CLASSIFICATION 01=Std, 02=SMA-1, 03=SMA-2, 04=SS;
+ * PAYMENT_FREQUENCY 09=Monthly; OCCU_CODE 01=Salaried,02=Self-Emp,03=Profession,04=Retired; NET_GROSS_INCOME_ID 02=Gross.
+ */
+ consumers: ConsumerRecord[] = [
+ this.makeConsumer({
+ CONSUMER_NAME: 'PREM NARAYAN LODHI',
+ DATE_OF_BIRTH: '24121964',
+ GENDER: '2',
+ INCOME_TAX_ID: 'AOOPN5028H',
+ UNIVERSAL_ID: '981481904746',
+ TELEPHONE_NO_MOBILE: '9522384824',
+ ADDRESS_1: 'LAL PHATHAR BASODA BASODA BASODA',
+ STATE_CODE_1: '23',
+ PINCODE_1: '464221',
+ ADDRESS_CATEGORY_1: '01',
+ AC_NUMBER: '001131301000090',
+ AC_TYPE: '15',
+ OWNERSHIP_IND: '1',
+ OPENED_DATE: '16061995',
+ LASTPAYMENTDATE: '18112025',
+ SANCTIONED_AMT: '224000',
+ CURRENT_BALANCE: '751',
+ AMOUNT_OVERDUE: '0',
+ NO_OF_DAYS_PAST_DUE: '0',
+ OLD_OWNERSHIP_INDICATOR: '1',
+ SUIT_FILED: '00',
+ ASSEST_CLASSIFICATION: '01',
+ TYPE_OF_COLLATERAL: '00',
+ RATE_OF_INTEREST: '9',
+ PAYMENT_FREQUENCY: '09',
+ ACTUAL_PAY_AMT: '220000',
+ OCCU_CODE: '02',
+ CKYC_ID: 'NULL'
+ }),
+ this.makeConsumer({
+ CONSUMER_NAME: 'GHANSHYAM YADAV',
+ DATE_OF_BIRTH: '11081960',
+ GENDER: '2',
+ INCOME_TAX_ID: 'ACDPY7665C',
+ UNIVERSAL_ID: '237742690370',
+ TELEPHONE_NO_MOBILE: '9826822168',
+ ADDRESS_1: 'WARD NO 7 LAXMI NARAYAN MANDIR K EY PASS BASODA BASODA',
+ STATE_CODE_1: '23',
+ PINCODE_1: '464221',
+ ADDRESS_CATEGORY_1: '01',
+ AC_NUMBER: '001131301000140',
+ AC_TYPE: '15',
+ OWNERSHIP_IND: '1',
+ OPENED_DATE: '05021999',
+ LASTPAYMENTDATE: '15012026',
+ SANCTIONED_AMT: '795000',
+ CURRENT_BALANCE: '525798',
+ AMOUNT_OVERDUE: '0',
+ NO_OF_DAYS_PAST_DUE: '0',
+ OLD_OWNERSHIP_INDICATOR: '1',
+ SUIT_FILED: '00',
+ ASSEST_CLASSIFICATION: '01',
+ TYPE_OF_COLLATERAL: '00',
+ RATE_OF_INTEREST: '9.50',
+ PAYMENT_FREQUENCY: '09',
+ ACTUAL_PAY_AMT: '60000',
+ OCCU_CODE: '02',
+ CKYC_ID: '60022671103351'
+ }),
+ this.makeConsumer({
+ CONSUMER_NAME: 'RAVINDRA BHAVSAR',
+ DATE_OF_BIRTH: '03011960',
+ GENDER: '2',
+ INCOME_TAX_ID: 'AEVPB6489H',
+ UNIVERSAL_ID: '592561453214',
+ TELEPHONE_NO_MOBILE: '9826810033',
+ ADDRESS_1: 'WARD NO 24 HANUMAN CHOWK BASODA BASODA',
+ STATE_CODE_1: '23',
+ PINCODE_1: '464221',
+ ADDRESS_CATEGORY_1: '01',
+ AC_NUMBER: '001131301000155',
+ AC_TYPE: '15',
+ OWNERSHIP_IND: '1',
+ OPENED_DATE: '06112000',
+ LASTPAYMENTDATE: '17052025',
+ SANCTIONED_AMT: '265000',
+ CURRENT_BALANCE: '777',
+ AMOUNT_OVERDUE: '0',
+ NO_OF_DAYS_PAST_DUE: '0',
+ OLD_OWNERSHIP_INDICATOR: '1',
+ SUIT_FILED: '00',
+ ASSEST_CLASSIFICATION: '01',
+ TYPE_OF_COLLATERAL: '00',
+ RATE_OF_INTEREST: '9.50',
+ PAYMENT_FREQUENCY: '09',
+ ACTUAL_PAY_AMT: '3500',
+ OCCU_CODE: '02',
+ CKYC_ID: '60040198535752'
+ }),
+ this.makeConsumer({
+ CONSUMER_NAME: 'SMT VIJAYA DEVI',
+ ADDRESS_1: 'WO SHRI NIRMAL KUMAR JAIN GANDHI CHOAK GANJ BASODA',
+ STATE_CODE_1: '23',
+ PINCODE_1: '464221',
+ ADDRESS_CATEGORY_1: '01',
+ AC_NUMBER: '001135201000244',
+ AC_TYPE: '50',
+ OWNERSHIP_IND: '3',
+ OPENED_DATE: '06022003',
+ LASTPAYMENTDATE: '14012026',
+ SANCTIONED_AMT: '2850000',
+ CURRENT_BALANCE: '1533970',
+ AMOUNT_OVERDUE: '0',
+ NO_OF_DAYS_PAST_DUE: '0',
+ OLD_OWNERSHIP_INDICATOR: '3',
+ SUIT_FILED: '00',
+ ASSEST_CLASSIFICATION: '01',
+ VALUE_OF_COLLATERAL: '1850000',
+ TYPE_OF_COLLATERAL: '01',
+ RATE_OF_INTEREST: '11.50',
+ PAYMENT_FREQUENCY: '09',
+ ACTUAL_PAY_AMT: '1000000',
+ OCCU_CODE: '04',
+ CKYC_ID: 'NULL'
+ }),
+ this.makeConsumer({
+ CONSUMER_NAME: 'MUNNI BAI GUPTA',
+ DATE_OF_BIRTH: '01011979',
+ GENDER: '1',
+ INCOME_TAX_ID: 'ABDPG5525A',
+ UNIVERSAL_ID: '520037914822',
+ TELEPHONE_NO_MOBILE: '9425148784',
+ ADDRESS_1: 'WARD N.03 NEHRU CHOWK BASODA GANJ BASODA',
+ STATE_CODE_1: '23',
+ PINCODE_1: '464221',
+ ADDRESS_CATEGORY_1: '01',
+ AC_NUMBER: '001135801202520',
+ AC_TYPE: '04',
+ OWNERSHIP_IND: '4',
+ OPENED_DATE: '02042025',
+ LASTPAYMENTDATE: '02042025',
+ SANCTIONED_AMT: '1600000',
+ CURRENT_BALANCE: '1743547',
+ AMOUNT_OVERDUE: '0',
+ NO_OF_DAYS_PAST_DUE: '0',
+ OLD_OWNERSHIP_INDICATOR: '4',
+ SUIT_FILED: '00',
+ ASSEST_CLASSIFICATION: '01',
+ TYPE_OF_COLLATERAL: '00',
+ RATE_OF_INTEREST: '11.50',
+ PAYMENT_FREQUENCY: '09',
+ OCCU_CODE: '02',
+ CKYC_ID: 'NULL'
+ }),
+ this.makeConsumer({
+ CONSUMER_NAME: 'RAJESH KUMAR SHARMA',
+ DATE_OF_BIRTH: '10081985',
+ GENDER: '2',
+ INCOME_TAX_ID: 'AAQPS8560J',
+ UNIVERSAL_ID: '981400112233',
+ TELEPHONE_NO_MOBILE: '9892011001',
+ EMAIL_ID_1: 'rajesh@example.com',
+ ADDRESS_1: 'FLAT 402 SUNRISE TOWERS MAROL ANDHERI EAST',
+ STATE_CODE_1: '27',
+ PINCODE_1: '400069',
+ ADDRESS_CATEGORY_1: '01',
+ RESIDENCE_CODE_1: '01',
+ AC_NUMBER: '001131302000312',
+ AC_TYPE: '02',
+ OWNERSHIP_IND: '1',
+ OPENED_DATE: '15032020',
+ LASTPAYMENTDATE: '05042026',
+ SANCTIONED_AMT: '5000000',
+ CURRENT_BALANCE: '4200000',
+ AMOUNT_OVERDUE: '0',
+ NO_OF_DAYS_PAST_DUE: '0',
+ OLD_OWNERSHIP_INDICATOR: '1',
+ SUIT_FILED: '00',
+ ASSEST_CLASSIFICATION: '01',
+ VALUE_OF_COLLATERAL: '7000000',
+ TYPE_OF_COLLATERAL: '01',
+ RATE_OF_INTEREST: '8.50',
+ REPAYMENT_TENURE: '240',
+ EMI_AMOUNT: '45000',
+ PAYMENT_FREQUENCY: '09',
+ ACTUAL_PAY_AMT: '45000',
+ OCCU_CODE: '01',
+ INCOME: '750000',
+ NET_GROSS_INCOME_ID: '02',
+ MONTHLY_ANNUAL_INCOME_ID: '02',
+ CKYC_ID: '60011225033445'
+ }),
+ this.makeConsumer({
+ CONSUMER_NAME: 'PRIYA PATEL',
+ DATE_OF_BIRTH: '22031990',
+ GENDER: '1',
+ INCOME_TAX_ID: 'AKLPS9933P',
+ UNIVERSAL_ID: '982200221221',
+ TELEPHONE_NO_MOBILE: '9822055002',
+ EMAIL_ID_1: 'priya@example.com',
+ ADDRESS_1: 'B-12 SHIVAJI NAGAR FC ROAD',
+ STATE_CODE_1: '27',
+ PINCODE_1: '411005',
+ ADDRESS_CATEGORY_1: '01',
+ AC_NUMBER: '001131302000418',
+ AC_TYPE: '05',
+ OWNERSHIP_IND: '1',
+ OPENED_DATE: '10092023',
+ LASTPAYMENTDATE: '15032026',
+ SANCTIONED_AMT: '500000',
+ CURRENT_BALANCE: '320000',
+ AMOUNT_OVERDUE: '12500',
+ NO_OF_DAYS_PAST_DUE: '30',
+ OLD_OWNERSHIP_INDICATOR: '1',
+ SUIT_FILED: '00',
+ ASSEST_CLASSIFICATION: '02',
+ TYPE_OF_COLLATERAL: '00',
+ RATE_OF_INTEREST: '12.75',
+ REPAYMENT_TENURE: '36',
+ EMI_AMOUNT: '16700',
+ PAYMENT_FREQUENCY: '09',
+ ACTUAL_PAY_AMT: '16700',
+ OCCU_CODE: '02',
+ INCOME: '480000',
+ NET_GROSS_INCOME_ID: '02',
+ MONTHLY_ANNUAL_INCOME_ID: '02',
+ CKYC_ID: '60022144558899'
+ }),
+ this.makeConsumer({
+ CONSUMER_NAME: 'NIRAV SHAH',
+ DATE_OF_BIRTH: '18051992',
+ GENDER: '2',
+ INCOME_TAX_ID: 'AHKPS5544Z',
+ PASSPORT: 'M5544321',
+ PASSPORT_ISSUE_DATE: '12082020',
+ PASSPORT_EXPIRY_DATE: '11082030',
+ UNIVERSAL_ID: '982400993311',
+ TELEPHONE_NO_MOBILE: '9824012010',
+ EMAIL_ID_1: 'nirav@example.com',
+ ADDRESS_1: 'A-401 ATLANTIS APARTMENTS SATELLITE ROAD',
+ STATE_CODE_1: '24',
+ PINCODE_1: '380015',
+ ADDRESS_CATEGORY_1: '01',
+ AC_NUMBER: '001131302000511',
+ AC_TYPE: '02',
+ OWNERSHIP_IND: '1',
+ OPENED_DATE: '14022018',
+ LASTPAYMENTDATE: '20122025',
+ DATE_CLOSED: '20122025',
+ SANCTIONED_AMT: '7500000',
+ CURRENT_BALANCE: '0',
+ AMOUNT_OVERDUE: '0',
+ NO_OF_DAYS_PAST_DUE: '0',
+ OLD_OWNERSHIP_INDICATOR: '1',
+ SUIT_FILED: '00',
+ CREDIT_FACILITY_STATUS: '13',
+ ASSEST_CLASSIFICATION: '01',
+ VALUE_OF_COLLATERAL: '11000000',
+ TYPE_OF_COLLATERAL: '01',
+ RATE_OF_INTEREST: '8.10',
+ REPAYMENT_TENURE: '180',
+ EMI_AMOUNT: '0',
+ PAYMENT_FREQUENCY: '09',
+ OCCU_CODE: '01',
+ INCOME: '1800000',
+ NET_GROSS_INCOME_ID: '02',
+ MONTHLY_ANNUAL_INCOME_ID: '02',
+ CKYC_ID: '60040301229988'
+ })
+ ];
+
+ /**
+ * Builds a fully-populated ConsumerRecord by spreading the supplied partial fields
+ * onto an empty 72-column template. Fields not provided default to '' (empty string in the file).
+ */
+ private makeConsumer(partial: Partial): ConsumerRecord {
+ const empty: ConsumerRecord = {
+ CONSUMER_NAME: '',
+ DATE_OF_BIRTH: '',
+ GENDER: '',
+ INCOME_TAX_ID: '',
+ PASSPORT: '',
+ PASSPORT_ISSUE_DATE: '',
+ PASSPORT_EXPIRY_DATE: '',
+ VOTER_ID: '',
+ DRIVING_LICENSE_NO: '',
+ DRIVING_LICENSE_ISSUE_DATE: '',
+ DRIVING_LICENSE_EXPIRY_DATE: '',
+ RATION_CARD_NO: '',
+ UNIVERSAL_ID: '',
+ ADDITIONAL_ID_1: '',
+ ADDITIONAL_ID_2: '',
+ TELEPHONE_NO_MOBILE: '',
+ TELEPHONE_NO_RESIDENCE: '',
+ TELEPHONE_NO_OFFICE: '',
+ EXTENSION_OFFICE: '',
+ TELEPHONE_NO_OTHER: '',
+ EXTENSION_OTHER: '',
+ EMAIL_ID_1: '',
+ EMAIL_ID_2: '',
+ ADDRESS_1: '',
+ STATE_CODE_1: '',
+ PINCODE_1: '',
+ ADDRESS_CATEGORY_1: '',
+ RESIDENCE_CODE_1: '',
+ ADDRESS_2: '',
+ STATE_CODE_2: '',
+ PINCODE_2: '',
+ ADDRESS_CATEGORY_2: '',
+ RESIDENCE_CODE_2: '',
+ MEMBER_CODE: 'COBBNS1059',
+ MEMBER_SHORT_NAME: 'BNSBM',
+ AC_NUMBER: '',
+ AC_TYPE: '',
+ OWNERSHIP_IND: '',
+ OPENED_DATE: '',
+ LASTPAYMENTDATE: '',
+ DATE_CLOSED: '',
+ DATE_REPORTED: '15012026',
+ SANCTIONED_AMT: '',
+ CURRENT_BALANCE: '',
+ AMOUNT_OVERDUE: '',
+ NO_OF_DAYS_PAST_DUE: '',
+ OLD_MEMBER_CODE: '',
+ OLD_MEMBER_NAME: '',
+ OLD_ACC_NO: '',
+ OLD_ACC_TYPE: '',
+ OLD_OWNERSHIP_INDICATOR: '',
+ SUIT_FILED: '',
+ CREDIT_FACILITY_STATUS: '',
+ ASSEST_CLASSIFICATION: '',
+ VALUE_OF_COLLATERAL: '',
+ TYPE_OF_COLLATERAL: '',
+ CREDIT_LIMIT: '',
+ CASH_LIMIT: '',
+ RATE_OF_INTEREST: '',
+ REPAYMENT_TENURE: '',
+ EMI_AMOUNT: '',
+ WRITTEN_OFF_AMOUNT_TOTAL: '',
+ WRITTEN_OFF_PRINCIPAL_AMOUNT: '',
+ SETTLEMENT_AMT: '',
+ PAYMENT_FREQUENCY: '',
+ ACTUAL_PAY_AMT: '',
+ OCCU_CODE: '',
+ INCOME: '',
+ NET_GROSS_INCOME_ID: '',
+ MONTHLY_ANNUAL_INCOME_ID: '',
+ CKYC_ID: '',
+ NREGA_CARD: ''
+ };
+ return { ...empty, ...partial };
+ }
+
+ /** Tail of header record — DT1..DT64 member-data fields (most empty in real submissions) */
+ private dtFields(): string[] {
+ const dt = new Array(64).fill('');
+ dt[0] = 'BRANCH-HO'; // DT1 — branch code
+ dt[1] = 'CYC-' + this.today.toISOString().slice(0, 7).replace('-', ''); // DT2 — internal cycle id
+ dt[2] = 'BATCH-001'; // DT3
+ return dt;
+ }
+
+ constructor() {
+ const month = this.today.toLocaleString('en-IN', { month: 'long', year: 'numeric' });
+ this.reportingPeriod = month;
+ }
+
+ get current(): CicReport {
+ return this.reports.find((r) => r.key === this.activeTab) || this.reports[0];
+ }
+
+ dqrBadge(pct: number): 'good' | 'moderate' | 'low' {
+ if (pct >= 97) return 'good';
+ if (pct >= 95) return 'moderate';
+ return 'low';
+ }
+
+ statusClass(status: string): string {
+ if (status === 'Submitted') return 'st-good';
+ if (status === 'In Progress') return 'st-mid';
+ return 'st-low';
+ }
+
+ /** Header column names for the HD line — matches real CIBIL Consumer file (8 fixed + DT1..DT64 = 72) */
+ private readonly HD_COLS = [
+ 'REPORTING_MEMBER_ID',
+ 'SHORT_NAME',
+ 'CYCLE_IDENTIFICATION',
+ 'DATE_REPORTED',
+ 'REPORTING_PASSWORD',
+ 'AUTHENTICATION_METHOD',
+ 'FUTURE_USE',
+ 'MEMBER_DATA',
+ ...Array.from({ length: 64 }, (_, i) => `DT${i + 1}`)
+ ];
+
+ /**
+ * Consumer + tradeline column names — 72-column CIBIL TUDF Consumer schema.
+ * Order is positional and must not change. Note: ASSEST_CLASSIFICATION typo is from the spec, not a bug.
+ */
+ private readonly CONSUMER_COLS: (keyof ConsumerRecord)[] = [
+ 'CONSUMER_NAME',
+ 'DATE_OF_BIRTH',
+ 'GENDER',
+ 'INCOME_TAX_ID',
+ 'PASSPORT',
+ 'PASSPORT_ISSUE_DATE',
+ 'PASSPORT_EXPIRY_DATE',
+ 'VOTER_ID',
+ 'DRIVING_LICENSE_NO',
+ 'DRIVING_LICENSE_ISSUE_DATE',
+ 'DRIVING_LICENSE_EXPIRY_DATE',
+ 'RATION_CARD_NO',
+ 'UNIVERSAL_ID',
+ 'ADDITIONAL_ID_1',
+ 'ADDITIONAL_ID_2',
+ 'TELEPHONE_NO_MOBILE',
+ 'TELEPHONE_NO_RESIDENCE',
+ 'TELEPHONE_NO_OFFICE',
+ 'EXTENSION_OFFICE',
+ 'TELEPHONE_NO_OTHER',
+ 'EXTENSION_OTHER',
+ 'EMAIL_ID_1',
+ 'EMAIL_ID_2',
+ 'ADDRESS_1',
+ 'STATE_CODE_1',
+ 'PINCODE_1',
+ 'ADDRESS_CATEGORY_1',
+ 'RESIDENCE_CODE_1',
+ 'ADDRESS_2',
+ 'STATE_CODE_2',
+ 'PINCODE_2',
+ 'ADDRESS_CATEGORY_2',
+ 'RESIDENCE_CODE_2',
+ 'MEMBER_CODE',
+ 'MEMBER_SHORT_NAME',
+ 'AC_NUMBER',
+ 'AC_TYPE',
+ 'OWNERSHIP_IND',
+ 'OPENED_DATE',
+ 'LASTPAYMENTDATE',
+ 'DATE_CLOSED',
+ 'DATE_REPORTED',
+ 'SANCTIONED_AMT',
+ 'CURRENT_BALANCE',
+ 'AMOUNT_OVERDUE',
+ 'NO_OF_DAYS_PAST_DUE',
+ 'OLD_MEMBER_CODE',
+ 'OLD_MEMBER_NAME',
+ 'OLD_ACC_NO',
+ 'OLD_ACC_TYPE',
+ 'OLD_OWNERSHIP_INDICATOR',
+ 'SUIT_FILED',
+ 'CREDIT_FACILITY_STATUS',
+ 'ASSEST_CLASSIFICATION',
+ 'VALUE_OF_COLLATERAL',
+ 'TYPE_OF_COLLATERAL',
+ 'CREDIT_LIMIT',
+ 'CASH_LIMIT',
+ 'RATE_OF_INTEREST',
+ 'REPAYMENT_TENURE',
+ 'EMI_AMOUNT',
+ 'WRITTEN_OFF_AMOUNT_TOTAL',
+ 'WRITTEN_OFF_PRINCIPAL_AMOUNT',
+ 'SETTLEMENT_AMT',
+ 'PAYMENT_FREQUENCY',
+ 'ACTUAL_PAY_AMT',
+ 'OCCU_CODE',
+ 'INCOME',
+ 'NET_GROSS_INCOME_ID',
+ 'MONTHLY_ANNUAL_INCOME_ID',
+ 'CKYC_ID',
+ 'NREGA_CARD'
+ ];
+
+ /**
+ * Builds the 72-column CIBIL TUDF Consumer file as a 2-D row array.
+ * Layout matches the real .xls (e.g. COBBNS1059-Consumer-DDMMYYYY-HHMMSS.xls):
+ * Rows 0-3: blank padding
+ * Row 4: HD column-name header (72 cols)
+ * Row 5: HD values
+ * Rows 6-8: blank
+ * Row 9: Consumer column-name header (72 cols)
+ * Rows 10+: Consumer/tradeline data (one row per loan account)
+ */
+ private buildTudfRows(report: CicReport): string[][] {
+ const dd = String(this.today.getDate()).padStart(2, '0');
+ const mm = String(this.today.getMonth() + 1).padStart(2, '0');
+ const yyyy = String(this.today.getFullYear());
+ const dateReported = `${dd}${mm}${yyyy}`; // DDMMYYYY (CIBIL format)
+
+ const rows: string[][] = [];
+ // 4 blank padding rows
+ rows.push([], [], [], []);
+ // HD column header
+ rows.push([...this.HD_COLS]);
+ // HD values
+ rows.push([
+ 'COBBNS1059', // REPORTING_MEMBER_ID
+ 'BNSBM', // SHORT_NAME
+ 'CO', // CYCLE_IDENTIFICATION (CO = Consumer)
+ dateReported, // DATE_REPORTED (DDMMYYYY)
+ 'N', // REPORTING_PASSWORD (N = none)
+ 'A', // AUTHENTICATION_METHOD (A = standard)
+ '', // FUTURE_USE
+ this.consumers.length.toString(), // MEMBER_DATA (record count)
+ ...this.dtFields() // DT1 - DT64
+ ]);
+ // 3 blank separator rows
+ rows.push([], [], []);
+ // Consumer column header
+ rows.push([...this.CONSUMER_COLS]);
+ // Consumer data rows
+ for (const c of this.consumers) {
+ rows.push(this.CONSUMER_COLS.map((k) => (c[k] || '').replace(/\t/g, ' ')));
+ }
+ return rows;
+ }
+
+ /**
+ * Tab-delimited .xls of the active bureau, in the real CIBIL Consumer schema
+ * (matches COBBNS1059-Consumer-DDMMYYYY.xls layout: 4 blank rows, HD header at row 5,
+ * HD values at row 6, 3 blanks, consumer header at row 10, then data).
+ */
+ downloadExcel(): void {
+ const r = this.current;
+ const rows = this.buildTudfRows(r);
+ const tsv = rows.map((row) => row.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
+ this.saveBlob(tsv, `${r.key.toUpperCase()}-Consumer-${this.dateStamp()}.xls`, 'application/vnd.ms-excel');
+ }
+
+ /** Concatenated bureau exports — one section per bureau, separated by a blank row. */
+ downloadAllExcel(): void {
+ const sections = this.reports.map((r) =>
+ this.buildTudfRows(r)
+ .map((row) => row.map((v) => (v || '').replace(/\t/g, ' ')).join('\t'))
+ .join('\n')
+ );
+ const tsv = sections.join('\n\n');
+ this.saveBlob(tsv, `All-Bureaus-Consumer-${this.dateStamp()}.xls`, 'application/vnd.ms-excel');
+ }
+
+ private dateStamp(): string {
+ const dd = String(this.today.getDate()).padStart(2, '0');
+ const mm = String(this.today.getMonth() + 1).padStart(2, '0');
+ const yyyy = String(this.today.getFullYear());
+ const hh = String(this.today.getHours()).padStart(2, '0');
+ const mi = String(this.today.getMinutes()).padStart(2, '0');
+ const ss = String(this.today.getSeconds()).padStart(2, '0');
+ return `${dd}${mm}${yyyy}-${hh}${mi}${ss}`;
+ }
+
+ private saveBlob(content: string, filename: string, mime: string): void {
+ const blob = new Blob([content], { type: `${mime};charset=utf-8` });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+
+ setTab(tab: 'cibil' | 'experian' | 'equifax' | 'crif'): void {
+ this.activeTab = tab;
+ }
+
+ close(): void {
+ this.dialogRef.close();
+ }
+}
diff --git a/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.html b/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.html
new file mode 100644
index 000000000..43842a57c
--- /dev/null
+++ b/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.html
@@ -0,0 +1,260 @@
+
+
+
+
+
+
+
+
+
CIMS / XBRL Returns — {{ currentForm.name }}
+
+
+
+
+
+
+ {{ validation.populated }}/{{ validation.total }} populated
+
+ ✕
+
+
+
+
+
+ Active Form:
+
+ {{ forms[code].name }}
+
+ {{ currentForm.cimsCode }}
+ {{ currentForm.periodicity }}
+
+ Next due: {{ currentForm.nextDue }}
+
+
+
+
+
+
+ Filing Information
+
+
+ Form Data & Facts
+
+
+ XBRL XML Preview
+
+
+
+
+
+
+
+
+
Reporting Entity
+
+
+
+
Bank Name
+
{{ bank.name }}
+
+
+
Branch
+
{{ bank.branch }}
+
+
+
BSR Code
+
{{ bank.bsrCode }}
+
+
+
IFSC
+
{{ bank.ifsc }}
+
+
+
Legal Entity ID
+
{{ bank.legalEntityId }}
+
+
+
+
+
+
+
+
Return Particulars
+
+
+
+
CIMS Form Code
+
{{ currentForm.cimsCode }}
+
+
+
Taxonomy Version
+
{{ currentForm.taxonomy }}
+
+
+
Periodicity
+
{{ currentForm.periodicity }}
+
+
+
Reporting Period
+
{{ bank.quarter }}
+
+
+
As On Date
+
{{ bank.asOnDate }}
+
+
+
Last Filed
+
{{ currentForm.lastFiled }}
+
+
+
Next Due
+
+ {{ currentForm.nextDue }}
+
+
+
+
CIMS Portal
+
{{ bank.cimsPortal }}
+
+
+
CIMS User ID
+
{{ bank.cimsUserId }}
+
+
+
+
+
+
+
+
Principal Officer
+
+
+
+
Name
+
{{ bank.principalOfficer }}
+
+
+
Designation
+
{{ bank.poDesignation }}
+
+
+
Email
+
{{ bank.poEmail }}
+
+
+
Mobile
+
{{ bank.poMobile }}
+
+
+
+
+
+
+
+
+
+
{{ sec }}
+
+
+
+
+
+ XBRL Element
+ Label
+ Value
+ Unit
+
+
+
+
+ {{ f.ref }}
+
+
+ {{ f.value }}
+ %
+
+
+ {{ f.unit }}
+
+
+
+
+
+
+
+
+
+
+
+
+
XBRL Instance Document Preview
+
+
+ Copy
+
+
+ Download .xml
+
+
+ Download Excel
+
+
+
+
+
+
+
+
+ XBRL Document Valid — ready to upload
+ {{ validation.empty }} field(s) need attention
+
+
+ {{ validation.populated }} of {{ validation.total }} elements populated · Taxonomy
+ {{ currentForm.taxonomy }}
+
+
+
+
+
{{ generateXbrlXml() }}
+
+
+
+
+
+
+
diff --git a/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.scss b/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.scss
new file mode 100644
index 000000000..d6d834e6c
--- /dev/null
+++ b/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.scss
@@ -0,0 +1,514 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --cims-good: #15803d;
+ --cims-good-l: #dcfce7;
+ --cims-mid: #d97706;
+ --cims-mid-l: #fef3c7;
+ --cims-low: #c0392b;
+ --cims-low-l: #fdecea;
+ --cims-cyan: #0891b2;
+ --cims-cyan-l: #cffafe;
+ --cims-teal: #0d9488;
+ --cims-teal-l: #ccfbf1;
+ --cims-g50: #f8fafc;
+ --cims-g100: #f1f5f9;
+ --cims-g200: #e2e8f0;
+ --cims-g300: #cbd5e1;
+ --cims-g500: #64748b;
+ --cims-g700: #334155;
+ --cims-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ color: var(--cims-g900);
+}
+
+.cims-dlg {
+ background: #fff;
+ border-radius: 14px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ max-height: 92vh;
+}
+
+/* HEADER */
+.cims-head {
+ padding: 18px 22px;
+ border-bottom: 1px solid var(--cims-g200);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-shrink: 0;
+ background: linear-gradient(135deg, #ecfeff 0%, #fff 100%);
+}
+
+.cims-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ flex: 1;
+}
+
+.cims-head-ic {
+ width: 44px;
+ height: 44px;
+ border-radius: 11px;
+ background: var(--cims-cyan-l);
+ color: var(--cims-cyan);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.cims-title {
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+ color: var(--cims-g900);
+}
+
+.cims-sub {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--cims-g500);
+}
+
+.cims-head-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.cims-status {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 6px 12px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 600;
+
+ &.valid {
+ background: var(--cims-good-l);
+ color: var(--cims-good);
+ }
+
+ &.warning {
+ background: var(--cims-mid-l);
+ color: var(--cims-mid);
+ }
+}
+
+.cims-close {
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ border: 1px solid var(--cims-g200);
+ background: #fff;
+ font-size: 14px;
+ color: var(--cims-g500);
+ cursor: pointer;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--cims-g100);
+ color: var(--cims-g900);
+ }
+}
+
+/* FORM SELECTOR BAR */
+.cims-form-bar {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 22px;
+ background: var(--cims-g50);
+ border-bottom: 1px solid var(--cims-g200);
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.cims-form-bar-l {
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--cims-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.cims-form-pill {
+ padding: 6px 14px;
+ border-radius: 999px;
+ border: 1px solid var(--cims-g200);
+ background: #fff;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--cims-g700);
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--cims-cyan-l);
+ border-color: var(--cims-cyan);
+ color: var(--cims-cyan);
+ }
+
+ &.active {
+ background: var(--cims-cyan);
+ border-color: var(--cims-cyan);
+ color: #fff;
+ box-shadow: 0 2px 6px rgb(8 145 178 / 25%);
+ }
+}
+
+.cims-form-meta {
+ display: inline-flex;
+ align-items: center;
+ font-size: 11px;
+ color: var(--cims-g500);
+ padding: 4px 10px;
+ background: #fff;
+ border: 1px solid var(--cims-g200);
+ border-radius: 6px;
+
+ fa-icon {
+ color: var(--cims-cyan);
+ margin-right: 2px;
+ }
+
+ strong {
+ color: var(--cims-g900);
+ margin-left: 3px;
+ }
+}
+
+/* TABS */
+.cims-tabs {
+ display: flex;
+ background: #fff;
+ border-bottom: 1px solid var(--cims-g200);
+ padding: 0 22px;
+ overflow-x: auto;
+ flex-shrink: 0;
+}
+
+.cims-tab {
+ display: flex;
+ align-items: center;
+ padding: 12px 18px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--cims-g500);
+ border-bottom: 2px solid transparent;
+ transition: all 0.15s;
+ white-space: nowrap;
+
+ &:hover {
+ color: var(--cims-g700);
+ background: var(--cims-g50);
+ }
+
+ &.active {
+ background: #fff;
+ color: var(--cims-cyan);
+ border-bottom-color: var(--cims-cyan);
+ }
+}
+
+/* BODY */
+.cims-body {
+ padding: 18px 22px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ overflow: auto;
+ flex: 1;
+ background: var(--cims-g50);
+}
+
+.cims-section {
+ background: #fff;
+ border: 1px solid var(--cims-g200);
+ border-radius: 12px;
+ padding: 16px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.cims-sec-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 14px;
+ flex-wrap: wrap;
+}
+
+.cims-sec-t {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--cims-g700);
+}
+
+.cims-sec-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.cims-btn-xls {
+ font-size: 12px !important;
+ height: 34px !important;
+ color: #fff !important;
+ background: var(--cims-cyan) !important;
+}
+
+.cims-btn-excel {
+ background: #16a34a !important;
+}
+
+.cims-btn-ghost {
+ font-size: 12px !important;
+ height: 34px !important;
+ color: var(--cims-g700) !important;
+ background: #fff !important;
+ border: 1px solid var(--cims-g200) !important;
+}
+
+/* INFO GRID */
+.cims-info-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 12px 24px;
+}
+
+.cims-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.cims-info-l {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--cims-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.cims-info-v {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--cims-g900);
+}
+
+.cims-desc {
+ margin-top: 12px;
+ padding: 10px 14px;
+ background: var(--cims-g50);
+ border-left: 3px solid var(--cims-cyan);
+ border-radius: 4px;
+ font-size: 12px;
+ color: var(--cims-g700);
+ line-height: 1.5;
+}
+
+/* TABLE */
+.cims-tbl-wrap {
+ border: 1px solid var(--cims-g200);
+ border-radius: 8px;
+ overflow: auto;
+}
+
+.cims-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 10px 14px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--cims-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ background: var(--cims-g50);
+ border-bottom: 1px solid var(--cims-g200);
+ white-space: nowrap;
+ }
+
+ td {
+ padding: 10px 14px;
+ border-bottom: 1px solid var(--cims-g100);
+ font-size: 13px;
+ color: var(--cims-g700);
+ vertical-align: middle;
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--cims-g50);
+ }
+}
+
+.cell-ref {
+ color: var(--cims-cyan);
+ font-weight: 600;
+}
+
+.unit-pill {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 6px;
+ background: var(--cims-teal-l);
+ color: var(--cims-teal);
+ font-family: 'DM Mono', monospace;
+ font-size: 10px;
+ font-weight: 600;
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 12px;
+}
+
+.muted {
+ color: var(--cims-g500);
+ font-weight: 400;
+}
+
+/* VALIDATION BANNER */
+.cims-validation {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 16px;
+ border-radius: 8px;
+ margin-bottom: 12px;
+
+ fa-icon {
+ font-size: 20px;
+ }
+
+ &.valid {
+ background: var(--cims-good-l);
+ color: var(--cims-good);
+ }
+
+ &.warning {
+ background: var(--cims-mid-l);
+ color: var(--cims-mid);
+ }
+}
+
+.cims-val-t {
+ font-size: 13px;
+ font-weight: 700;
+}
+
+.cims-val-d {
+ font-size: 11px;
+ margin-top: 2px;
+ opacity: 0.85;
+}
+
+/* XBRL PREVIEW */
+.xbrl-preview {
+ background: #0f172a;
+ color: #e2e8f0;
+ padding: 16px;
+ border-radius: 8px;
+ font-family: 'DM Mono', 'Cascadia Code', monospace;
+ font-size: 11.5px;
+ line-height: 1.6;
+ overflow: auto;
+ max-height: 480px;
+ margin: 0;
+ white-space: pre;
+ border: 1px solid var(--cims-g200);
+
+ code {
+ font-family: inherit;
+ background: transparent;
+ color: inherit;
+ padding: 0;
+ }
+}
+
+/* FOOTER */
+.cims-foot {
+ padding: 12px 22px;
+ border-top: 1px solid var(--cims-g200);
+ background: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.cims-foot-l {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: var(--cims-g500);
+
+ fa-icon {
+ color: var(--cims-cyan);
+ }
+}
+
+.cims-foot-actions {
+ display: flex;
+ gap: 8px;
+}
+
+/* RESPONSIVE */
+@media (width <= 1100px) {
+ .cims-info-grid {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+@media (width <= 720px) {
+ .cims-info-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .cims-head,
+ .cims-form-bar,
+ .cims-foot {
+ flex-wrap: wrap;
+ }
+}
diff --git a/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.ts b/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.ts
new file mode 100644
index 000000000..fbf07020c
--- /dev/null
+++ b/src/app/home/dashboard/cims-xbrl-dialog/cims-xbrl-dialog.component.ts
@@ -0,0 +1,840 @@
+/**
+ * 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/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { CommonModule, DecimalPipe } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+
+export type CimsFormCode = 'OSS-2' | 'PSA' | 'RAL' | 'RCP-UCB';
+
+export interface CimsXbrlDialogData {
+ initialForm?: CimsFormCode;
+}
+
+interface CimsField {
+ ref: string;
+ label: string;
+ section: string;
+ value: number | string;
+ unit?: 'INR-Lakh' | 'INR-Cr' | 'Pure' | 'Percent' | 'Number';
+ type?: 'monetary' | 'numeric' | 'string' | 'percent';
+}
+
+interface CimsForm {
+ code: CimsFormCode;
+ name: string;
+ fullName: string;
+ description: string;
+ cimsCode: string;
+ taxonomy: string;
+ periodicity: 'Daily' | 'Monthly' | 'Quarterly' | 'Half-Yearly' | 'Annual';
+ lastFiled: string;
+ nextDue: string;
+ fields: CimsField[];
+}
+
+@Component({
+ selector: 'mifosx-cims-xbrl-dialog',
+ standalone: true,
+ templateUrl: './cims-xbrl-dialog.component.html',
+ styleUrls: ['./cims-xbrl-dialog.component.scss'],
+ imports: [
+ CommonModule,
+ DecimalPipe,
+ FormsModule,
+ MatDialogModule,
+ MatButtonModule,
+ MatTooltipModule,
+ FaIconComponent
+ ]
+})
+export class CimsXbrlDialogComponent {
+ private dialogRef = inject(MatDialogRef);
+
+ activeForm: CimsFormCode;
+ activeTab: 'filing' | 'data' | 'preview' = 'filing';
+
+ bank = {
+ name: 'Bharat Urban Bank Ltd',
+ branch: 'Head Office',
+ bsrCode: 'BUBL0000123',
+ ifsc: 'BUBL0000123',
+ legalEntityId: 'BUBL-LEI-2025',
+ pan: 'AAACB1234L',
+ asOnDate: '31-03-2026',
+ quarter: 'Q4 FY 2025-26',
+ fiscalYear: '2025-26',
+ principalOfficer: 'Karthik Kamath',
+ poDesignation: 'Chief Compliance Officer',
+ poEmail: 'compliance@bharaturbanbank.in',
+ poMobile: '+91-9988002211',
+ cimsUserId: 'BUBL_CIMS_001',
+ cimsPortal: 'https://cims.rbi.org.in/'
+ };
+
+ forms: Record = {
+ 'OSS-2': {
+ code: 'OSS-2',
+ name: 'OSS-2',
+ fullName: 'Off-site Surveillance — Statement of Asset Quality & Capital Adequacy',
+ description: 'Quarterly return on asset classification, NPA, capital adequacy and profitability indicators.',
+ cimsCode: 'CIMS_OSS_002',
+ taxonomy: 'rbi-cims-oss-2024-04-01',
+ periodicity: 'Quarterly',
+ lastFiled: '15-01-2026',
+ nextDue: '15-04-2026',
+ fields: [
+ {
+ ref: 'oss2:CapAdq.CRAR',
+ section: 'Capital Adequacy',
+ label: 'CRAR (Total)',
+ value: 14.82,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:CapAdq.Tier1',
+ section: 'Capital Adequacy',
+ label: 'Tier-I CRAR',
+ value: 12.18,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:CapAdq.Tier2',
+ section: 'Capital Adequacy',
+ label: 'Tier-II CRAR',
+ value: 2.64,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Asset.Total',
+ section: 'Asset Classification',
+ label: 'Total Advances (₹ Lakh)',
+ value: 416265,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'oss2:Asset.Standard',
+ section: 'Asset Classification',
+ label: 'Standard Assets (₹ Lakh)',
+ value: 401820,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'oss2:Asset.SubStd',
+ section: 'Asset Classification',
+ label: 'Sub-Standard (₹ Lakh)',
+ value: 6220,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'oss2:Asset.Doubtful',
+ section: 'Asset Classification',
+ label: 'Doubtful (₹ Lakh)',
+ value: 5840,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'oss2:Asset.Loss',
+ section: 'Asset Classification',
+ label: 'Loss (₹ Lakh)',
+ value: 2385,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'oss2:Asset.GNPA',
+ section: 'NPA Position',
+ label: 'Gross NPA (₹ Lakh)',
+ value: 14445,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'oss2:Asset.NNPA',
+ section: 'NPA Position',
+ label: 'Net NPA (₹ Lakh)',
+ value: 5212,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'oss2:Asset.GNPAPct',
+ section: 'NPA Position',
+ label: 'GNPA %',
+ value: 3.47,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Asset.NNPAPct',
+ section: 'NPA Position',
+ label: 'NNPA %',
+ value: 1.27,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Asset.PCR',
+ section: 'NPA Position',
+ label: 'Provision Coverage Ratio',
+ value: 64.13,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Profit.NIM',
+ section: 'Profitability',
+ label: 'Net Interest Margin',
+ value: 3.21,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Profit.ROA',
+ section: 'Profitability',
+ label: 'Return on Assets',
+ value: 0.92,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Profit.ROE',
+ section: 'Profitability',
+ label: 'Return on Equity',
+ value: 11.48,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Liq.CRR',
+ section: 'Liquidity',
+ label: 'CRR Maintained',
+ value: 4.52,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'oss2:Liq.SLR',
+ section: 'Liquidity',
+ label: 'SLR Maintained',
+ value: 19.24,
+ unit: 'Percent',
+ type: 'percent'
+ }
+ ]
+ },
+ PSA: {
+ code: 'PSA',
+ name: 'PSA',
+ fullName: 'Priority Sector Advances — Annual Achievement Statement',
+ description: 'Annual return on priority sector lending, sub-targets and weaker section coverage.',
+ cimsCode: 'CIMS_PSA_001',
+ taxonomy: 'rbi-cims-psa-2024-04-01',
+ periodicity: 'Annual',
+ lastFiled: '30-04-2025',
+ nextDue: '30-04-2026',
+ fields: [
+ {
+ ref: 'psa:Reference.ANBC',
+ section: 'Reference',
+ label: 'Adjusted Net Bank Credit (₹ Lakh)',
+ value: 600000,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:Reference.OBE',
+ section: 'Reference',
+ label: 'Credit Equivalent of OBE (₹ Lakh)',
+ value: 25000,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.Total',
+ section: 'PSL Achievement',
+ label: 'Total PSL Outstanding (₹ Lakh)',
+ value: 241700,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.TotalPct',
+ section: 'PSL Achievement',
+ label: 'Total PSL % of ANBC',
+ value: 40.28,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'psa:PSL.Agri',
+ section: 'Sub-targets',
+ label: 'Agriculture (₹ Lakh)',
+ value: 124000,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.AgriPct',
+ section: 'Sub-targets',
+ label: 'Agriculture % of ANBC',
+ value: 20.67,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'psa:PSL.SMF',
+ section: 'Sub-targets',
+ label: 'Small & Marginal Farmers (₹ Lakh)',
+ value: 72000,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.SMFPct',
+ section: 'Sub-targets',
+ label: 'SMF % of ANBC',
+ value: 12.0,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'psa:PSL.MicroEnt',
+ section: 'Sub-targets',
+ label: 'Micro Enterprises (₹ Lakh)',
+ value: 51000,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.MicroPct',
+ section: 'Sub-targets',
+ label: 'Micro Enterprises % of ANBC',
+ value: 8.5,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'psa:PSL.Weaker',
+ section: 'Sub-targets',
+ label: 'Weaker Sections (₹ Lakh)',
+ value: 85500,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.WeakerPct',
+ section: 'Sub-targets',
+ label: 'Weaker Sections % of ANBC',
+ value: 14.25,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'psa:PSL.Housing',
+ section: 'Other Categories',
+ label: 'Housing (₹ Lakh)',
+ value: 9200,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.Education',
+ section: 'Other Categories',
+ label: 'Education (₹ Lakh)',
+ value: 4200,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.Export',
+ section: 'Other Categories',
+ label: 'Export Credit (₹ Lakh)',
+ value: 3800,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'psa:PSL.RenEnergy',
+ section: 'Other Categories',
+ label: 'Renewable Energy (₹ Lakh)',
+ value: 2400,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ }
+ ]
+ },
+ RAL: {
+ code: 'RAL',
+ name: 'RAL',
+ fullName: 'Return on Assets & Liabilities (Half-Yearly)',
+ description: 'Half-yearly statement of bank-wide assets and liabilities composition for cooperative banks.',
+ cimsCode: 'CIMS_RAL_004',
+ taxonomy: 'rbi-cims-ral-2024-04-01',
+ periodicity: 'Half-Yearly',
+ lastFiled: '30-10-2025',
+ nextDue: '30-04-2026',
+ fields: [
+ {
+ ref: 'ral:Liab.Capital',
+ section: 'Liabilities',
+ label: 'Capital (₹ Lakh)',
+ value: 22000,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Liab.Reserves',
+ section: 'Liabilities',
+ label: 'Reserves & Surplus (₹ Lakh)',
+ value: 32400,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Liab.DepDemand',
+ section: 'Liabilities',
+ label: 'Demand Deposits (₹ Lakh)',
+ value: 84200,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Liab.DepSavings',
+ section: 'Liabilities',
+ label: 'Savings Deposits (₹ Lakh)',
+ value: 218600,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Liab.DepTerm',
+ section: 'Liabilities',
+ label: 'Term Deposits (₹ Lakh)',
+ value: 462800,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Liab.Borrow',
+ section: 'Liabilities',
+ label: 'Borrowings (₹ Lakh)',
+ value: 24180,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Liab.Other',
+ section: 'Liabilities',
+ label: 'Other Liabilities & Provisions (₹ Lakh)',
+ value: 18420,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.Cash',
+ section: 'Assets',
+ label: 'Cash on Hand (₹ Lakh)',
+ value: 8420,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.RBI',
+ section: 'Assets',
+ label: 'Balances with RBI (₹ Lakh)',
+ value: 38400,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.Banks',
+ section: 'Assets',
+ label: 'Balances with Banks (₹ Lakh)',
+ value: 24800,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.Money',
+ section: 'Assets',
+ label: 'Money at Call & Short Notice (₹ Lakh)',
+ value: 12400,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.Invest',
+ section: 'Assets',
+ label: 'Investments (₹ Lakh)',
+ value: 184600,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.Adv',
+ section: 'Assets',
+ label: 'Advances (₹ Lakh)',
+ value: 416265,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.Fixed',
+ section: 'Assets',
+ label: 'Fixed Assets (₹ Lakh)',
+ value: 14200,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:Asset.Other',
+ section: 'Assets',
+ label: 'Other Assets (₹ Lakh)',
+ value: 12480,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:OBE.Guar',
+ section: 'Off-Balance Sheet',
+ label: 'Bank Guarantees (₹ Lakh)',
+ value: 18420,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'ral:OBE.LC',
+ section: 'Off-Balance Sheet',
+ label: 'Letters of Credit (₹ Lakh)',
+ value: 6580,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ }
+ ]
+ },
+ 'RCP-UCB': {
+ code: 'RCP-UCB',
+ name: 'RCP (UCB)',
+ fullName: 'Return on Cash Position — Urban Cooperative Banks',
+ description: 'Fortnightly statement of CRR/SLR maintenance, NDTL composition and reserve position.',
+ cimsCode: 'CIMS_RCP_UCB_007',
+ taxonomy: 'rbi-cims-rcp-ucb-2024-04-01',
+ periodicity: 'Monthly',
+ lastFiled: '14-04-2026',
+ nextDue: '28-04-2026',
+ fields: [
+ {
+ ref: 'rcp:Liab.DemandDep',
+ section: 'NDTL Composition',
+ label: 'Demand Liabilities (₹ Lakh)',
+ value: 84200,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:Liab.TimeDep',
+ section: 'NDTL Composition',
+ label: 'Time Liabilities (₹ Lakh)',
+ value: 681400,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:Liab.NDTL',
+ section: 'NDTL Composition',
+ label: 'Net Demand & Time Liabilities (NDTL)',
+ value: 740000,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:CRR.Required',
+ section: 'CRR Maintenance',
+ label: 'CRR Required (4.5%)',
+ value: 33300,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:CRR.Maintained',
+ section: 'CRR Maintenance',
+ label: 'CRR Maintained (avg)',
+ value: 33448,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:CRR.MaintainedPct',
+ section: 'CRR Maintenance',
+ label: 'CRR % Maintained',
+ value: 4.52,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'rcp:CRR.Surplus',
+ section: 'CRR Maintenance',
+ label: 'CRR Surplus / Shortfall (₹ Lakh)',
+ value: 148,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:SLR.Required',
+ section: 'SLR Maintenance',
+ label: 'SLR Required (18%)',
+ value: 133200,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:SLR.Maintained',
+ section: 'SLR Maintenance',
+ label: 'SLR Maintained (avg)',
+ value: 142376,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:SLR.MaintainedPct',
+ section: 'SLR Maintenance',
+ label: 'SLR % Maintained',
+ value: 19.24,
+ unit: 'Percent',
+ type: 'percent'
+ },
+ {
+ ref: 'rcp:SLR.Surplus',
+ section: 'SLR Maintenance',
+ label: 'SLR Surplus / Shortfall (₹ Lakh)',
+ value: 9176,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:Cash.OnHand',
+ section: 'Cash Position',
+ label: 'Cash on Hand (₹ Lakh)',
+ value: 8420,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:Cash.AtRBI',
+ section: 'Cash Position',
+ label: 'Cash at RBI (₹ Lakh)',
+ value: 33448,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ },
+ {
+ ref: 'rcp:Cash.AtBanks',
+ section: 'Cash Position',
+ label: 'Cash at other Banks (₹ Lakh)',
+ value: 24800,
+ unit: 'INR-Lakh',
+ type: 'monetary'
+ }
+ ]
+ }
+ };
+
+ formCodes: CimsFormCode[] = [
+ 'OSS-2',
+ 'PSA',
+ 'RAL',
+ 'RCP-UCB'
+ ];
+
+ data = inject(MAT_DIALOG_DATA, { optional: true });
+
+ constructor() {
+ this.activeForm = this.data?.initialForm ?? 'OSS-2';
+ }
+
+ get currentForm(): CimsForm {
+ return this.forms[this.activeForm];
+ }
+
+ get sections(): string[] {
+ const s: string[] = [];
+ for (const f of this.currentForm.fields) {
+ if (!s.includes(f.section)) s.push(f.section);
+ }
+ return s;
+ }
+ fieldsBySection(section: string): CimsField[] {
+ return this.currentForm.fields.filter((f) => f.section === section);
+ }
+
+ setForm(code: CimsFormCode): void {
+ this.activeForm = code;
+ this.activeTab = 'filing';
+ }
+ setTab(tab: 'filing' | 'data' | 'preview'): void {
+ this.activeTab = tab;
+ }
+ close(): void {
+ this.dialogRef.close();
+ }
+
+ /** Format a number for XBRL output (no commas, 2 dp for monetary/percent) */
+ private fmtXbrl(f: CimsField): string {
+ if (typeof f.value === 'string') return f.value;
+ if (f.type === 'monetary' || f.type === 'percent') return f.value.toFixed(2);
+ return f.value.toString();
+ }
+
+ /** Build XBRL XML document for a form (representative; real CIMS taxonomy differs) */
+ generateXbrlXml(): string {
+ const f = this.currentForm;
+ const period = `${this.bank.fiscalYear} (${this.bank.quarter})`;
+ const ctxId = `ctx_${f.code.replace('-', '_')}`;
+ const startDate = '2026-01-01';
+ const endDate = '2026-03-31';
+ const lines: string[] = [];
+ lines.push('');
+ lines.push('`);
+ lines.push(` `);
+ lines.push(` `);
+ lines.push(` `);
+ lines.push(' ');
+ lines.push(` ${this.bank.legalEntityId} `);
+ lines.push(' ');
+ lines.push(' ');
+ lines.push(` ${startDate} `);
+ lines.push(` ${endDate} `);
+ lines.push(' ');
+ lines.push(' ');
+ lines.push(' iso4217:INR ');
+ lines.push(' xbrli:pure ');
+ lines.push(' ');
+ lines.push(` ${this.bank.name} `);
+ lines.push(` ${this.bank.bsrCode} `);
+ lines.push(` ${f.cimsCode} `);
+ lines.push(' ');
+ for (const fld of f.fields) {
+ const unitRef = fld.type === 'monetary' ? 'INR_Lakh' : 'Pure';
+ const decimals = fld.type === 'monetary' || fld.type === 'percent' ? '2' : '0';
+ lines.push(
+ ` <${fld.ref} contextRef="${ctxId}" unitRef="${unitRef}" decimals="${decimals}">${this.fmtXbrl(
+ fld
+ )}${fld.ref}>`
+ );
+ }
+ lines.push(' ');
+ return lines.join('\n');
+ }
+
+ /** Validation summary — counts fields, flags any zero/missing */
+ get validation(): { total: number; populated: number; empty: number; status: 'valid' | 'warning' } {
+ const fields = this.currentForm.fields;
+ const populated = fields.filter((f) => f.value !== '' && f.value !== 0).length;
+ const empty = fields.length - populated;
+ return {
+ total: fields.length,
+ populated,
+ empty,
+ status: empty === 0 ? 'valid' : 'warning'
+ };
+ }
+
+ // -------------------- Exports --------------------
+ exportXbrl(): void {
+ const xml = this.generateXbrlXml();
+ this.saveBlob(xml, `${this.currentForm.cimsCode}-${this.dateTag()}.xml`, 'application/xml');
+ }
+ copyXbrl(): void {
+ const xml = this.generateXbrlXml();
+ if (navigator.clipboard) navigator.clipboard.writeText(xml);
+ }
+ exportExcel(): void {
+ const f = this.currentForm;
+ const rows: string[][] = [];
+ rows.push([
+ this.bank.name,
+ this.bank.branch
+ ]);
+ rows.push([
+ f.fullName,
+ `As On ${this.bank.asOnDate}`
+ ]);
+ rows.push([
+ `CIMS Code: ${f.cimsCode}`,
+ `Taxonomy: ${f.taxonomy}`
+ ]);
+ rows.push([
+ `Periodicity: ${f.periodicity}`,
+ `Quarter: ${this.bank.quarter}`
+ ]);
+ rows.push([
+ `Principal Officer: ${this.bank.principalOfficer}`,
+ `Email: ${this.bank.poEmail}`
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Section',
+ 'Element Ref',
+ 'Label',
+ 'Value',
+ 'Unit'
+ ]);
+ for (const fld of f.fields) {
+ rows.push([
+ fld.section,
+ fld.ref,
+ fld.label.replace(/&/g, '&'),
+ this.fmtXbrl(fld),
+ fld.unit ?? ''
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ 'Certified by',
+ this.bank.principalOfficer + ' — ' + this.bank.poDesignation
+ ]);
+ this.saveTsv(rows, `${f.cimsCode}-${this.dateTag()}.xls`);
+ }
+
+ // ---- helpers ----
+ private dateTag(): string {
+ const d = new Date();
+ return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
+ }
+ private saveTsv(rows: string[][], filename: string): void {
+ const tsv = rows.map((r) => r.map((v) => (v || '').replace(/\t/g, ' ')).join('\t')).join('\n');
+ this.saveBlob(tsv, filename, 'application/vnd.ms-excel');
+ }
+ private saveBlob(content: string, filename: string, mime: string): void {
+ const blob = new Blob([content], { type: `${mime};charset=utf-8` });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+}
diff --git a/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.html b/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.html
new file mode 100644
index 000000000..355e27c99
--- /dev/null
+++ b/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.html
@@ -0,0 +1,419 @@
+
+
+
+
+
+
+
+
+
CKYC Compliance Reports — {{ currentReport.name }}
+
{{ currentReport.description }}
+
+
+
+ Ref {{ currentReport.sourceRef }}
+
+ As on {{ bank.asOnDate }}
+
+ ✕
+
+
+
+
+
+ Report:
+
+ {{ r.name }}
+
+
+
+
+
+
+
+
+
+
Total Customers
+
{{ rekyc.length | number }}
+
Filtered: {{ filteredRekyc.length | number }}
+
+
+
KYC Overdue
+
{{ rekycOverdue }}
+
Past expiry date
+
+
+
Due in 90 days
+
{{ rekycDueSoon }}
+
Refresh window opening
+
+
+
High Risk
+
{{ rekycHighRisk }}
+
Yearly KYC required
+
+
+
≥ ₹25 Lacs Segment
+
{{ rekycAbove25L }}
+
Enhanced due diligence
+
+
+
+
+
+
Re-KYC Pending Register — {{ filteredRekyc.length }} of {{ rekyc.length }}
+
+ Export CSV
+
+
+
+
+
+ Risk
+
+ All
+ Low
+ Medium
+ High
+
+
+
+ Branch
+
+
+
+ Segment
+
+ All
+ ≥ ₹25 Lacs
+ < ₹25 Lacs
+
+
+
+ KYC Status
+
+ All
+ Overdue
+ Due in 90 days
+
+
+
+
+
+
+
+
+
+
+
+ UCode
+ Customer Name
+ Type
+ Risk
+ Branch
+ KYC Expiry
+ Status
+ Segment
+
+
+
+
+ {{ r.ucode }}
+ {{ r.name }}
+ {{ r.customerType }}
+
+ {{ r.risk }}
+
+
+ {{ r.branch }} ({{ r.branchCode }})
+
+ {{ r.kycExpiry }}
+
+ 0,
+ warn: r.daysOverdue <= 0 && r.daysOverdue >= -90,
+ good: r.daysOverdue < -90
+ }"
+ >
+ 0">OVERDUE {{ r.daysOverdue }}d
+ = -90"
+ >DUE IN {{ -r.daysOverdue }}d
+ WITHIN
+
+
+
+ ≥ 25 L
+ < 25 L
+
+
+
+ No customers match the current filters.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total Reviews
+
{{ riskReview.length | number }}
+
{{ riskReviewPeriod.from }} to {{ riskReviewPeriod.to }}
+
+
+
Risk Inserted
+
{{ riskInsertedCount }}
+
Occupation / Pancard
+
+
+
Risk Upgraded
+
{{ riskUpgradedCount }}
+
LOW→MED, MED→HIGH
+
+
+
Accounts Frozen
+
{{ riskFrozenCount }}
+
Suspicious / High-Risk
+
+
+
+
+
+
+ Risk Updating History — {{ riskReviewPeriod.from }} TO {{ riskReviewPeriod.to }}
+
+
+ Export CSV
+
+
+
+
+
+ Branch
+
+
+
+ New Risk
+
+ All
+ Low
+ Medium
+ High
+
+
+
+ Account Frozen
+
+ All
+ Yes
+ No
+
+
+
+
+
+
+
+
+
+
+
+ Branch
+ A/c Type
+ Client ID
+ Customer
+ Old → New Risk
+ Mobile
+ Description
+ Frozen
+ Updated On
+
+
+
+
+
+ {{ r.branch }} ({{ r.branchCode }})
+
+
+ {{ r.acType }}
+
+ {{ r.clientId }}
+ {{ r.customerName }}
+
+ {{ r.oldRisk }}
+
+ {{ r.newRisk }}
+
+ {{ r.mobile }}
+ {{ r.description }}
+
+ FROZEN
+ —
+
+ {{ r.updatedOn }}
+
+
+ No risk-update entries match the filters.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Total Secured A/c
+
{{ cersaiTotalSecured | number }}
+
Across all branches
+
+
+
CERSAI Found
+
{{ cersaiTotalFound | number }}
+
Registered with CERSAI
+
+
+
CERSAI Not Found
+
{{ cersaiTotalNotFound | number }}
+
Pending registration
+
+
+
Compliance %
+
+ {{ cersaiCompliancePct.toFixed(2) }}%
+
+
Target: ≥ 95%
+
+
+
+
+
+
CERSAI Registration Status — Branch & Account-Type Wise
+
+ Export CSV
+
+
+
+
+
+ Branch
+
+
+
+ Account Type
+
+ All
+ Term Loan (TL)
+ Home Loan (HL)
+ Cash Credit (CC)
+ Overdraft (OD)
+
+
+
+
+
+
+
+
+
+
+
+ Branch Code
+ Branch
+ A/c Type
+ CERSAI Found
+ CERSAI Not Found
+ Total Secured
+ Compliance %
+
+
+
+
+ {{ r.branchCode }}
+ {{ r.branch }}
+
+ {{ r.acType }}
+
+ {{ r.found | number }}
+ 0">{{ r.notFound | number }}
+
+ {{ r.totalSecured | number }}
+
+
+ {{ cersaiPct(r).toFixed(1) }}%
+
+
+
+ BANK TOTAL
+
+ {{ cersaiTotalFound | number }}
+
+
+ {{ cersaiTotalNotFound | number }}
+
+
+ {{ cersaiTotalSecured | number }}
+
+
+ {{ cersaiCompliancePct.toFixed(2) }}%
+
+
+
+ No CERSAI rows match the filters.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.scss b/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.scss
new file mode 100644
index 000000000..3445a6c15
--- /dev/null
+++ b/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.scss
@@ -0,0 +1,610 @@
+/**
+ * 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/.
+ */
+
+:host {
+ --ckr-good: #15803d;
+ --ckr-good-l: #dcfce7;
+ --ckr-mid: #d97706;
+ --ckr-mid-l: #fef3c7;
+ --ckr-low: #c0392b;
+ --ckr-low-l: #fdecea;
+ --ckr-pink: #be185d;
+ --ckr-pink-l: #fce7f3;
+ --ckr-purple: #6d28d9;
+ --ckr-purple-l: #ede9fe;
+ --ckr-blue: #2563eb;
+ --ckr-blue-l: #dbeafe;
+ --ckr-g50: #f8fafc;
+ --ckr-g100: #f1f5f9;
+ --ckr-g200: #e2e8f0;
+ --ckr-g300: #cbd5e1;
+ --ckr-g500: #64748b;
+ --ckr-g700: #334155;
+ --ckr-g900: #0f172a;
+
+ display: block;
+ font-family:
+ 'DM Sans',
+ system-ui,
+ -apple-system,
+ sans-serif;
+ color: var(--ckr-g900);
+}
+
+.ckr-dlg {
+ background: #fff;
+ border-radius: 14px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ max-height: 92vh;
+}
+
+/* HEADER */
+.ckr-head {
+ padding: 18px 22px;
+ border-bottom: 1px solid var(--ckr-g200);
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-shrink: 0;
+ background: linear-gradient(135deg, #fdf2f8 0%, #fff 100%);
+}
+
+.ckr-head-left {
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ flex: 1;
+}
+
+.ckr-head-ic {
+ width: 44px;
+ height: 44px;
+ border-radius: 11px;
+ background: var(--ckr-pink-l);
+ color: var(--ckr-pink);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.ckr-title {
+ margin: 0;
+ font-size: 17px;
+ font-weight: 700;
+ color: var(--ckr-g900);
+}
+
+.ckr-sub {
+ margin: 2px 0 0;
+ font-size: 12px;
+ color: var(--ckr-g500);
+}
+
+.ckr-head-actions {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.ckr-meta {
+ display: inline-flex;
+ align-items: center;
+ font-size: 11px;
+ color: var(--ckr-g500);
+ padding: 5px 10px;
+ background: #fff;
+ border: 1px solid var(--ckr-g200);
+ border-radius: 6px;
+
+ fa-icon {
+ color: var(--ckr-pink);
+ margin-right: 4px;
+ }
+}
+
+.ckr-close {
+ width: 32px;
+ height: 32px;
+ border-radius: 8px;
+ border: 1px solid var(--ckr-g200);
+ background: #fff;
+ font-size: 14px;
+ color: var(--ckr-g500);
+ cursor: pointer;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--ckr-g100);
+ color: var(--ckr-g900);
+ }
+}
+
+/* REPORT SELECTOR BAR */
+.ckr-sel-bar {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 22px;
+ background: var(--ckr-g50);
+ border-bottom: 1px solid var(--ckr-g200);
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.ckr-sel-l {
+ font-size: 11px;
+ font-weight: 700;
+ color: var(--ckr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-right: 4px;
+}
+
+.ckr-sel-pill {
+ padding: 7px 16px;
+ border-radius: 999px;
+ border: 1px solid var(--ckr-g200);
+ background: #fff;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--ckr-g700);
+ transition: all 0.15s;
+ display: inline-flex;
+ align-items: center;
+
+ fa-icon {
+ color: var(--ckr-g500);
+ }
+
+ &:hover {
+ background: var(--ckr-pink-l);
+ border-color: var(--ckr-pink);
+ color: var(--ckr-pink);
+
+ fa-icon {
+ color: var(--ckr-pink);
+ }
+ }
+
+ &.active {
+ background: var(--ckr-pink);
+ border-color: var(--ckr-pink);
+ color: #fff;
+ box-shadow: 0 2px 6px rgb(190 24 93 / 25%);
+
+ fa-icon {
+ color: #fff;
+ }
+ }
+}
+
+/* BODY */
+.ckr-body {
+ padding: 18px 22px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ overflow: auto;
+ flex: 1;
+ background: var(--ckr-g50);
+}
+
+/* KPI STATS */
+.ckr-stats {
+ display: grid;
+ grid-template-columns: repeat(5, 1fr);
+ gap: 12px;
+}
+
+.ckr-stat-card {
+ background: #fff;
+ border: 1px solid var(--ckr-g200);
+ border-radius: 12px;
+ padding: 14px 16px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+
+ &.alert {
+ background: var(--ckr-low-l);
+ border-color: var(--ckr-low);
+
+ .ckr-stat-v {
+ color: var(--ckr-low);
+ }
+ }
+
+ &.warn {
+ background: var(--ckr-mid-l);
+ border-color: var(--ckr-mid);
+
+ .ckr-stat-v {
+ color: var(--ckr-mid);
+ }
+ }
+
+ &.good {
+ background: var(--ckr-good-l);
+ border-color: var(--ckr-good);
+
+ .ckr-stat-v {
+ color: var(--ckr-good);
+ }
+ }
+}
+
+.ckr-stat-l {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--ckr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.ckr-stat-v {
+ font-size: 22px;
+ font-weight: 700;
+ color: var(--ckr-g900);
+ line-height: 1.2;
+ margin-top: 2px;
+
+ &.good {
+ color: var(--ckr-good);
+ }
+
+ &.mid {
+ color: var(--ckr-mid);
+ }
+
+ &.low {
+ color: var(--ckr-low);
+ }
+}
+
+.ckr-stat-meta {
+ font-size: 11px;
+ color: var(--ckr-g500);
+ margin-top: 4px;
+}
+
+/* SECTION */
+.ckr-section {
+ background: #fff;
+ border: 1px solid var(--ckr-g200);
+ border-radius: 12px;
+ padding: 16px 18px;
+ box-shadow: 0 1px 3px rgb(0 0 0 / 4%);
+}
+
+.ckr-sec-h {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 12px;
+ flex-wrap: wrap;
+}
+
+.ckr-sec-t {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 700;
+ color: var(--ckr-g700);
+}
+
+.ckr-btn-xls {
+ font-size: 12px !important;
+ height: 34px !important;
+ color: #fff !important;
+ background: var(--ckr-pink) !important;
+}
+
+/* FILTERS */
+.ckr-filters {
+ display: flex;
+ gap: 12px;
+ padding: 12px 14px;
+ background: var(--ckr-g50);
+ border: 1px solid var(--ckr-g200);
+ border-radius: 8px;
+ margin-bottom: 12px;
+ align-items: flex-end;
+ flex-wrap: wrap;
+}
+
+.ckr-f {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 140px;
+
+ label {
+ font-size: 10px;
+ font-weight: 600;
+ color: var(--ckr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+
+ select,
+ input {
+ height: 32px;
+ padding: 0 10px;
+ border: 1px solid var(--ckr-g200);
+ border-radius: 6px;
+ background: #fff;
+ font-family: inherit;
+ font-size: 12px;
+ color: var(--ckr-g900);
+
+ &:focus {
+ outline: none;
+ border-color: var(--ckr-pink);
+ box-shadow: 0 0 0 3px rgb(190 24 93 / 12%);
+ }
+ }
+}
+
+.ckr-f-reset {
+ height: 32px;
+ width: 32px;
+ border-radius: 6px;
+ border: 1px solid var(--ckr-g200);
+ background: #fff;
+ color: var(--ckr-g500);
+ cursor: pointer;
+ transition: all 0.15s;
+
+ &:hover {
+ background: var(--ckr-low-l);
+ color: var(--ckr-low);
+ border-color: var(--ckr-low);
+ }
+}
+
+/* TABLE */
+.ckr-tbl-wrap {
+ border: 1px solid var(--ckr-g200);
+ border-radius: 8px;
+ overflow: auto;
+}
+
+.ckr-tbl {
+ width: 100%;
+ border-collapse: collapse;
+
+ th {
+ text-align: left;
+ padding: 10px 14px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--ckr-g500);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ background: var(--ckr-g50);
+ border-bottom: 1px solid var(--ckr-g200);
+ white-space: nowrap;
+ }
+
+ td {
+ padding: 10px 14px;
+ border-bottom: 1px solid var(--ckr-g100);
+ font-size: 13px;
+ color: var(--ckr-g700);
+ vertical-align: middle;
+
+ &.bad {
+ color: var(--ckr-low);
+ font-weight: 600;
+ }
+ }
+
+ tr:last-child td {
+ border-bottom: none;
+ }
+
+ tr:hover td {
+ background: var(--ckr-g50);
+ }
+
+ .row-total td {
+ background: var(--ckr-pink-l);
+ color: var(--ckr-g900);
+ font-weight: 700;
+ border-top: 2px solid var(--ckr-pink);
+ }
+}
+
+/* PILLS */
+.risk-pill {
+ display: inline-block;
+ padding: 2px 9px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 700;
+
+ &.risk-low {
+ background: var(--ckr-good-l);
+ color: var(--ckr-good);
+ }
+
+ &.risk-mid {
+ background: var(--ckr-mid-l);
+ color: var(--ckr-mid);
+ }
+
+ &.risk-high {
+ background: var(--ckr-low-l);
+ color: var(--ckr-low);
+ }
+}
+
+.risk-arrow {
+ margin: 0 6px;
+ color: var(--ckr-g500);
+}
+
+.actyp-pill {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 6px;
+ background: var(--ckr-blue-l);
+ color: var(--ckr-blue);
+ font-family: 'DM Mono', monospace;
+ font-size: 11px;
+ font-weight: 600;
+}
+
+.status-pill {
+ display: inline-block;
+ padding: 3px 10px;
+ font-size: 10px;
+ font-weight: 700;
+ border-radius: 999px;
+ letter-spacing: 0.04em;
+
+ &.bad {
+ background: var(--ckr-low-l);
+ color: var(--ckr-low);
+ }
+
+ &.warn {
+ background: var(--ckr-mid-l);
+ color: var(--ckr-mid);
+ }
+
+ &.good {
+ background: var(--ckr-good-l);
+ color: var(--ckr-good);
+ }
+}
+
+.seg-pill {
+ display: inline-block;
+ padding: 2px 9px;
+ border-radius: 6px;
+ font-size: 11px;
+ font-weight: 700;
+
+ &.seg-high {
+ background: var(--ckr-purple-l);
+ color: var(--ckr-purple);
+ }
+
+ &.seg-low {
+ background: var(--ckr-g100);
+ color: var(--ckr-g500);
+ }
+}
+
+.pct-pill {
+ display: inline-block;
+ padding: 3px 10px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 700;
+
+ &.good {
+ background: var(--ckr-good-l);
+ color: var(--ckr-good);
+ }
+
+ &.mid {
+ background: var(--ckr-mid-l);
+ color: var(--ckr-mid);
+ }
+
+ &.low {
+ background: var(--ckr-low-l);
+ color: var(--ckr-low);
+ }
+}
+
+.muted {
+ color: var(--ckr-g300);
+ font-weight: 400;
+}
+
+.ta-c {
+ text-align: center;
+}
+
+.ta-r {
+ text-align: right;
+}
+
+.mono {
+ font-family: 'DM Mono', monospace;
+ font-size: 12px;
+}
+
+.empty {
+ text-align: center;
+ color: var(--ckr-g500);
+ padding: 24px !important;
+ font-style: italic;
+}
+
+.ckr-foot {
+ margin-top: 12px;
+ padding: 10px 14px;
+ font-size: 12px;
+ color: var(--ckr-g500);
+ background: var(--ckr-g50);
+ border-radius: 6px;
+ text-align: right;
+}
+
+/* DIALOG FOOTER */
+.ckr-footer {
+ padding: 12px 22px;
+ border-top: 1px solid var(--ckr-g200);
+ background: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.ckr-footer-l {
+ font-size: 12px;
+ color: var(--ckr-g500);
+ display: flex;
+ align-items: center;
+
+ fa-icon {
+ color: var(--ckr-pink);
+ margin-right: 4px;
+ }
+}
+
+/* RESPONSIVE */
+@media (width <= 1100px) {
+ .ckr-stats {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
+@media (width <= 720px) {
+ .ckr-stats {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .ckr-head,
+ .ckr-sel-bar,
+ .ckr-footer {
+ flex-wrap: wrap;
+ }
+}
diff --git a/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.ts b/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.ts
new file mode 100644
index 000000000..9f4270bfe
--- /dev/null
+++ b/src/app/home/dashboard/ckyc-reports-dialog/ckyc-reports-dialog.component.ts
@@ -0,0 +1,849 @@
+/**
+ * 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/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { CommonModule, DecimalPipe } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
+import { MatButtonModule } from '@angular/material/button';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { FaIconComponent } from '@fortawesome/angular-fontawesome';
+
+export type CkycReportCode = 'REKYC' | 'RISK-REVIEW' | 'CERSAI';
+
+export interface CkycReportsDialogData {
+ initialReport?: CkycReportCode;
+}
+
+interface RekycRow {
+ ucode: string;
+ name: string;
+ customerType: 'INDIVIDUAL' | 'FIRM' | 'SOCIETY' | 'HUF';
+ risk: 'LOW' | 'MEDIUM' | 'HIGH';
+ branch: string;
+ branchCode: string;
+ kycExpiry: string;
+ daysOverdue: number;
+ segment: 'ABOVE-25-LACS' | 'BELOW-25-LACS';
+}
+
+interface RiskReviewRow {
+ branchCode: string;
+ branch: string;
+ acType: 'SB' | 'CA' | 'OD' | 'TL' | 'CC' | 'LT';
+ clientId: string;
+ customerName: string;
+ oldRisk: 'LOW' | 'MEDIUM' | 'HIGH';
+ newRisk: 'LOW' | 'MEDIUM' | 'HIGH';
+ mobile: string;
+ description: string;
+ frozen: boolean;
+ updatedOn: string;
+}
+
+interface CersaiRow {
+ branchCode: string;
+ branch: string;
+ acType: 'TL' | 'CC' | 'OD' | 'HL';
+ found: number;
+ notFound: number;
+ totalSecured: number;
+}
+
+@Component({
+ selector: 'mifosx-ckyc-reports-dialog',
+ standalone: true,
+ templateUrl: './ckyc-reports-dialog.component.html',
+ styleUrls: ['./ckyc-reports-dialog.component.scss'],
+ imports: [
+ CommonModule,
+ DecimalPipe,
+ FormsModule,
+ MatDialogModule,
+ MatButtonModule,
+ MatTooltipModule,
+ FaIconComponent
+ ]
+})
+export class CkycReportsDialogComponent {
+ private dialogRef = inject(MatDialogRef);
+
+ activeReport: CkycReportCode;
+
+ bank = {
+ name: 'Bharat Urban Bank Ltd',
+ branch: 'Head Office',
+ asOnDate: '29-01-2026',
+ fiscalYear: '2025-26',
+ quarter: 'Q3 FY 2025-26',
+ principalOfficer: 'Karthik Kamath',
+ poDesignation: 'Principal Compliance Officer',
+ reportRefNo: '2075/1',
+ userCode: 'BUBL01',
+ fyStart: '01-04-2025',
+ fyEnd: '31-03-2026'
+ };
+
+ reports = [
+ {
+ code: 'REKYC' as CkycReportCode,
+ name: 'Re-KYC Report',
+ icon: 'sync-alt',
+ description: 'Customers due / overdue for periodic KYC refresh based on risk category.',
+ sourceRef: '2075/1'
+ },
+ {
+ code: 'RISK-REVIEW' as CkycReportCode,
+ name: 'Risk Review',
+ icon: 'exclamation-triangle',
+ description: 'Audit trail of risk-category changes per RBI Master Direction on KYC.',
+ sourceRef: '2075/2'
+ },
+ {
+ code: 'CERSAI' as CkycReportCode,
+ name: 'CERSAI Report',
+ icon: 'shield-alt',
+ description: 'Branch-wise CERSAI registration status for secured advances under SARFAESI.',
+ sourceRef: '2075/3'
+ }
+ ];
+
+ // -------------------- Re-KYC Data --------------------
+ rekycFilterRisk: '' | 'LOW' | 'MEDIUM' | 'HIGH' = '';
+ rekycFilterBranch = '';
+ rekycFilterSegment: '' | 'ABOVE-25-LACS' | 'BELOW-25-LACS' = '';
+ rekycFilterStatus: '' | 'OVERDUE' | 'DUE-SOON' = '';
+
+ rekyc: RekycRow[] = [
+ {
+ ucode: 'BUBL000201',
+ name: 'Ravi Kumar Sharma',
+ customerType: 'INDIVIDUAL',
+ risk: 'LOW',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ kycExpiry: '12-04-2026',
+ daysOverdue: -73,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL000418',
+ name: 'Priya Patel',
+ customerType: 'INDIVIDUAL',
+ risk: 'MEDIUM',
+ branch: 'Pune FC Road',
+ branchCode: '02',
+ kycExpiry: '08-12-2025',
+ daysOverdue: 52,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL000522',
+ name: 'Arjun Deshmukh',
+ customerType: 'INDIVIDUAL',
+ risk: 'LOW',
+ branch: 'Nagpur Dharampeth',
+ branchCode: '03',
+ kycExpiry: '01-08-2026',
+ daysOverdue: -184,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL000711',
+ name: 'Vikram Reddy',
+ customerType: 'INDIVIDUAL',
+ risk: 'HIGH',
+ branch: 'Bengaluru Jayanagar',
+ branchCode: '04',
+ kycExpiry: '15-11-2025',
+ daysOverdue: 75,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL000814',
+ name: 'Lakshmi Iyer',
+ customerType: 'INDIVIDUAL',
+ risk: 'LOW',
+ branch: 'Bengaluru Indiranagar',
+ branchCode: '05',
+ kycExpiry: '08-10-2026',
+ daysOverdue: -252,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL001012',
+ name: 'Nirav Shah',
+ customerType: 'INDIVIDUAL',
+ risk: 'MEDIUM',
+ branch: 'Ahmedabad Satellite',
+ branchCode: '06',
+ kycExpiry: '14-02-2026',
+ daysOverdue: -16,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL001145',
+ name: 'Kavita Verma',
+ customerType: 'INDIVIDUAL',
+ risk: 'HIGH',
+ branch: 'Lucknow Gomti Nagar',
+ branchCode: '07',
+ kycExpiry: '11-11-2025',
+ daysOverdue: 79,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL001458',
+ name: 'Sameer Kapoor',
+ customerType: 'INDIVIDUAL',
+ risk: 'MEDIUM',
+ branch: 'Delhi Greater Kailash',
+ branchCode: '08',
+ kycExpiry: '25-08-2026',
+ daysOverdue: -208,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL001721',
+ name: 'Karthik Subramanian',
+ customerType: 'INDIVIDUAL',
+ risk: 'LOW',
+ branch: 'Chennai Valasaravakkam',
+ branchCode: '09',
+ kycExpiry: '03-05-2027',
+ daysOverdue: -460,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL002012',
+ name: 'Abhishek Banerjee',
+ customerType: 'INDIVIDUAL',
+ risk: 'MEDIUM',
+ branch: 'Kolkata Salt Lake',
+ branchCode: '10',
+ kycExpiry: '17-12-2025',
+ daysOverdue: 43,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL002314',
+ name: 'Gaurav Thakur',
+ customerType: 'INDIVIDUAL',
+ risk: 'LOW',
+ branch: 'Shimla Sector 4',
+ branchCode: '11',
+ kycExpiry: '22-04-2027',
+ daysOverdue: -448,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL002615',
+ name: 'Manmohan Singh Gill',
+ customerType: 'INDIVIDUAL',
+ risk: 'HIGH',
+ branch: 'Amritsar Model Town',
+ branchCode: '12',
+ kycExpiry: '05-01-2026',
+ daysOverdue: 24,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL002918',
+ name: 'Sunita Devi',
+ customerType: 'INDIVIDUAL',
+ risk: 'LOW',
+ branch: 'Bhopal Arera Colony',
+ branchCode: '13',
+ kycExpiry: '18-06-2027',
+ daysOverdue: -505,
+ segment: 'BELOW-25-LACS'
+ },
+ {
+ ucode: 'BUBL003221',
+ name: 'Deepak Tiwari',
+ customerType: 'INDIVIDUAL',
+ risk: 'MEDIUM',
+ branch: 'Indore Vijay Nagar',
+ branchCode: '14',
+ kycExpiry: '12-03-2026',
+ daysOverdue: -42,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL003524',
+ name: 'Harpreet Kaur',
+ customerType: 'INDIVIDUAL',
+ risk: 'MEDIUM',
+ branch: 'Ludhiana Sarabha Nagar',
+ branchCode: '15',
+ kycExpiry: '08-09-2025',
+ daysOverdue: 143,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL003812',
+ name: 'Saraswati Mahila Co-op Society',
+ customerType: 'SOCIETY',
+ risk: 'MEDIUM',
+ branch: 'Mumbai Main',
+ branchCode: '01',
+ kycExpiry: '14-12-2025',
+ daysOverdue: 46,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL004115',
+ name: 'Aditya Enterprises',
+ customerType: 'FIRM',
+ risk: 'HIGH',
+ branch: 'Pune FC Road',
+ branchCode: '02',
+ kycExpiry: '02-10-2025',
+ daysOverdue: 119,
+ segment: 'ABOVE-25-LACS'
+ },
+ {
+ ucode: 'BUBL004318',
+ name: 'Vasudha Trading HUF',
+ customerType: 'HUF',
+ risk: 'MEDIUM',
+ branch: 'Delhi Greater Kailash',
+ branchCode: '08',
+ kycExpiry: '28-11-2025',
+ daysOverdue: 62,
+ segment: 'ABOVE-25-LACS'
+ }
+ ];
+
+ // -------------------- Risk Review Data --------------------
+ riskFilterBranch = '';
+ riskFilterNew: '' | 'LOW' | 'MEDIUM' | 'HIGH' = '';
+ riskFilterFrozen: '' | 'Y' | 'N' = '';
+
+ riskReviewPeriod = { from: '01-10-2025', to: '31-12-2025' };
+
+ riskReview: RiskReviewRow[] = [
+ {
+ branchCode: '01',
+ branch: 'Mumbai Main',
+ acType: 'SB',
+ clientId: 'BUBL000201',
+ customerName: 'Ravi Kumar Sharma',
+ oldRisk: 'LOW',
+ newRisk: 'LOW',
+ mobile: '+91-9988002211',
+ description: 'Periodic Risk Review — Individual',
+ frozen: false,
+ updatedOn: '12-10-2025'
+ },
+ {
+ branchCode: '02',
+ branch: 'Pune FC Road',
+ acType: 'CA',
+ clientId: 'BUBL000418',
+ customerName: 'Priya Patel',
+ oldRisk: 'LOW',
+ newRisk: 'MEDIUM',
+ mobile: '+91-9617086784',
+ description: 'Risk Updated — Account Turn Over Medium',
+ frozen: false,
+ updatedOn: '18-10-2025'
+ },
+ {
+ branchCode: '03',
+ branch: 'Nagpur Dharampeth',
+ acType: 'TL',
+ clientId: 'BUBL000522',
+ customerName: 'Arjun Deshmukh',
+ oldRisk: 'MEDIUM',
+ newRisk: 'LOW',
+ mobile: '+91-9755179366',
+ description: 'Risk Insert With Pancard verification',
+ frozen: false,
+ updatedOn: '02-11-2025'
+ },
+ {
+ branchCode: '04',
+ branch: 'Bengaluru Jayanagar',
+ acType: 'OD',
+ clientId: 'BUBL000711',
+ customerName: 'Vikram Reddy',
+ oldRisk: 'MEDIUM',
+ newRisk: 'HIGH',
+ mobile: '+91-9826544604',
+ description: 'Risk Insert With Occupation — Politically Exposed',
+ frozen: true,
+ updatedOn: '08-11-2025'
+ },
+ {
+ branchCode: '05',
+ branch: 'Bengaluru Indiranagar',
+ acType: 'SB',
+ clientId: 'BUBL000814',
+ customerName: 'Lakshmi Iyer',
+ oldRisk: 'LOW',
+ newRisk: 'LOW',
+ mobile: '+91-9893677629',
+ description: 'Identification — Individual Confirmed',
+ frozen: false,
+ updatedOn: '15-11-2025'
+ },
+ {
+ branchCode: '06',
+ branch: 'Ahmedabad Satellite',
+ acType: 'CC',
+ clientId: 'BUBL001012',
+ customerName: 'Nirav Shah',
+ oldRisk: 'LOW',
+ newRisk: 'MEDIUM',
+ mobile: '+91-9926362230',
+ description: 'Risk Updated — Account Turn Over Medium',
+ frozen: false,
+ updatedOn: '22-11-2025'
+ },
+ {
+ branchCode: '07',
+ branch: 'Lucknow Gomti Nagar',
+ acType: 'CA',
+ clientId: 'BUBL001145',
+ customerName: 'Kavita Verma',
+ oldRisk: 'MEDIUM',
+ newRisk: 'HIGH',
+ mobile: '+91-9926322804',
+ description: 'Suspicious Transaction Trigger — STR raised',
+ frozen: true,
+ updatedOn: '28-11-2025'
+ },
+ {
+ branchCode: '08',
+ branch: 'Delhi Greater Kailash',
+ acType: 'OD',
+ clientId: 'BUBL001458',
+ customerName: 'Sameer Kapoor',
+ oldRisk: 'MEDIUM',
+ newRisk: 'MEDIUM',
+ mobile: '+91-9827444636',
+ description: 'Periodic Risk Review — Individual',
+ frozen: false,
+ updatedOn: '04-12-2025'
+ },
+ {
+ branchCode: '09',
+ branch: 'Chennai Valasaravakkam',
+ acType: 'SB',
+ clientId: 'BUBL001721',
+ customerName: 'Karthik Subramanian',
+ oldRisk: 'LOW',
+ newRisk: 'LOW',
+ mobile: '+91-9827565456',
+ description: 'Identification — Individual Confirmed',
+ frozen: false,
+ updatedOn: '11-12-2025'
+ },
+ {
+ branchCode: '10',
+ branch: 'Kolkata Salt Lake',
+ acType: 'TL',
+ clientId: 'BUBL002012',
+ customerName: 'Abhishek Banerjee',
+ oldRisk: 'LOW',
+ newRisk: 'MEDIUM',
+ mobile: '+91-9174151366',
+ description: 'Risk Insert With Occupation',
+ frozen: false,
+ updatedOn: '15-12-2025'
+ },
+ {
+ branchCode: '11',
+ branch: 'Shimla Sector 4',
+ acType: 'SB',
+ clientId: 'BUBL002314',
+ customerName: 'Gaurav Thakur',
+ oldRisk: 'LOW',
+ newRisk: 'LOW',
+ mobile: '+91-9111026263',
+ description: 'Periodic Risk Review — Individual',
+ frozen: false,
+ updatedOn: '18-12-2025'
+ },
+ {
+ branchCode: '12',
+ branch: 'Amritsar Model Town',
+ acType: 'CC',
+ clientId: 'BUBL002615',
+ customerName: 'Manmohan Singh Gill',
+ oldRisk: 'HIGH',
+ newRisk: 'HIGH',
+ mobile: '+91-9407272122',
+ description: 'High-Risk Re-confirmation — PEP review',
+ frozen: true,
+ updatedOn: '22-12-2025'
+ },
+ {
+ branchCode: '13',
+ branch: 'Bhopal Arera Colony',
+ acType: 'SB',
+ clientId: 'BUBL002918',
+ customerName: 'Sunita Devi',
+ oldRisk: 'LOW',
+ newRisk: 'LOW',
+ mobile: '+91-9691683108',
+ description: 'Identification — Individual Confirmed',
+ frozen: false,
+ updatedOn: '24-12-2025'
+ },
+ {
+ branchCode: '14',
+ branch: 'Indore Vijay Nagar',
+ acType: 'OD',
+ clientId: 'BUBL003221',
+ customerName: 'Deepak Tiwari',
+ oldRisk: 'MEDIUM',
+ newRisk: 'HIGH',
+ mobile: '+91-9826875772',
+ description: 'Risk Updated — Account Turn Over High',
+ frozen: true,
+ updatedOn: '28-12-2025'
+ },
+ {
+ branchCode: '15',
+ branch: 'Ludhiana Sarabha Nagar',
+ acType: 'TL',
+ clientId: 'BUBL003524',
+ customerName: 'Harpreet Kaur',
+ oldRisk: 'MEDIUM',
+ newRisk: 'MEDIUM',
+ mobile: '+91-9407256202',
+ description: 'Risk Insert With Pancard',
+ frozen: false,
+ updatedOn: '30-12-2025'
+ }
+ ];
+
+ // -------------------- CERSAI Data --------------------
+ cersai: CersaiRow[] = [
+ { branchCode: '01', branch: 'Mumbai Main', acType: 'TL', found: 142, notFound: 8, totalSecured: 150 },
+ { branchCode: '02', branch: 'Pune FC Road', acType: 'TL', found: 98, notFound: 4, totalSecured: 102 },
+ { branchCode: '02', branch: 'Pune FC Road', acType: 'CC', found: 38, notFound: 12, totalSecured: 50 },
+ { branchCode: '03', branch: 'Nagpur Dharampeth', acType: 'HL', found: 64, notFound: 6, totalSecured: 70 },
+ { branchCode: '04', branch: 'Bengaluru Jayanagar', acType: 'TL', found: 124, notFound: 0, totalSecured: 124 },
+ { branchCode: '04', branch: 'Bengaluru Jayanagar', acType: 'OD', found: 42, notFound: 8, totalSecured: 50 },
+ { branchCode: '05', branch: 'Bengaluru Indiranagar', acType: 'HL', found: 86, notFound: 4, totalSecured: 90 },
+ { branchCode: '06', branch: 'Ahmedabad Satellite', acType: 'TL', found: 110, notFound: 14, totalSecured: 124 },
+ { branchCode: '07', branch: 'Lucknow Gomti Nagar', acType: 'CC', found: 28, notFound: 12, totalSecured: 40 },
+ { branchCode: '08', branch: 'Delhi Greater Kailash', acType: 'TL', found: 156, notFound: 4, totalSecured: 160 },
+ { branchCode: '08', branch: 'Delhi Greater Kailash', acType: 'OD', found: 64, notFound: 16, totalSecured: 80 },
+ { branchCode: '09', branch: 'Chennai Valasaravakkam', acType: 'HL', found: 72, notFound: 8, totalSecured: 80 },
+ { branchCode: '10', branch: 'Kolkata Salt Lake', acType: 'TL', found: 88, notFound: 12, totalSecured: 100 },
+ { branchCode: '11', branch: 'Shimla Sector 4', acType: 'TL', found: 32, notFound: 8, totalSecured: 40 },
+ { branchCode: '12', branch: 'Amritsar Model Town', acType: 'CC', found: 18, notFound: 22, totalSecured: 40 },
+ { branchCode: '13', branch: 'Bhopal Arera Colony', acType: 'HL', found: 56, notFound: 4, totalSecured: 60 },
+ { branchCode: '14', branch: 'Indore Vijay Nagar', acType: 'TL', found: 92, notFound: 18, totalSecured: 110 },
+ { branchCode: '15', branch: 'Ludhiana Sarabha Nagar', acType: 'OD', found: 36, notFound: 14, totalSecured: 50 }
+ ];
+
+ cersaiFilterBranch = '';
+ cersaiFilterAcType: '' | 'TL' | 'CC' | 'OD' | 'HL' = '';
+
+ data = inject(MAT_DIALOG_DATA, { optional: true });
+
+ constructor() {
+ this.activeReport = this.data?.initialReport ?? 'REKYC';
+ }
+
+ // -------------------- Re-KYC computed --------------------
+ get filteredRekyc(): RekycRow[] {
+ const branch = this.rekycFilterBranch.trim().toLowerCase();
+ return this.rekyc.filter((r) => {
+ if (this.rekycFilterRisk && r.risk !== this.rekycFilterRisk) return false;
+ if (branch && !r.branch.toLowerCase().includes(branch)) return false;
+ if (this.rekycFilterSegment && r.segment !== this.rekycFilterSegment) return false;
+ if (this.rekycFilterStatus === 'OVERDUE' && r.daysOverdue <= 0) return false;
+ if (this.rekycFilterStatus === 'DUE-SOON' && (r.daysOverdue > 0 || r.daysOverdue < -90)) return false;
+ return true;
+ });
+ }
+ resetRekycFilters(): void {
+ this.rekycFilterRisk = '';
+ this.rekycFilterBranch = '';
+ this.rekycFilterSegment = '';
+ this.rekycFilterStatus = '';
+ }
+ get rekycOverdue(): number {
+ return this.rekyc.filter((r) => r.daysOverdue > 0).length;
+ }
+ get rekycDueSoon(): number {
+ return this.rekyc.filter((r) => r.daysOverdue <= 0 && r.daysOverdue >= -90).length;
+ }
+ get rekycHighRisk(): number {
+ return this.rekyc.filter((r) => r.risk === 'HIGH').length;
+ }
+ get rekycAbove25L(): number {
+ return this.rekyc.filter((r) => r.segment === 'ABOVE-25-LACS').length;
+ }
+
+ // -------------------- Risk Review computed --------------------
+ get filteredRiskReview(): RiskReviewRow[] {
+ const branch = this.riskFilterBranch.trim().toLowerCase();
+ return this.riskReview.filter((r) => {
+ if (branch && !r.branch.toLowerCase().includes(branch)) return false;
+ if (this.riskFilterNew && r.newRisk !== this.riskFilterNew) return false;
+ if (this.riskFilterFrozen === 'Y' && !r.frozen) return false;
+ if (this.riskFilterFrozen === 'N' && r.frozen) return false;
+ return true;
+ });
+ }
+ resetRiskFilters(): void {
+ this.riskFilterBranch = '';
+ this.riskFilterNew = '';
+ this.riskFilterFrozen = '';
+ }
+ get riskInsertedCount(): number {
+ return this.riskReview.filter((r) => r.description.toLowerCase().includes('insert')).length;
+ }
+ get riskUpgradedCount(): number {
+ return this.riskReview.filter(
+ (r) => (r.oldRisk === 'LOW' && r.newRisk !== 'LOW') || (r.oldRisk === 'MEDIUM' && r.newRisk === 'HIGH')
+ ).length;
+ }
+ get riskFrozenCount(): number {
+ return this.riskReview.filter((r) => r.frozen).length;
+ }
+ riskBadgeClass(risk: 'LOW' | 'MEDIUM' | 'HIGH'): string {
+ return risk === 'HIGH' ? 'risk-high' : risk === 'MEDIUM' ? 'risk-mid' : 'risk-low';
+ }
+
+ // -------------------- CERSAI computed --------------------
+ get filteredCersai(): CersaiRow[] {
+ const branch = this.cersaiFilterBranch.trim().toLowerCase();
+ return this.cersai.filter((r) => {
+ if (branch && !r.branch.toLowerCase().includes(branch)) return false;
+ if (this.cersaiFilterAcType && r.acType !== this.cersaiFilterAcType) return false;
+ return true;
+ });
+ }
+ resetCersaiFilters(): void {
+ this.cersaiFilterBranch = '';
+ this.cersaiFilterAcType = '';
+ }
+ get cersaiTotalFound(): number {
+ return this.cersai.reduce((s, r) => s + r.found, 0);
+ }
+ get cersaiTotalNotFound(): number {
+ return this.cersai.reduce((s, r) => s + r.notFound, 0);
+ }
+ get cersaiTotalSecured(): number {
+ return this.cersai.reduce((s, r) => s + r.totalSecured, 0);
+ }
+ get cersaiCompliancePct(): number {
+ return this.cersaiTotalSecured === 0 ? 0 : +((this.cersaiTotalFound / this.cersaiTotalSecured) * 100).toFixed(2);
+ }
+ cersaiPct(r: CersaiRow): number {
+ return r.totalSecured === 0 ? 0 : +((r.found / r.totalSecured) * 100).toFixed(1);
+ }
+ cersaiPctClass(pct: number): string {
+ if (pct >= 95) return 'good';
+ if (pct >= 80) return 'mid';
+ return 'low';
+ }
+
+ setReport(code: CkycReportCode): void {
+ this.activeReport = code;
+ }
+ close(): void {
+ this.dialogRef.close();
+ }
+
+ get currentReport() {
+ return this.reports.find((r) => r.code === this.activeReport)!;
+ }
+
+ // -------------------- Exports --------------------
+
+ exportRekycCsv(): void {
+ const headers = [
+ 'UCode',
+ 'Name',
+ 'Customer Type',
+ 'Risk',
+ 'Branch',
+ 'Branch Code',
+ 'KYC Expiry',
+ 'Days Overdue',
+ 'Status',
+ 'Segment'
+ ];
+ const rows = [headers];
+ for (const r of this.filteredRekyc) {
+ const status = r.daysOverdue > 0 ? 'OVERDUE' : r.daysOverdue >= -90 ? 'DUE SOON' : 'WITHIN';
+ rows.push([
+ r.ucode,
+ r.name,
+ r.customerType,
+ r.risk,
+ r.branch,
+ r.branchCode,
+ r.kycExpiry,
+ r.daysOverdue.toString(),
+ status,
+ r.segment
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ 'TOTAL COUNT',
+ this.filteredRekyc.length.toString()
+ ]);
+ rows.push([
+ 'Report Ref No',
+ this.bank.reportRefNo,
+ 'User Code',
+ this.bank.userCode
+ ]);
+ this.saveCsv(rows, `Rekyc-Report-${this.dateTag()}.csv`);
+ }
+
+ exportRiskReviewCsv(): void {
+ const headers = [
+ 'Branch Code',
+ 'Branch',
+ 'A/c Type',
+ 'Client ID',
+ 'Customer Name',
+ 'Old Risk',
+ 'New Risk',
+ 'Mobile',
+ 'Description',
+ 'Account Frozen',
+ 'Updated On'
+ ];
+ const rows = [headers];
+ for (const r of this.filteredRiskReview) {
+ rows.push([
+ r.branchCode,
+ r.branch,
+ r.acType,
+ r.clientId,
+ r.customerName,
+ r.oldRisk,
+ r.newRisk,
+ r.mobile,
+ r.description,
+ r.frozen ? 'Y' : 'N',
+ r.updatedOn
+ ]);
+ }
+ rows.push([]);
+ rows.push(['Risk Category Updated Summary']);
+ rows.push([
+ 'Total Clients',
+ this.filteredRiskReview.length.toString()
+ ]);
+ rows.push([
+ 'Risk Inserted',
+ this.riskInsertedCount.toString()
+ ]);
+ rows.push([
+ 'Risk Upgraded',
+ this.riskUpgradedCount.toString()
+ ]);
+ rows.push([
+ 'Account Frozen',
+ this.riskFrozenCount.toString()
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Period',
+ `${this.riskReviewPeriod.from} TO ${this.riskReviewPeriod.to}`
+ ]);
+ rows.push([
+ 'Print Date',
+ new Date().toLocaleString('en-IN')
+ ]);
+ this.saveCsv(rows, `Risk-Review-${this.dateTag()}.csv`);
+ }
+
+ exportCersaiCsv(): void {
+ const headers = [
+ 'Branch Code',
+ 'Branch',
+ 'A/c Type',
+ 'CERSAI Found',
+ 'CERSAI Not Found',
+ 'Total Secured',
+ 'Compliance %'
+ ];
+ const rows = [headers];
+ for (const r of this.filteredCersai) {
+ rows.push([
+ r.branchCode,
+ r.branch,
+ r.acType,
+ r.found.toString(),
+ r.notFound.toString(),
+ r.totalSecured.toString(),
+ this.cersaiPct(r).toFixed(2) + '%'
+ ]);
+ }
+ rows.push([]);
+ rows.push([
+ 'BANK TOTAL',
+ '',
+ '',
+ this.cersaiTotalFound.toString(),
+ this.cersaiTotalNotFound.toString(),
+ this.cersaiTotalSecured.toString(),
+ this.cersaiCompliancePct.toFixed(2) + '%'
+ ]);
+ rows.push([]);
+ rows.push([
+ 'Bank',
+ this.bank.name
+ ]);
+ rows.push([
+ 'Branch',
+ this.bank.branch
+ ]);
+ rows.push([
+ 'As On',
+ this.bank.asOnDate
+ ]);
+ rows.push([
+ 'Principal Officer',
+ this.bank.principalOfficer
+ ]);
+ this.saveCsv(rows, `CERSAI-Report-${this.dateTag()}.csv`);
+ }
+
+ exportCurrentExcel(): void {
+ if (this.activeReport === 'REKYC') return this.exportRekycCsv();
+ if (this.activeReport === 'RISK-REVIEW') return this.exportRiskReviewCsv();
+ if (this.activeReport === 'CERSAI') return this.exportCersaiCsv();
+ }
+
+ // ---- helpers ----
+ private dateTag(): string {
+ const d = new Date();
+ return `${String(d.getDate()).padStart(2, '0')}${String(d.getMonth() + 1).padStart(2, '0')}${d.getFullYear()}`;
+ }
+ private saveCsv(rows: string[][], filename: string): void {
+ const escape = (v: string): string => {
+ const s = (v || '').replace(/"/g, '""');
+ if (s.includes(',') || s.includes('"') || s.includes('\n')) return `"${s}"`;
+ return s;
+ };
+ const csv = rows.map((r) => r.map(escape).join(',')).join('\n') + '\n';
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ URL.revokeObjectURL(url);
+ }
+}
diff --git a/src/app/home/dashboard/crar-dialog/crar-dialog.component.html b/src/app/home/dashboard/crar-dialog/crar-dialog.component.html
index ed3276aa1..d17105661 100644
--- a/src/app/home/dashboard/crar-dialog/crar-dialog.component.html
+++ b/src/app/home/dashboard/crar-dialog/crar-dialog.component.html
@@ -22,7 +22,13 @@
Q3 FY 2025-26
Reporting Quarter
-