Files
cbsnexora/src/app/deaf/deaf.component.ts
T
AdityaandClaude Opus 4.7 12bfc81219 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>
2026-04-29 18:59:50 +05:30

359 lines
9.6 KiB
TypeScript

/**
* 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);
}
}