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>
359 lines
9.6 KiB
TypeScript
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);
|
|
}
|
|
}
|