feat: add 11 banking compliance & accounting report modules

Adds dialog components and feature modules for RBI/banking compliance,
KYC, and accounting reports. Wires them into the shell menu (RBI,
General Ledger, CKYC Mgmt, Others, Insurance) and dashboard cards.

Dialog components (lazy-loaded MatDialog):
- PSL Returns (Form III) — dashboard card + RBI menu
- ALM Statement of Structural Liquidity (Form ALM-1) — RBI menu
- CIMS / XBRL Returns (OSS-2 / PSA / RAL / RCP-UCB) — RBI menu, shared
  dialog with form-pill switcher
- CIC DQR (CIBIL / Experian / Equifax / CRIF, 72-col TUDF) — Others menu
  + dashboard card
- CKYC Compliance Reports (Re-KYC / Risk Review / CERSAI) — CKYC Mgmt
- Accounting Reports (P&L / Balance Sheet / Comparative B/S / GL DR/CR
  / Misc Expense Ledger) — General Ledger menu

Feature modules (Route.withShell):
- DEAF (/deaf): Form I, Form II, Annex 4, accounts register
- DICGC (/dicgc): Slab Report, SAHAY Depositor List
- FIU-IND (/fiu): FINnet 2.0 Statement of Financial Transactions
- SFT (/sft): Income Tax Section 285BA SFT
- CKYC Reports (/ckyc/ckyc-reports): standalone dashboard

UI:
- Theme-colored icons on 22 wired menu links across 5 menus —
  visually distinguishes wired (colored) from placeholder (gray) links.
- Backdrop classes per dialog in main.scss.

All using dummy data (Bharat Urban Bank Ltd, Karthik Kamath as Principal
Officer, dummy customer names) — no real bank or customer references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-04-29 18:59:50 +05:30
co-authored by Claude Opus 4.7
parent 634003a75c
commit 12bfc81219
61 changed files with 22207 additions and 725 deletions
+475
View File
@@ -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'
}
];
+29
View File
@@ -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 {}
+416
View File
@@ -0,0 +1,416 @@
<!--
Copyright since 2025 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="deaf-root">
<!-- HEADER -->
<div class="deaf-head">
<div class="deaf-head-left">
<fa-icon icon="university" class="deaf-head-ic"></fa-icon>
<div>
<h2 class="deaf-head-t">Depositor Education and Awareness Fund (DEAF)</h2>
<div class="deaf-head-s">RBI Master Direction on DEAF, 2014 &middot; Returns &amp; refund register</div>
</div>
</div>
<div class="deaf-head-meta">
<div>
<span class="deaf-meta-l">Bank DEAF Code</span><span class="deaf-meta-v">{{ formII.bankDeafCode }}</span>
</div>
<div>
<span class="deaf-meta-l">Sponsor Bank</span><span class="deaf-meta-v">{{ formII.sponsorBankName }}</span>
</div>
</div>
</div>
<!-- TABS -->
<div class="deaf-tabs">
<button class="deaf-tab" [class.active]="activeTab === 'dashboard'" (click)="showTab('dashboard')">
<fa-icon icon="chart-pie"></fa-icon> Dashboard
</button>
<button class="deaf-tab" [class.active]="activeTab === 'form1'" (click)="showTab('form1')">
<fa-icon icon="file-invoice"></fa-icon> Form I (Transfer Return)
</button>
<button class="deaf-tab" [class.active]="activeTab === 'form2'" (click)="showTab('form2')">
<fa-icon icon="file-invoice"></fa-icon> Form II (Position with Fund)
</button>
<button class="deaf-tab" [class.active]="activeTab === 'refund'" (click)="showTab('refund')">
<fa-icon icon="hand-holding-usd"></fa-icon> Annex 4 (Refund Claim)
</button>
<button class="deaf-tab" [class.active]="activeTab === 'eligible'" (click)="showTab('eligible')">
<fa-icon icon="users"></fa-icon> Accounts Register
</button>
</div>
<!-- DASHBOARD -->
@if (activeTab === 'dashboard') {
<div class="deaf-section">
<div class="deaf-kpis">
<div class="deaf-kpi deaf-kpi--blue">
<div class="deaf-kpi-ic"><fa-icon icon="piggy-bank"></fa-icon></div>
<div>
<div class="deaf-kpi-n">&#8377;{{ totalWithFund | number: '1.2-2' }}</div>
<div class="deaf-kpi-l">Total with Fund</div>
<div class="deaf-kpi-sub">{{ totalAccountsWithFund | number }} accounts</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--teal">
<div class="deaf-kpi-ic"><fa-icon icon="upload"></fa-icon></div>
<div>
<div class="deaf-kpi-n">&#8377;{{ transferredThisMonth | number: '1.2-2' }}</div>
<div class="deaf-kpi-l">Transferred this Month</div>
<div class="deaf-kpi-sub">{{ accountsTransferredThisMonth | number }} accounts</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--green">
<div class="deaf-kpi-ic"><fa-icon icon="hand-holding-usd"></fa-icon></div>
<div>
<div class="deaf-kpi-n">&#8377;{{ refundedThisCycle | number: '1.2-2' }}</div>
<div class="deaf-kpi-l">Claims Paid (refunded)</div>
<div class="deaf-kpi-sub">{{ refundAccountsThisCycle | number }} accounts</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--amber">
<div class="deaf-kpi-ic"><fa-icon icon="exclamation-triangle"></fa-icon></div>
<div>
<div class="deaf-kpi-n">{{ accountsCrossing10Y | number }}</div>
<div class="deaf-kpi-l">Crossing 10y this Cycle</div>
<div class="deaf-kpi-sub">eligible to transfer</div>
</div>
</div>
<div class="deaf-kpi deaf-kpi--red">
<div class="deaf-kpi-ic"><fa-icon icon="clock"></fa-icon></div>
<div>
<div class="deaf-kpi-n">{{ pendingClaims | number }}</div>
<div class="deaf-kpi-l">Refund Claims Pending</div>
<div class="deaf-kpi-sub">awaiting RBI settlement</div>
</div>
</div>
</div>
<!-- Compliance summary -->
<div class="deaf-compliance">
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="check-circle" class="dc-ok"></fa-icon><span>Form I — Online Submission</span>
</div>
<div class="dc-meta">Last filed: <strong>15 Jan 2026</strong> &middot; Cycle: {{ formI.monthYear }}</div>
<div class="dc-meta">Next due: <strong>15 Feb 2026</strong></div>
<button class="dc-btn" (click)="showTab('form1')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="check-circle" class="dc-ok"></fa-icon><span>Form II — Physical Hardcopy</span>
</div>
<div class="dc-meta">Last filed: <strong>15 Jan 2026</strong> &middot; Cycle: {{ formII.monthYear }}</div>
<div class="dc-meta">Next due: <strong>15 Feb 2026</strong></div>
<button class="dc-btn" (click)="showTab('form2')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
<div class="dc-card">
<div class="dc-card-h">
<fa-icon icon="hand-holding-usd" class="dc-info"></fa-icon><span>Annex 4 — Refund Claim</span>
</div>
<div class="dc-meta">
Cycle: <strong>{{ refund.month }} {{ refund.year }}</strong>
</div>
<div class="dc-meta">
Pending claims: <strong>{{ pendingClaims }}</strong>
</div>
<button class="dc-btn" (click)="showTab('refund')"><fa-icon icon="external-link-alt"></fa-icon> Open</button>
</div>
</div>
</div>
}
<!-- FORM I / FORM II — shared template -->
@if (activeTab === 'form1' || activeTab === 'form2') {
@let _form = activeTab === 'form1' ? formI : formII;
<div class="deaf-section">
<div class="deaf-form-meta">
<div class="deaf-form-title">
@if (activeTab === 'form1') {
<h3>Form I — Monthly Return of Unclaimed Deposits Transferred to DEAF</h3>
<p>Filed online with RBI by 15th of the succeeding month</p>
} @else {
<h3>Form II — Position with the Fund (snapshot)</h3>
<p>Original hardcopy submitted to RBI by 15th of the succeeding month</p>
}
</div>
<button class="deaf-btn deaf-btn-xls" (click)="activeTab === 'form1' ? exportFormI() : exportFormII()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
<div class="deaf-meta-grid">
<div>
<label>Name of the Bank</label><span>{{ _form.bankName }}</span>
</div>
<div>
<label>DEAF Code (RBI)</label><span>{{ _form.bankDeafCode }}</span>
</div>
<div>
<label>Remitted via Sponsor</label><span>{{ _form.remittedThroughSponsor === 'Y' ? 'Yes' : 'No' }}</span>
</div>
<div>
<label>Sponsor Bank</label><span>{{ _form.sponsorBankName }}</span>
</div>
<div>
<label>Month / Year</label><span>{{ _form.monthYear }}</span>
</div>
<div>
<label>Date of Transfer</label><span>{{ _form.dateOfTransfer }}</span>
</div>
</div>
<div class="deaf-tbl-wrap">
<table class="deaf-tbl">
<thead>
<tr class="hd-1">
<th rowspan="2" class="ta-c">Sr.No</th>
<th rowspan="2">Particulars</th>
<th colspan="2" class="ta-c">Interest Bearing Deposits (a)</th>
<th colspan="2" class="ta-c">Non-Interest Bearing Deposits (b)</th>
<th colspan="2" class="ta-c">Other Credits (c)</th>
<th colspan="2" class="ta-c">Total (d) = (a)+(b)+(c)</th>
</tr>
<tr class="hd-2">
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
</tr>
</thead>
<tbody>
@for (r of _form.rows; track r.sl) {
<tr [class]="amountClass(r)">
<td class="ta-c">{{ r.sl }}</td>
<td>{{ r.particulars }}</td>
<td class="ta-r">{{ r.ibdAccounts | number }}</td>
<td class="ta-r mono">{{ r.ibdAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.nibdAccounts | number }}</td>
<td class="ta-r mono">{{ r.nibdAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.ocAccounts | number }}</td>
<td class="ta-r mono">{{ r.ocAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.totalAccounts | number }}</td>
<td class="ta-r mono">{{ r.totalAmount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
<div class="deaf-sign">
<div class="deaf-sign-block">
<label>Authorised Officer</label>
<div class="sign-name">{{ _form.officer }}</div>
<div class="sign-desg">{{ _form.officerDesignation }}</div>
</div>
<div class="deaf-sign-block">
<label>Concurrent Auditor</label>
<div class="sign-name">{{ _form.auditor }}</div>
<div class="sign-desg">{{ _form.auditorAddress }}</div>
</div>
</div>
</div>
}
<!-- ANNEX 4 — REFUND CLAIM -->
@if (activeTab === 'refund') {
<div class="deaf-section">
<div class="deaf-form-meta">
<div class="deaf-form-title">
<h3>Annex 4 — Monthly Return claiming refund from DEA Fund</h3>
<p>Submitted by 15th of succeeding month for the cycle to which the claim pertains</p>
</div>
<button class="deaf-btn deaf-btn-xls" (click)="exportRefund()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
<div class="deaf-meta-grid">
<div>
<label>Name of the Bank</label><span>{{ refund.bankName }}</span>
</div>
<div>
<label>DEAF Code (RBI)</label><span>{{ refund.bankDeafCode }}</span>
</div>
<div>
<label>Current A/c with RBI</label><span class="mono">{{ refund.currentAcWithRbi }}</span>
</div>
<div>
<label>Maintained with</label><span>{{ refund.sponsorBank }}</span>
</div>
<div>
<label>Month / Year</label><span>{{ refund.month }} {{ refund.year }}</span>
</div>
</div>
<div class="deaf-tbl-wrap">
<table class="deaf-tbl">
<thead>
<tr class="hd-1">
<th rowspan="2" class="ta-c">Sr.No</th>
<th colspan="3" class="ta-c">Interest Bearing Deposits</th>
<th colspan="2" class="ta-c">Non-Interest Bearing Deposits</th>
<th colspan="2" class="ta-c">Other Credits</th>
<th colspan="2" class="ta-c">Grand Total</th>
</tr>
<tr class="hd-2">
<th class="ta-r">Principal</th>
<th class="ta-r">Interest</th>
<th class="ta-r">Total</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
<th class="ta-r">No. of A/c</th>
<th class="ta-r">Amount</th>
</tr>
</thead>
<tbody>
@for (r of refund.rows; track r.slNo) {
<tr>
<td class="ta-c">{{ r.slNo }}</td>
<td class="ta-r mono">{{ r.ibdPrincipal | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ r.ibdInterest | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ r.ibdTotal | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.nibdAccounts | number }}</td>
<td class="ta-r mono">{{ r.nibdAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.ocAccounts | number }}</td>
<td class="ta-r mono">{{ r.ocAmount | number: '1.2-2' }}</td>
<td class="ta-r">{{ r.grandTotalAccounts | number }}</td>
<td class="ta-r mono">{{ r.grandTotalAmount | number: '1.2-2' }}</td>
</tr>
}
</tbody>
</table>
</div>
<div class="deaf-note">
<fa-icon icon="info-circle"></fa-icon>
<span
><strong>Note:</strong> 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.</span
>
</div>
<div class="deaf-sign">
<div class="deaf-sign-block">
<label>First Authorised Signatory</label>
<div class="sign-name">{{ refund.firstSignatory }}</div>
<div class="sign-desg">{{ refund.firstDesignation }}</div>
</div>
<div class="deaf-sign-block">
<label>Second Authorised Signatory</label>
<div class="sign-name">{{ refund.secondSignatory }}</div>
<div class="sign-desg">{{ refund.secondDesignation }}</div>
</div>
<div class="deaf-sign-block">
<label>Concurrent Auditor</label>
<div class="sign-name">{{ refund.auditor }}</div>
</div>
</div>
</div>
}
<!-- ELIGIBLE / TRANSFERRED ACCOUNTS REGISTER -->
@if (activeTab === 'eligible') {
<div class="deaf-section">
<div class="deaf-form-meta">
<div class="deaf-form-title">
<h3>Accounts Register</h3>
<p>Inoperative / unclaimed accounts crossing 10y, transferred, claimed and refunded</p>
</div>
<button class="deaf-btn deaf-btn-xls" (click)="exportEligible()">
<fa-icon icon="file-excel"></fa-icon> Download Excel
</button>
</div>
<div class="deaf-filters">
<div class="deaf-filter">
<label>Status</label>
<select class="deaf-inp" [(ngModel)]="filterStatus">
<option value="">All</option>
<option value="eligible-this-month">Eligible (≥10y)</option>
<option value="transferred">Transferred</option>
<option value="claim-pending">Claim Pending</option>
<option value="refunded">Refunded</option>
</select>
</div>
<div class="deaf-filter">
<label>Bucket</label>
<select class="deaf-inp" [(ngModel)]="filterBucket">
<option value="">All</option>
<option value="IBD">Interest Bearing</option>
<option value="NIBD">Non-Interest Bearing</option>
<option value="OC">Other Credits</option>
</select>
</div>
<div class="deaf-filter">
<label>Branch</label>
<input class="deaf-inp" type="text" [(ngModel)]="filterBranch" placeholder="Branch" />
</div>
<button class="deaf-btn deaf-btn-g" (click)="resetFilters()">Reset</button>
</div>
<div class="deaf-tbl-wrap">
<table class="deaf-tbl">
<thead>
<tr>
<th>A/c No</th>
<th>Customer</th>
<th class="ta-c">Type</th>
<th>Branch</th>
<th>Opened</th>
<th>Last Txn</th>
<th class="ta-c">Inactive</th>
<th class="ta-c">Bucket</th>
<th class="ta-r">Principal</th>
<th class="ta-r">Interest</th>
<th class="ta-r">Total</th>
<th class="ta-c">Status</th>
</tr>
</thead>
<tbody>
@for (a of filteredAccounts; track a.acNo) {
<tr>
<td class="mono">{{ a.acNo }}</td>
<td>{{ a.customerName }}</td>
<td class="ta-c">{{ a.acType }}</td>
<td>{{ a.branch }}</td>
<td>{{ a.openedDate }}</td>
<td>{{ a.lastTxnDate }}</td>
<td class="ta-c">{{ a.inactiveYears }}y</td>
<td class="ta-c">
<span class="bucket-pill" [class]="'bk-' + a.bucket.toLowerCase()">{{ a.bucket }}</span>
</td>
<td class="ta-r mono">{{ a.principal | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ a.interest | number: '1.2-2' }}</td>
<td class="ta-r mono">{{ a.total | number: '1.2-2' }}</td>
<td class="ta-c">
<span class="status-pill" [class]="'st-' + a.status">{{ statusLabel(a.status) }}</span>
</td>
</tr>
} @empty {
<tr>
<td colspan="12" class="deaf-empty">No accounts match the filters.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
<!-- TOAST -->
<div class="deaf-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
</div>
+672
View File
@@ -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;
}
}
+358
View File
@@ -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);
}
}
+22
View File
@@ -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 {}