/** * 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, OnInit } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatDialog } from '@angular/material/dialog'; import { MatTooltip } from '@angular/material/tooltip'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; import { NeftRtgsEntryComponent } from '../neft-rtgs-entry/neft-rtgs-entry.component'; export type PaymentType = 'NEFT' | 'RTGS'; export type ProcessingState = 'UPLOADED' | 'VALIDATED' | 'SENT' | 'SETTLED' | 'PENDING' | 'RETURNED'; export interface NeftRtgsTxn { batchId: string; instructionId: string; utrNumber: string; remitterAcc: string; remitterBranch: string; remitterUser: string; remitterChannel: 'BRANCH' | 'MOBILE'; beneAcc: string; beneIfsc: string; beneName: string; beneBank: string; paymentType: PaymentType; amount: number; valueDate: string; // YYYY-MM-DD purposeCode: string; leiCode?: string; processingState: ProcessingState; returnCode?: string; returnReason?: string; sentTimestamp: string; // YYYY-MM-DD HH:mm:ss } type ReportTab = 'trends' | 'bank' | 'date' | 'pending' | 'channel' | 'user'; interface BankRow { bank: string; count: number; amount: number; settled: number; pending: number; returned: number; } interface DateRow { date: string; count: number; amount: number; neft: number; rtgs: number; } interface UserRow { user: string; count: number; amount: number; branch: string; } interface ChannelRow { channel: 'BRANCH' | 'MOBILE'; count: number; amount: number; share: number; } @Component({ selector: 'mifosx-neft-rtgs-report', templateUrl: './neft-rtgs-report.component.html', styleUrls: ['./neft-rtgs-report.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, FormsModule, FaIconComponent, MatTooltip ] }) export class NeftRtgsReportComponent implements OnInit { private dialog = inject(MatDialog); /** Active tab */ activeTab: ReportTab = 'trends'; /** Search / filter */ search = ''; typeFilter: '' | PaymentType = ''; stateFilter: '' | ProcessingState = ''; /** All transactions (mock data — replace with API). */ txns: NeftRtgsTxn[] = []; /** Tab labels */ readonly tabs: { id: ReportTab; label: string; icon: string }[] = [ { id: 'trends', label: 'Trends', icon: 'chart-line' }, { id: 'bank', label: 'Bank-wise', icon: 'university' }, { id: 'date', label: 'Date-wise', icon: 'calendar-alt' }, { id: 'pending', label: 'Pending / Pass / Return', icon: 'tasks' }, { id: 'channel', label: 'Branch / Mobile', icon: 'mobile-alt' }, { id: 'user', label: 'User-wise', icon: 'user-tie' } ]; ngOnInit(): void { this.txns = this.seedTransactions(); } // ───────────────────── KPIs ───────────────────── get totalCount(): number { return this.txns.length; } get totalAmount(): number { return this.txns.reduce((s, t) => s + t.amount, 0); } get settledCount(): number { return this.txns.filter((t) => t.processingState === 'SETTLED').length; } get pendingCount(): number { return this.txns.filter((t) => [ 'UPLOADED', 'VALIDATED', 'SENT', 'PENDING' ].includes(t.processingState)).length; } get returnedCount(): number { return this.txns.filter((t) => t.processingState === 'RETURNED').length; } get neftCount(): number { return this.txns.filter((t) => t.paymentType === 'NEFT').length; } get rtgsCount(): number { return this.txns.filter((t) => t.paymentType === 'RTGS').length; } get neftAmount(): number { return this.txns.filter((t) => t.paymentType === 'NEFT').reduce((s, t) => s + t.amount, 0); } get rtgsAmount(): number { return this.txns.filter((t) => t.paymentType === 'RTGS').reduce((s, t) => s + t.amount, 0); } get successRate(): number { if (this.totalCount === 0) return 0; return (this.settledCount / this.totalCount) * 100; } get avgTicket(): number { if (this.totalCount === 0) return 0; return this.totalAmount / this.totalCount; } // ───────────────────── Filtered list ───────────────────── get filteredTxns(): NeftRtgsTxn[] { const q = this.search.trim().toLowerCase(); return this.txns.filter((t) => { if (this.typeFilter && t.paymentType !== this.typeFilter) return false; if (this.stateFilter && t.processingState !== this.stateFilter) return false; if (!q) return true; return ( t.utrNumber.toLowerCase().includes(q) || t.batchId.toLowerCase().includes(q) || t.instructionId.toLowerCase().includes(q) || t.beneAcc.toLowerCase().includes(q) || t.beneIfsc.toLowerCase().includes(q) || t.beneName.toLowerCase().includes(q) || t.remitterAcc.toLowerCase().includes(q) ); }); } // ───────────────────── Tab-specific aggregations ───────────────────── get bankRows(): BankRow[] { const map = new Map(); for (const t of this.txns) { const key = t.beneBank; if (!map.has(key)) { map.set(key, { bank: key, count: 0, amount: 0, settled: 0, pending: 0, returned: 0 }); } const r = map.get(key)!; r.count++; r.amount += t.amount; if (t.processingState === 'SETTLED') r.settled++; else if (t.processingState === 'RETURNED') r.returned++; else r.pending++; } return Array.from(map.values()).sort((a, b) => b.amount - a.amount); } get dateRows(): DateRow[] { const map = new Map(); for (const t of this.txns) { if (!map.has(t.valueDate)) { map.set(t.valueDate, { date: t.valueDate, count: 0, amount: 0, neft: 0, rtgs: 0 }); } const r = map.get(t.valueDate)!; r.count++; r.amount += t.amount; if (t.paymentType === 'NEFT') r.neft++; else r.rtgs++; } return Array.from(map.values()).sort((a, b) => (a.date < b.date ? 1 : -1)); } get userRows(): UserRow[] { const map = new Map(); for (const t of this.txns) { const key = `${t.remitterUser}|${t.remitterBranch}`; if (!map.has(key)) { map.set(key, { user: t.remitterUser, branch: t.remitterBranch, count: 0, amount: 0 }); } const r = map.get(key)!; r.count++; r.amount += t.amount; } return Array.from(map.values()).sort((a, b) => b.amount - a.amount); } get channelRows(): ChannelRow[] { const total = this.totalAmount || 1; const map = new Map<'BRANCH' | 'MOBILE', ChannelRow>(); for (const t of this.txns) { if (!map.has(t.remitterChannel)) { map.set(t.remitterChannel, { channel: t.remitterChannel, count: 0, amount: 0, share: 0 }); } const r = map.get(t.remitterChannel)!; r.count++; r.amount += t.amount; } map.forEach((r) => (r.share = (r.amount / total) * 100)); return Array.from(map.values()); } /** Trend buckets — last 7 value-dates */ get trendBuckets(): DateRow[] { return this.dateRows.slice(0, 7).reverse(); } /** Max amount across trend buckets (for bar chart scaling). */ get trendMaxAmount(): number { const buckets = this.trendBuckets; if (!buckets.length) return 1; return Math.max(...buckets.map((b) => b.amount)); } /** Return-code breakdown for Pending/Pass/Return tab */ get returnCodeRows(): { code: string; reason: string; count: number; amount: number }[] { const map = new Map(); for (const t of this.txns) { if (t.processingState !== 'RETURNED' || !t.returnCode) continue; if (!map.has(t.returnCode)) { map.set(t.returnCode, { code: t.returnCode, reason: t.returnReason || '', count: 0, amount: 0 }); } const r = map.get(t.returnCode)!; r.count++; r.amount += t.amount; } return Array.from(map.values()).sort((a, b) => b.count - a.count); } // ───────────────────── Actions ───────────────────── showTab(tab: ReportTab): void { this.activeTab = tab; } /** Floating action — open NEFT/RTGS entry in a non-dismissible dialog. */ openInitiateDialog(): void { const ref = this.dialog.open(NeftRtgsEntryComponent, { width: '880px', maxWidth: '94vw', maxHeight: '88vh', disableClose: true, autoFocus: false, restoreFocus: true, panelClass: 'nr-entry-dialog', backdropClass: 'nr-entry-backdrop' }); ref.afterClosed().subscribe((result) => { if (result && result.submitted && result.payload) { this.txns = [ this.toTxn(result.payload), ...this.txns ]; } }); } exportCsv(): void { const headers = [ 'Batch ID', 'Instruction ID', 'UTR Number', 'Remitter Acc', 'Bene Acc No', 'Bene IFSC', 'Bene Name', 'Payment Type', 'Amount', 'Value Date', 'Purpose Code', 'LEI Code', 'Processing State', 'Return Code', 'Sent Timestamp' ]; const rows: string[][] = [headers]; for (const t of this.filteredTxns) { rows.push([ t.batchId, t.instructionId, t.utrNumber, t.remitterAcc, t.beneAcc, t.beneIfsc, t.beneName, t.paymentType, t.amount.toFixed(2), t.valueDate, t.purposeCode, t.leiCode || '', t.processingState, t.returnCode || '', t.sentTimestamp ]); } const escape = (v: string): string => { const s = (v || '').replace(/"/g, '""'); return /[,"\n]/.test(s) ? `"${s}"` : 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 = `NEFT_RTGS_Report_${new Date().toISOString().slice(0, 10)}.csv`; a.click(); URL.revokeObjectURL(url); } clearFilters(): void { this.search = ''; this.typeFilter = ''; this.stateFilter = ''; } // ───────────────────── Helpers ───────────────────── stateColor(state: ProcessingState): string { switch (state) { case 'SETTLED': return 'green'; case 'RETURNED': return 'red'; case 'SENT': return 'blue'; case 'VALIDATED': return 'teal'; case 'UPLOADED': return 'amber'; case 'PENDING': return 'amber'; } } trendBarHeight(amount: number): number { return Math.max(6, Math.round((amount / this.trendMaxAmount) * 100)); } /** Convert a submitted entry-form payload into a synthetic transaction row. */ private toTxn(p: any): NeftRtgsTxn { const valueDate = p.valueDate ? new Date(p.valueDate).toISOString().slice(0, 10) : new Date().toISOString().slice(0, 10); const ts = new Date(); const pad = (n: number) => String(n).padStart(2, '0'); return { batchId: `H2H-${valueDate.replace(/-/g, '')}-${pad(Math.floor(Math.random() * 99) + 1)}`, instructionId: 'ERP' + Date.now().toString().slice(-8), utrNumber: 'KKBK' + Date.now().toString().slice(-11), remitterAcc: p.senderAccount || '', remitterBranch: 'Main Branch', remitterUser: 'Current User', remitterChannel: 'BRANCH', beneAcc: p.beneficiaryAccount || '', beneIfsc: (p.ifscCode || '').toUpperCase(), beneName: p.beneficiaryName || '', beneBank: this.bankFromIfsc(p.ifscCode || ''), paymentType: p.transactionType, amount: Number(p.amount) || 0, valueDate, purposeCode: this.purposeCodeFor(p.purposeOfRemittance), leiCode: Number(p.amount) >= 500000000 ? '549300ABCDEF1234' : undefined, processingState: 'UPLOADED', sentTimestamp: ts.getFullYear() + '-' + pad(ts.getMonth() + 1) + '-' + pad(ts.getDate()) + ' ' + pad(ts.getHours()) + ':' + pad(ts.getMinutes()) + ':' + pad(ts.getSeconds()) }; } private bankFromIfsc(ifsc: string): string { const code = ifsc.slice(0, 4).toUpperCase(); const map: Record = { HDFC: 'HDFC Bank', ICIC: 'ICICI Bank', SBIN: 'State Bank of India', KKBK: 'Kotak Mahindra Bank', AXIS: 'Axis Bank', UTIB: 'Axis Bank', PUNB: 'Punjab National Bank', BARB: 'Bank of Baroda', CNRB: 'Canara Bank', YESB: 'YES Bank', IDFB: 'IDFC FIRST Bank' }; return map[code] || code + ' Bank'; } private purposeCodeFor(p: string): string { const map: Record = { CAPITAL_TRANSFER: 'P0102', FAMILY_MAINTENANCE: 'P1301', TRADE_SETTLEMENT: 'P0801', SALARY_PAYMENT: 'P1006', LOAN_REPAYMENT: 'P0103', TAX_PAYMENT: 'P1099', VENDOR_PAYMENT: 'P0802', INVESTMENT: 'P0202', GIFT: 'P1302', OTHER: 'P0099' }; return map[p] || 'P0099'; } // ───────────────────── Mock seed data ───────────────────── private seedTransactions(): NeftRtgsTxn[] { const today = new Date(); const dt = (offset: number, h = 10, m = 0): string => { const d = new Date(today); d.setDate(d.getDate() - offset); const pad = (n: number) => String(n).padStart(2, '0'); return ( d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(h) + ':' + pad(m) + ':00' ); }; const vd = (offset: number): string => dt(offset).slice(0, 10); return [ { batchId: 'H2H-20260326-01', instructionId: 'ERP77889922', utrNumber: 'KKBKR5202608511', remitterAcc: '50200012345678', remitterBranch: 'Mumbai — BKC', remitterUser: 'Anita Desai', remitterChannel: 'BRANCH', beneAcc: '91000222333444', beneIfsc: 'HDFC0001234', beneName: 'Global Logistics Ltd', beneBank: 'HDFC Bank', paymentType: 'RTGS', amount: 550000, valueDate: vd(0), purposeCode: 'P0102', leiCode: '549300ABCDEF1234', processingState: 'SETTLED', sentTimestamp: dt(0, 14, 5) }, { batchId: 'H2H-20260326-01', instructionId: 'ERP77889923', utrNumber: 'HDFCR5202608712', remitterAcc: '50200012345678', remitterBranch: 'Mumbai — BKC', remitterUser: 'Anita Desai', remitterChannel: 'BRANCH', beneAcc: '345678901234', beneIfsc: 'ICIC0000567', beneName: 'Bluestone Trading Co.', beneBank: 'ICICI Bank', paymentType: 'NEFT', amount: 78500, valueDate: vd(0), purposeCode: 'P0802', processingState: 'SENT', sentTimestamp: dt(0, 14, 6) }, { batchId: 'H2H-20260325-04', instructionId: 'ERP77889811', utrNumber: 'SBINR5202608902', remitterAcc: '50200099887766', remitterBranch: 'Bengaluru — MG Road', remitterUser: 'Karthik Rao', remitterChannel: 'MOBILE', beneAcc: '912345678901', beneIfsc: 'SBIN0011223', beneName: 'Anita Verma', beneBank: 'State Bank of India', paymentType: 'NEFT', amount: 15000, valueDate: vd(1), purposeCode: 'P1301', processingState: 'SETTLED', sentTimestamp: dt(1, 11, 22) }, { batchId: 'H2H-20260325-04', instructionId: 'ERP77889812', utrNumber: 'AXISR5202608811', remitterAcc: '50200099887766', remitterBranch: 'Bengaluru — MG Road', remitterUser: 'Karthik Rao', remitterChannel: 'MOBILE', beneAcc: '6789012345', beneIfsc: 'UTIB0000456', beneName: 'Rakesh Industries', beneBank: 'Axis Bank', paymentType: 'RTGS', amount: 1250000, valueDate: vd(1), purposeCode: 'P0801', leiCode: '549300XYZAB7890', processingState: 'RETURNED', returnCode: 'R01', returnReason: 'Insufficient Funds', sentTimestamp: dt(1, 11, 25) }, { batchId: 'H2H-20260324-02', instructionId: 'ERP77889701', utrNumber: 'PUNBR5202608619', remitterAcc: '50100221122334', remitterBranch: 'Delhi — Connaught', remitterUser: 'Sushma Iyer', remitterChannel: 'BRANCH', beneAcc: '50100998877665', beneIfsc: 'PUNB0123400', beneName: 'Madhavi Singh', beneBank: 'Punjab National Bank', paymentType: 'NEFT', amount: 42000, valueDate: vd(2), purposeCode: 'P1006', processingState: 'SETTLED', sentTimestamp: dt(2, 9, 35) }, { batchId: 'H2H-20260324-02', instructionId: 'ERP77889702', utrNumber: 'YESBR5202608520', remitterAcc: '50100221122334', remitterBranch: 'Delhi — Connaught', remitterUser: 'Sushma Iyer', remitterChannel: 'BRANCH', beneAcc: '11223344556677', beneIfsc: 'YESB0000123', beneName: 'Sigma Constructions', beneBank: 'YES Bank', paymentType: 'RTGS', amount: 825000, valueDate: vd(2), purposeCode: 'P0102', processingState: 'PENDING', sentTimestamp: dt(2, 9, 38) }, { batchId: 'H2H-20260323-07', instructionId: 'ERP77889601', utrNumber: 'CNRBR5202608417', remitterAcc: '50100887766554', remitterBranch: 'Chennai — T. Nagar', remitterUser: 'Ramesh Krishnan', remitterChannel: 'MOBILE', beneAcc: '99887766554433', beneIfsc: 'CNRB0001890', beneName: 'Padma Rao', beneBank: 'Canara Bank', paymentType: 'NEFT', amount: 9800, valueDate: vd(3), purposeCode: 'P1302', processingState: 'SETTLED', sentTimestamp: dt(3, 16, 15) }, { batchId: 'H2H-20260323-07', instructionId: 'ERP77889602', utrNumber: 'BARBR5202608318', remitterAcc: '50100887766554', remitterBranch: 'Chennai — T. Nagar', remitterUser: 'Ramesh Krishnan', remitterChannel: 'BRANCH', beneAcc: '50100123456987', beneIfsc: 'BARB0VJVATA', beneName: 'Vijaya Traders', beneBank: 'Bank of Baroda', paymentType: 'NEFT', amount: 125000, valueDate: vd(3), purposeCode: 'P0802', processingState: 'VALIDATED', sentTimestamp: dt(3, 16, 20) }, { batchId: 'H2H-20260322-03', instructionId: 'ERP77889501', utrNumber: 'IDFBR5202608215', remitterAcc: '50200445566778', remitterBranch: 'Pune — Koregaon Park', remitterUser: 'Vikram Joshi', remitterChannel: 'BRANCH', beneAcc: '70010023456789', beneIfsc: 'IDFB0040101', beneName: 'Pinnacle Realty Pvt Ltd', beneBank: 'IDFC FIRST Bank', paymentType: 'RTGS', amount: 3450000, valueDate: vd(4), purposeCode: 'P0202', leiCode: '549300QRSTUV5678', processingState: 'SETTLED', sentTimestamp: dt(4, 12, 45) }, { batchId: 'H2H-20260322-03', instructionId: 'ERP77889502', utrNumber: 'HDFCR5202608116', remitterAcc: '50200445566778', remitterBranch: 'Pune — Koregaon Park', remitterUser: 'Vikram Joshi', remitterChannel: 'MOBILE', beneAcc: '50100654321789', beneIfsc: 'HDFC0009876', beneName: 'Neha Kapoor', beneBank: 'HDFC Bank', paymentType: 'NEFT', amount: 25000, valueDate: vd(4), purposeCode: 'P1301', processingState: 'SETTLED', sentTimestamp: dt(4, 12, 50) }, { batchId: 'H2H-20260321-05', instructionId: 'ERP77889411', utrNumber: 'KKBKR5202608013', remitterAcc: '50200778899001', remitterBranch: 'Hyderabad — Banjara', remitterUser: 'Lakshmi Naidu', remitterChannel: 'BRANCH', beneAcc: '12345678900011', beneIfsc: 'KKBK0001899', beneName: 'Aarav Enterprises', beneBank: 'Kotak Mahindra Bank', paymentType: 'NEFT', amount: 67000, valueDate: vd(5), purposeCode: 'P0802', processingState: 'RETURNED', returnCode: 'R03', returnReason: 'Account Closed', sentTimestamp: dt(5, 10, 5) }, { batchId: 'H2H-20260321-05', instructionId: 'ERP77889412', utrNumber: 'SBINR5202607914', remitterAcc: '50200778899001', remitterBranch: 'Hyderabad — Banjara', remitterUser: 'Lakshmi Naidu', remitterChannel: 'BRANCH', beneAcc: '50100445566778', beneIfsc: 'SBIN0008765', beneName: 'Spectra Components', beneBank: 'State Bank of India', paymentType: 'RTGS', amount: 980000, valueDate: vd(5), purposeCode: 'P0801', leiCode: '549300LMNOPQ2468', processingState: 'SETTLED', sentTimestamp: dt(5, 10, 10) }, { batchId: 'H2H-20260320-06', instructionId: 'ERP77889301', utrNumber: 'ICICR5202607812', remitterAcc: '50100334455667', remitterBranch: 'Kolkata — Park Street', remitterUser: 'Arjun Banerjee', remitterChannel: 'MOBILE', beneAcc: '88990011223344', beneIfsc: 'ICIC0001234', beneName: 'Eastern Logistics', beneBank: 'ICICI Bank', paymentType: 'NEFT', amount: 188000, valueDate: vd(6), purposeCode: 'P0802', processingState: 'SETTLED', sentTimestamp: dt(6, 14, 30) }, { batchId: 'H2H-20260320-06', instructionId: 'ERP77889302', utrNumber: 'AXISR5202607711', remitterAcc: '50100334455667', remitterBranch: 'Kolkata — Park Street', remitterUser: 'Arjun Banerjee', remitterChannel: 'BRANCH', beneAcc: '50200998877665', beneIfsc: 'UTIB0001234', beneName: 'Indrani Sen', beneBank: 'Axis Bank', paymentType: 'NEFT', amount: 5500, valueDate: vd(6), purposeCode: 'P1302', processingState: 'SETTLED', sentTimestamp: dt(6, 14, 32) } ]; } }