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