feat: add CFCFRMS module, bank menus, mock backend, and dashboard redesign

- CFCFRMS cyber fraud complaint management with dashboard, complaints,
  escalation, report suspect/mule/fraud txn, FMR submission, CFR lookup
- Mock HTTP interceptor with 20 dummy complaints and all endpoints
- Simulator service for I4C/MHA integration testing
- 3-row menu bar with 22 items (7+8+8 layout)
- Mega menus for General Ledger, Sub Ledger, Deposits, Share, Others,
  HRM, RBI with search and grouped columns
- Dropdowns for Cashier, Clearing, NPA, CKYC, Advances, Letter Mgmt,
  Insurance, Digital
- Dashboard redesign with regulatory, financial health, deposits,
  risk/NPA sections using static banking data
- All translations added to en-US.json
- FontAwesome icons registered for all new menu items

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-03-18 18:05:43 +05:30
co-authored by Claude Opus 4.6
parent 0a32d7135e
commit b3a4cce0cf
55 changed files with 9667 additions and 298 deletions
+849
View File
@@ -0,0 +1,849 @@
/**
* 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 { Complaint, ComplaintStatus, DashboardStats, TimelineEvent } from '../models/cfcfrms.models';
// ─── Helper: generate dates relative to now ───
function daysAgo(n: number): string {
const d = new Date();
d.setDate(d.getDate() - n);
return d.toISOString();
}
function hoursAgo(n: number): string {
const d = new Date();
d.setHours(d.getHours() - n);
return d.toISOString();
}
// ─── 20 Realistic Dummy Complaints ───
export const MOCK_COMPLAINTS: Complaint[] = [
{
ticketId: 'CFCFRMS-2024-00001',
complainantName: 'Rajesh Kumar Sharma',
complainantMobile: '9876543210',
complainantEmail: 'rajesh.sharma@gmail.com',
complainantAddress: '42, MG Road, Sector 18, Noida, UP 201301',
fraudAmount: 250000,
fraudType: 'UPI_FRAUD',
fraudDescription:
'Received fake KYC update link via SMS. After clicking, ₹2.5L was debited via multiple UPI transactions within 10 minutes.',
suspectAccountNumber: '50100268347291',
suspectAccountName: 'Vikram Enterprises',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240115-00981',
status: 'RECEIVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: '',
createdAt: daysAgo(0),
updatedAt: daysAgo(0)
},
{
ticketId: 'CFCFRMS-2024-00002',
complainantName: 'Priya Nair',
complainantMobile: '8765432109',
complainantEmail: 'priya.nair@yahoo.com',
complainantAddress: '15, Anna Salai, T. Nagar, Chennai, TN 600017',
fraudAmount: 487500,
fraudType: 'OLX_FRAUD',
fraudDescription:
'Victim was selling a sofa on OLX. Scammer sent a QR code claiming it was for payment. Scanning the QR debited ₹4,87,500.',
suspectAccountNumber: '50100399281746',
suspectAccountName: 'Deepak Trading Co',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240115-01102',
status: 'UNDER_REVIEW',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 312000,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: 'Officer Meena K.',
createdAt: daysAgo(1),
updatedAt: hoursAgo(6)
},
{
ticketId: 'CFCFRMS-2024-00003',
complainantName: 'Amit Patel',
complainantMobile: '7654321098',
complainantEmail: 'amit.patel@outlook.com',
complainantAddress: '78, SG Highway, Bodakdev, Ahmedabad, GJ 380054',
fraudAmount: 150000,
fraudType: 'LOAN_APP_FRAUD',
fraudDescription:
'Downloaded fake loan app from link shared on WhatsApp. App collected bank details and debited ₹1.5L in two transactions.',
suspectAccountNumber: '50100412738956',
suspectAccountName: 'QuickCash Solutions',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'MHA_PORTAL',
acknowledgementNumber: 'ACK-MHA-20240114-00437',
status: 'LIEN_MARKED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 89000,
fundsHeld: 150000,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Lien placed on suspect account. Balance sufficient to cover fraud amount.',
assignedOfficer: 'Officer Suresh R.',
createdAt: daysAgo(3),
updatedAt: daysAgo(1)
},
{
ticketId: 'CFCFRMS-2024-00004',
complainantName: 'Sunita Devi',
complainantMobile: '6543210987',
complainantEmail: '',
complainantAddress: 'Village Rampur, Block Sadar, Varanasi, UP 221002',
fraudAmount: 75000,
fraudType: 'CUSTOMER_CARE_FRAUD',
fraudDescription:
'Called fake customer care number from Google. Scammer installed AnyDesk on victim phone and transferred ₹75,000.',
suspectAccountNumber: '50100528463190',
suspectAccountName: 'Rahul Verma',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240113-00215',
status: 'DEBIT_FROZEN',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 42000,
fundsHeld: 75000,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Debit freeze applied. Full fraud amount held. Account holder notified via registered mobile.',
assignedOfficer: 'Officer Suresh R.',
createdAt: daysAgo(5),
updatedAt: daysAgo(2)
},
{
ticketId: 'CFCFRMS-2024-00005',
complainantName: 'Mohammed Irfan',
complainantMobile: '9988776655',
complainantEmail: 'irfan.m@gmail.com',
complainantAddress: '23, Charminar Road, Hyderabad, TS 500002',
fraudAmount: 890000,
fraudType: 'INVESTMENT_FRAUD',
fraudDescription:
'Lured into fake stock trading app via Telegram group. Invested ₹8.9L over two weeks. Unable to withdraw funds.',
suspectAccountNumber: '50100637291845',
suspectAccountName: 'Global Trade Hub',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240112-00876',
status: 'FUNDS_MOVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 12500,
fundsHeld: 0,
nextHopAccountNumber: '91820034567812',
nextHopBankIfsc: 'SBIN0001234',
actionNote: 'Only ₹12,500 available. Majority funds moved to SBI account. Escalating to SBI.',
assignedOfficer: 'Officer Meena K.',
createdAt: daysAgo(8),
updatedAt: daysAgo(4)
},
{
ticketId: 'CFCFRMS-2024-00006',
complainantName: 'Kavitha Reddy',
complainantMobile: '8877665544',
complainantEmail: 'kavitha.r@hotmail.com',
complainantAddress: '56, Indiranagar, Bangalore, KA 560038',
fraudAmount: 320000,
fraudType: 'PART_TIME_JOB_FRAUD',
fraudDescription:
'Scammed via Telegram part-time job offer. Made initial ₹5,000 "investment" then lured into paying ₹3.2L as "tax" and "processing fee".',
suspectAccountNumber: '50100745829361',
suspectAccountName: 'EarnMore Digital Services',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240110-00543',
status: 'RESOLVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Full amount recovered and refunded to complainant via NEFT. Case closed.',
assignedOfficer: 'Officer Anita S.',
createdAt: daysAgo(15),
updatedAt: daysAgo(5)
},
{
ticketId: 'CFCFRMS-2024-00007',
complainantName: 'Arvind Singh',
complainantMobile: '7766554433',
complainantEmail: 'arvind.singh@gmail.com',
complainantAddress: '12, Civil Lines, Jaipur, RJ 302006',
fraudAmount: 45000,
fraudType: 'UPI_FRAUD',
fraudDescription:
'Received call from person posing as bank official. Tricked into sharing OTP. ₹45,000 debited via UPI.',
suspectAccountNumber: '50100854917263',
suspectAccountName: 'Sanjay Meena',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'MHA_PORTAL',
acknowledgementNumber: 'ACK-MHA-20240109-00129',
status: 'REJECTED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Suspect account does not belong to LincPay. IFSC mismatch — ticket redirected to correct bank (PNB).',
assignedOfficer: 'Officer Suresh R.',
createdAt: daysAgo(18),
updatedAt: daysAgo(16)
},
{
ticketId: 'CFCFRMS-2024-00008',
complainantName: 'Lakshmi Iyer',
complainantMobile: '9654321076',
complainantEmail: 'lakshmi.iyer@gmail.com',
complainantAddress: '34, Marine Drive, Kochi, KL 682031',
fraudAmount: 560000,
fraudType: 'MATRIMONY_FRAUD',
fraudDescription:
'Met scammer on matrimony site. Paid ₹5.6L over 2 months for "visa processing" and "medical emergency".',
suspectAccountNumber: '50100963845712',
suspectAccountName: 'Premium Match Services',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240108-00321',
status: 'LIEN_MARKED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 234000,
fundsHeld: 560000,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Lien marked. Sufficient balance to cover full fraud amount.',
assignedOfficer: 'Officer Anita S.',
createdAt: daysAgo(10),
updatedAt: daysAgo(7)
},
{
ticketId: 'CFCFRMS-2024-00009',
complainantName: 'Rajan Tiwari',
complainantMobile: '8543219876',
complainantEmail: 'rajan.t@yahoo.com',
complainantAddress: '67, Boring Road, Patna, BR 800001',
fraudAmount: 198000,
fraudType: 'ELECTRICITY_BILL_FRAUD',
fraudDescription:
'Received SMS about pending electricity bill with threat of disconnection. Clicked link and ₹1.98L was stolen via screen-sharing app.',
suspectAccountNumber: '50100172839465',
suspectAccountName: 'Power Solutions India',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240107-00654',
status: 'UNDER_REVIEW',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 56000,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: 'Officer Meena K.',
createdAt: daysAgo(2),
updatedAt: daysAgo(1)
},
{
ticketId: 'CFCFRMS-2024-00010',
complainantName: 'Deepa Nayak',
complainantMobile: '7432108765',
complainantEmail: 'deepa.n@gmail.com',
complainantAddress: '89, Panjim, Goa 403001',
fraudAmount: 125000,
fraudType: 'COURIER_FRAUD',
fraudDescription:
'Fake FedEx call claiming parcel with contraband. Transferred to fake "cyber cell officer" who demanded ₹1.25L as "security deposit".',
suspectAccountNumber: '50100281934567',
suspectAccountName: 'SecurePay Hub',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'MHA_PORTAL',
acknowledgementNumber: 'ACK-MHA-20240106-00087',
status: 'RECEIVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: '',
createdAt: daysAgo(1),
updatedAt: daysAgo(1)
},
{
ticketId: 'CFCFRMS-2024-00011',
complainantName: 'Suresh Babu',
complainantMobile: '9321654870',
complainantEmail: 'suresh.b@gmail.com',
complainantAddress: '22, Gandhi Nagar, Vijayawada, AP 520003',
fraudAmount: 1500000,
fraudType: 'INVESTMENT_FRAUD',
fraudDescription:
'Joined fake crypto trading group on Telegram. Deposited ₹15L over 3 months. Website is now down, funds lost.',
suspectAccountNumber: '50100391726845',
suspectAccountName: 'CryptoVault Exchange',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240105-01234',
status: 'DEBIT_FROZEN',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 340000,
fundsHeld: 340000,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Only ₹3.4L available in account. Rest already moved. Debit frozen on available balance.',
assignedOfficer: 'Officer Suresh R.',
createdAt: daysAgo(6),
updatedAt: daysAgo(3)
},
{
ticketId: 'CFCFRMS-2024-00012',
complainantName: 'Ananya Gupta',
complainantMobile: '8210543976',
complainantEmail: 'ananya.g@outlook.com',
complainantAddress: '45, Connaught Place, New Delhi 110001',
fraudAmount: 95000,
fraudType: 'OLX_FRAUD',
fraudDescription:
'Tried to buy an iPhone on OLX. Seller sent fake payment link. ₹95,000 debited upon entering card details.',
suspectAccountNumber: '50100402819375',
suspectAccountName: 'FastDeal Online',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240104-00789',
status: 'RESOLVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Funds recovered and refunded to complainant.',
assignedOfficer: 'Officer Anita S.',
createdAt: daysAgo(20),
updatedAt: daysAgo(10)
},
{
ticketId: 'CFCFRMS-2024-00013',
complainantName: 'Vikas Joshi',
complainantMobile: '9109876543',
complainantEmail: 'vikas.j@gmail.com',
complainantAddress: '78, FC Road, Pune, MH 411004',
fraudAmount: 67000,
fraudType: 'UPI_FRAUD',
fraudDescription:
'Received collect request on PhonePe with message "Refund of ₹67,000". Approved the collect request and lost funds.',
suspectAccountNumber: '50100513928476',
suspectAccountName: 'Mohan Kumar',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240103-00456',
status: 'RECEIVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: '',
createdAt: hoursAgo(4),
updatedAt: hoursAgo(4)
},
{
ticketId: 'CFCFRMS-2024-00014',
complainantName: 'Geeta Menon',
complainantMobile: '8098765432',
complainantEmail: 'geeta.menon@gmail.com',
complainantAddress: '90, MG Road, Thrissur, KL 680001',
fraudAmount: 430000,
fraudType: 'LOAN_APP_FRAUD',
fraudDescription:
'Downloaded "InstaCash" loan app. After providing bank details, ₹4.3L was transferred out in 5 transactions within 30 minutes.',
suspectAccountNumber: '50100624738291',
suspectAccountName: 'InstaCash Finance',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240102-00567',
status: 'LIEN_MARKED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 178000,
fundsHeld: 430000,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Full fraud amount covered by lien. Investigation ongoing.',
assignedOfficer: 'Officer Meena K.',
createdAt: daysAgo(7),
updatedAt: daysAgo(4)
},
{
ticketId: 'CFCFRMS-2024-00015',
complainantName: 'Pankaj Mishra',
complainantMobile: '7987654321',
complainantEmail: 'pankaj.m@gmail.com',
complainantAddress: '11, Hazratganj, Lucknow, UP 226001',
fraudAmount: 210000,
fraudType: 'SEXTORTION',
fraudDescription:
'Victim blackmailed after video call on dating app. Paid ₹2.1L across 4 transactions to prevent video leak.',
suspectAccountNumber: '50100735829461',
suspectAccountName: 'Nikhil Yadav',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20240101-00890',
status: 'FUNDS_MOVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 5000,
fundsHeld: 0,
nextHopAccountNumber: '40120098765432',
nextHopBankIfsc: 'HDFC0001234',
actionNote: 'Funds already moved to HDFC account. Escalated.',
assignedOfficer: 'Officer Suresh R.',
createdAt: daysAgo(12),
updatedAt: daysAgo(8)
},
{
ticketId: 'CFCFRMS-2024-00016',
complainantName: 'Rekha Pandey',
complainantMobile: '6876543210',
complainantEmail: 'rekha.p@yahoo.com',
complainantAddress: '33, Lalbagh Road, Mangalore, KA 575001',
fraudAmount: 38000,
fraudType: 'CUSTOMER_CARE_FRAUD',
fraudDescription:
'Googled customer care for Paytm. Called fake number. Agent installed TeamViewer and debited ₹38,000.',
suspectAccountNumber: '50100846729153',
suspectAccountName: 'Digital Pay Services',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'MHA_PORTAL',
acknowledgementNumber: 'ACK-MHA-20240101-00044',
status: 'UNDER_REVIEW',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 18500,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: 'Officer Anita S.',
createdAt: daysAgo(2),
updatedAt: hoursAgo(12)
},
{
ticketId: 'CFCFRMS-2024-00017',
complainantName: 'Dinesh Choudhary',
complainantMobile: '9765432180',
complainantEmail: 'dinesh.c@gmail.com',
complainantAddress: '55, Ashram Road, Ahmedabad, GJ 380009',
fraudAmount: 780000,
fraudType: 'INVESTMENT_FRAUD',
fraudDescription:
'Invested in fake forex trading platform "ForexPro". Platform blocked withdrawal after total deposit of ₹7.8L.',
suspectAccountNumber: '50100957382614',
suspectAccountName: 'ForexPro Trading LLP',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20241230-01567',
status: 'DEBIT_FROZEN',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 520000,
fundsHeld: 520000,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Partial amount frozen. ₹2.6L already transferred out.',
assignedOfficer: 'Officer Meena K.',
createdAt: daysAgo(9),
updatedAt: daysAgo(5)
},
{
ticketId: 'CFCFRMS-2024-00018',
complainantName: 'Fatima Begum',
complainantMobile: '8654321097',
complainantEmail: '',
complainantAddress: '12, Chowk Bazaar, Bhopal, MP 462001',
fraudAmount: 55000,
fraudType: 'ELECTRICITY_BILL_FRAUD',
fraudDescription:
'Received WhatsApp message about electricity bill overdue. Called the number provided. Agent made victim install AnyDesk. ₹55,000 debited.',
suspectAccountNumber: '50100168293475',
suspectAccountName: 'Ravi Shankar',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20241229-00234',
status: 'RESOLVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Funds recovered from suspect account and refunded.',
assignedOfficer: 'Officer Suresh R.',
createdAt: daysAgo(25),
updatedAt: daysAgo(15)
},
{
ticketId: 'CFCFRMS-2024-00019',
complainantName: 'Manoj Kumar',
complainantMobile: '9543210876',
complainantEmail: 'manoj.k@gmail.com',
complainantAddress: '67, Park Street, Kolkata, WB 700016',
fraudAmount: 175000,
fraudType: 'PART_TIME_JOB_FRAUD',
fraudDescription:
'Joined "Amazon Review" job scam on Telegram. Initially earned small amounts, then lost ₹1.75L as "upgrade fees".',
suspectAccountNumber: '50100279384651',
suspectAccountName: 'SmartWork Hub',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20241228-00901',
status: 'LIEN_MARKED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 98000,
fundsHeld: 175000,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: 'Lien marked successfully. Full amount available.',
assignedOfficer: 'Officer Anita S.',
createdAt: daysAgo(4),
updatedAt: daysAgo(2)
},
{
ticketId: 'CFCFRMS-2024-00020',
complainantName: 'Shreya Das',
complainantMobile: '8432109765',
complainantEmail: 'shreya.d@gmail.com',
complainantAddress: '101, Bandra West, Mumbai, MH 400050',
fraudAmount: 2100000,
fraudType: 'INVESTMENT_FRAUD',
fraudDescription:
'Invested ₹21L in fake IPO allotment scheme promoted via Instagram ads. Payment split across 3 accounts.',
suspectAccountNumber: '50100388274916',
suspectAccountName: 'IPO Goldmine Pvt Ltd',
suspectBankIfsc: 'LINC0000001',
suspectBankName: 'LincPay Digital Bank',
sourcePortal: 'I4C_CFCFRMS',
acknowledgementNumber: 'ACK-I4C-20241227-01789',
status: 'RECEIVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: '',
createdAt: hoursAgo(2),
updatedAt: hoursAgo(2)
}
];
// ─── Timeline Events keyed by ticketId ───
export const MOCK_TIMELINES: { [ticketId: string]: TimelineEvent[] } = {
'CFCFRMS-2024-00001': [
{
id: 1,
ticketId: 'CFCFRMS-2024-00001',
eventType: 'COMPLAINT_RECEIVED',
eventDescription: 'Complaint received from I4C CFCFRMS portal.',
actor: 'System',
actorRole: 'SYSTEM',
timestamp: daysAgo(0),
metadata: {}
}
],
'CFCFRMS-2024-00002': [
{
id: 1,
ticketId: 'CFCFRMS-2024-00002',
eventType: 'COMPLAINT_RECEIVED',
eventDescription: 'Complaint received from I4C CFCFRMS portal.',
actor: 'System',
actorRole: 'SYSTEM',
timestamp: daysAgo(1),
metadata: {}
},
{
id: 2,
ticketId: 'CFCFRMS-2024-00002',
eventType: 'ASSIGNED',
eventDescription: 'Assigned to Officer Meena K. for review.',
actor: 'Admin',
actorRole: 'ADMIN',
timestamp: hoursAgo(18),
metadata: {}
},
{
id: 3,
ticketId: 'CFCFRMS-2024-00002',
eventType: 'UNDER_REVIEW',
eventDescription: 'Investigation initiated. Checking suspect account transaction history.',
actor: 'Officer Meena K.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: hoursAgo(6),
metadata: {}
}
],
'CFCFRMS-2024-00003': [
{
id: 1,
ticketId: 'CFCFRMS-2024-00003',
eventType: 'COMPLAINT_RECEIVED',
eventDescription: 'Complaint received from MHA Portal.',
actor: 'System',
actorRole: 'SYSTEM',
timestamp: daysAgo(3),
metadata: {}
},
{
id: 2,
ticketId: 'CFCFRMS-2024-00003',
eventType: 'ASSIGNED',
eventDescription: 'Assigned to Officer Suresh R.',
actor: 'Admin',
actorRole: 'ADMIN',
timestamp: daysAgo(3),
metadata: {}
},
{
id: 3,
ticketId: 'CFCFRMS-2024-00003',
eventType: 'UNDER_REVIEW',
eventDescription: 'Account details verified. ₹1.5L fraud amount confirmed.',
actor: 'Officer Suresh R.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(2),
metadata: {}
},
{
id: 4,
ticketId: 'CFCFRMS-2024-00003',
eventType: 'LIEN_MARKED',
eventDescription: 'Lien placed on suspect account. Balance ₹2,39,000. Full fraud amount secured.',
actor: 'Officer Suresh R.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(1),
metadata: { availableBalance: 89000, fundsHeld: 150000 }
}
],
'CFCFRMS-2024-00004': [
{
id: 1,
ticketId: 'CFCFRMS-2024-00004',
eventType: 'COMPLAINT_RECEIVED',
eventDescription: 'Complaint received from I4C CFCFRMS portal.',
actor: 'System',
actorRole: 'SYSTEM',
timestamp: daysAgo(5),
metadata: {}
},
{
id: 2,
ticketId: 'CFCFRMS-2024-00004',
eventType: 'ASSIGNED',
eventDescription: 'Assigned to Officer Suresh R.',
actor: 'Admin',
actorRole: 'ADMIN',
timestamp: daysAgo(5),
metadata: {}
},
{
id: 3,
ticketId: 'CFCFRMS-2024-00004',
eventType: 'LIEN_MARKED',
eventDescription: 'Lien placed on suspect account.',
actor: 'Officer Suresh R.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(3),
metadata: {}
},
{
id: 4,
ticketId: 'CFCFRMS-2024-00004',
eventType: 'DEBIT_FROZEN',
eventDescription: 'Debit freeze applied. Full ₹75,000 held. Account holder notified via registered mobile.',
actor: 'Officer Suresh R.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(2),
metadata: { availableBalance: 42000, fundsHeld: 75000 }
}
],
'CFCFRMS-2024-00005': [
{
id: 1,
ticketId: 'CFCFRMS-2024-00005',
eventType: 'COMPLAINT_RECEIVED',
eventDescription: 'Complaint received from I4C CFCFRMS portal.',
actor: 'System',
actorRole: 'SYSTEM',
timestamp: daysAgo(8),
metadata: {}
},
{
id: 2,
ticketId: 'CFCFRMS-2024-00005',
eventType: 'ASSIGNED',
eventDescription: 'Assigned to Officer Meena K.',
actor: 'Admin',
actorRole: 'ADMIN',
timestamp: daysAgo(8),
metadata: {}
},
{
id: 3,
ticketId: 'CFCFRMS-2024-00005',
eventType: 'LIEN_MARKED',
eventDescription: 'Only ₹12,500 available in account.',
actor: 'Officer Meena K.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(6),
metadata: {}
},
{
id: 4,
ticketId: 'CFCFRMS-2024-00005',
eventType: 'DEBIT_FROZEN',
eventDescription: 'Account debit frozen.',
actor: 'Officer Meena K.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(5),
metadata: {}
},
{
id: 5,
ticketId: 'CFCFRMS-2024-00005',
eventType: 'FUNDS_MOVED',
eventDescription: 'Majority of funds already moved to SBI account 91820034567812. Escalation initiated.',
actor: 'Officer Meena K.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(4),
metadata: { nextHopAccountNumber: '91820034567812', nextHopBankIfsc: 'SBIN0001234' }
}
],
'CFCFRMS-2024-00006': [
{
id: 1,
ticketId: 'CFCFRMS-2024-00006',
eventType: 'COMPLAINT_RECEIVED',
eventDescription: 'Complaint received from I4C CFCFRMS portal.',
actor: 'System',
actorRole: 'SYSTEM',
timestamp: daysAgo(15),
metadata: {}
},
{
id: 2,
ticketId: 'CFCFRMS-2024-00006',
eventType: 'ASSIGNED',
eventDescription: 'Assigned to Officer Anita S.',
actor: 'Admin',
actorRole: 'ADMIN',
timestamp: daysAgo(15),
metadata: {}
},
{
id: 3,
ticketId: 'CFCFRMS-2024-00006',
eventType: 'LIEN_MARKED',
eventDescription: 'Full amount liened.',
actor: 'Officer Anita S.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(13),
metadata: {}
},
{
id: 4,
ticketId: 'CFCFRMS-2024-00006',
eventType: 'DEBIT_FROZEN',
eventDescription: 'Debit frozen on suspect account.',
actor: 'Officer Anita S.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(10),
metadata: {}
},
{
id: 5,
ticketId: 'CFCFRMS-2024-00006',
eventType: 'RESOLVED',
eventDescription: 'Full amount ₹3,20,000 recovered and refunded to complainant via NEFT. Case closed.',
actor: 'Officer Anita S.',
actorRole: 'COMPLIANCE_OFFICER',
timestamp: daysAgo(5),
metadata: {}
}
]
};
// ─── Dashboard Stats (computed from mock data) ───
export function computeMockStats(): DashboardStats {
const statusDist: { [key: string]: number } = {};
const amountDist: { [key: string]: number } = {};
let pendingReview = 0;
let lienMarked = 0;
let fundsRecovered = 0;
MOCK_COMPLAINTS.forEach((c) => {
statusDist[c.status] = (statusDist[c.status] || 0) + 1;
amountDist[c.status] = (amountDist[c.status] || 0) + c.fraudAmount;
if (c.status === 'RECEIVED' || c.status === 'UNDER_REVIEW') pendingReview++;
if (c.status === 'LIEN_MARKED') lienMarked++;
if (c.status === 'RESOLVED') fundsRecovered += c.fraudAmount;
});
return {
totalComplaints: MOCK_COMPLAINTS.length,
pendingReview,
lienMarked,
fundsRecovered,
statusDistribution: statusDist as any,
amountByStatus: amountDist as any
};
}
@@ -0,0 +1,618 @@
/**
* 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 { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { MOCK_COMPLAINTS, MOCK_TIMELINES, computeMockStats } from './cfcfrms-mock-data';
import { Complaint, ComplaintStatus, TimelineEvent, EscalationResponse, PagedResponse } from '../models/cfcfrms.models';
// ─── Mutable in-memory state (survives during session) ───
const complaints = [...MOCK_COMPLAINTS];
const timelines: { [key: string]: TimelineEvent[] } = JSON.parse(JSON.stringify(MOCK_TIMELINES));
let nextEventId = 1000;
let nextTicketSeq = 21;
function now(): string {
return new Date().toISOString();
}
function genTicketId(): string {
const seq = String(nextTicketSeq++).padStart(5, '0');
return `CFCFRMS-2024-${seq}`;
}
function genRefNumber(): string {
return `ESC-${Date.now()}-${Math.floor(Math.random() * 9000 + 1000)}`;
}
function addTimeline(
ticketId: string,
eventType: string,
description: string,
actor: string,
actorRole: string,
meta: any = {}
): void {
if (!timelines[ticketId]) timelines[ticketId] = [];
timelines[ticketId].push({
id: ++nextEventId,
ticketId,
eventType,
eventDescription: description,
actor,
actorRole,
timestamp: now(),
metadata: meta
});
}
// ─── Bank-side handlers ───
function handleGetComplaints(url: URL): PagedResponse<Complaint> {
const page = parseInt(url.searchParams.get('page') || '0', 10);
const size = parseInt(url.searchParams.get('size') || '10', 10);
const status = url.searchParams.get('status') || '';
const search = (url.searchParams.get('search') || '').toLowerCase();
const dateFrom = url.searchParams.get('dateFrom') || '';
const dateTo = url.searchParams.get('dateTo') || '';
let filtered = [...complaints];
if (status) {
filtered = filtered.filter((c) => c.status === status);
}
if (search) {
filtered = filtered.filter(
(c) =>
c.ticketId.toLowerCase().includes(search) ||
c.complainantMobile.includes(search) ||
c.complainantName.toLowerCase().includes(search)
);
}
if (dateFrom) {
const from = new Date(dateFrom);
filtered = filtered.filter((c) => new Date(c.createdAt) >= from);
}
if (dateTo) {
const to = new Date(dateTo);
to.setHours(23, 59, 59, 999);
filtered = filtered.filter((c) => new Date(c.createdAt) <= to);
}
filtered.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
const totalElements = filtered.length;
const totalPages = Math.ceil(totalElements / size);
const start = page * size;
const content = filtered.slice(start, start + size);
return { content, totalElements, totalPages, size, number: page, first: page === 0, last: page >= totalPages - 1 };
}
function handleGetComplaintById(ticketId: string): Complaint | null {
return complaints.find((c) => c.ticketId === ticketId) || null;
}
function handleSubmitAction(ticketId: string, body: any): Complaint | null {
const idx = complaints.findIndex((c) => c.ticketId === ticketId);
if (idx === -1) return null;
const complaint = complaints[idx];
complaint.status = body.action as ComplaintStatus;
complaint.actionNote = body.actionNote || complaint.actionNote;
complaint.updatedAt = now();
if (body.availableBalance != null) complaint.availableBalance = body.availableBalance;
if (body.fundsHeld != null) complaint.fundsHeld = body.fundsHeld;
if (body.nextHopAccountNumber) complaint.nextHopAccountNumber = body.nextHopAccountNumber;
if (body.nextHopBankIfsc) complaint.nextHopBankIfsc = body.nextHopBankIfsc;
addTimeline(
ticketId,
body.action,
body.actionNote || `Status changed to ${body.action}`,
'Current User',
'COMPLIANCE_OFFICER',
{
availableBalance: body.availableBalance,
fundsHeld: body.fundsHeld,
nextHopAccountNumber: body.nextHopAccountNumber,
nextHopBankIfsc: body.nextHopBankIfsc
}
);
return complaint;
}
function handleGetTimeline(ticketId: string): TimelineEvent[] {
return (timelines[ticketId] || []).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
}
function handleEscalate(ticketId: string, body: any): EscalationResponse | null {
const complaint = complaints.find((c) => c.ticketId === ticketId);
if (!complaint) return null;
complaint.nextHopAccountNumber = body.nextHopAccountNumber;
complaint.nextHopBankIfsc = body.nextHopBankIfsc;
complaint.status = 'FUNDS_MOVED';
complaint.updatedAt = now();
addTimeline(
ticketId,
'ESCALATED',
`Escalated to bank ${body.nextHopBankIfsc}. Remarks: ${body.remarks}`,
'Current User',
'COMPLIANCE_OFFICER',
body
);
return {
ticketId,
referenceNumber: genRefNumber(),
escalatedTo: body.nextHopBankIfsc,
escalatedBankIfsc: body.nextHopBankIfsc,
status: 'ESCALATED',
timestamp: now()
};
}
function handleCreateComplaint(body: any): Complaint {
const ticket: Complaint = {
ticketId: genTicketId(),
complainantName: body.complainantName || 'Unknown',
complainantMobile: body.complainantMobile || '',
complainantEmail: body.complainantEmail || '',
complainantAddress: body.complainantAddress || '',
fraudAmount: body.fraudAmount || 0,
fraudType: body.fraudType || 'UNKNOWN',
fraudDescription: body.fraudDescription || '',
suspectAccountNumber: body.suspectAccountNumber || '',
suspectAccountName: body.suspectAccountName || '',
suspectBankIfsc: body.suspectBankIfsc || 'LINC0000001',
suspectBankName: body.suspectBankName || 'LincPay Digital Bank',
sourcePortal: body.sourcePortal || 'I4C_CFCFRMS',
acknowledgementNumber: body.acknowledgementNumber || `ACK-${Date.now()}`,
status: 'RECEIVED',
currentBankIfsc: 'LINC0000001',
currentBankName: 'LincPay Digital Bank',
availableBalance: 0,
fundsHeld: 0,
nextHopAccountNumber: '',
nextHopBankIfsc: '',
actionNote: '',
assignedOfficer: '',
createdAt: now(),
updatedAt: now()
};
complaints.unshift(ticket);
addTimeline(ticket.ticketId, 'COMPLAINT_RECEIVED', 'Complaint registered.', 'System', 'SYSTEM');
return ticket;
}
// ─── Simulator handlers ───
function handleSimPushComplaint(body: any): any {
const ticket = handleCreateComplaint(body);
return { success: true, message: 'Complaint pushed to bank', ticketId: ticket.ticketId, status: ticket.status };
}
function handleSimPushHoldDirective(body: any): any {
const ticketId = body.ticketId || body.referenceNumber;
const complaint = complaints.find((c) => c.ticketId === ticketId);
if (!complaint) return { success: false, message: 'Ticket not found' };
complaint.status = 'DEBIT_FROZEN';
complaint.fundsHeld = body.holdAmount || complaint.fraudAmount;
complaint.updatedAt = now();
addTimeline(
ticketId,
'DEBIT_FROZEN',
`Urgent hold directive from I4C. Amount: ${complaint.fundsHeld}`,
'I4C System',
'REGULATOR'
);
return {
success: true,
message: 'Hold directive applied',
ticketId,
status: 'DEBIT_FROZEN',
fundsHeld: complaint.fundsHeld
};
}
function handleSimPushBulk(body: any): any {
const items = body.complaints || [];
const results = items.slice(0, 10).map((c: any) => handleSimPushComplaint(c));
return { success: true, totalPushed: results.length, results };
}
function handleSimReceiveBankUpdate(body: any): any {
const complaint = complaints.find((c) => c.ticketId === body.ticketId);
if (!complaint) return { success: false, message: 'Ticket not found' };
return {
success: true,
ticketId: body.ticketId,
currentStatus: complaint.status,
lastUpdated: complaint.updatedAt,
actionNote: complaint.actionNote
};
}
function handleSimReceiveEscalation(body: any): any {
const complaint = complaints.find((c) => c.ticketId === body.ticketId);
if (!complaint) return { success: false, message: 'Ticket not found' };
return {
success: true,
ticketId: body.ticketId,
escalatedTo: complaint.nextHopBankIfsc || 'N/A',
fundsHeld: complaint.fundsHeld,
currentStatus: complaint.status
};
}
function handleSimVerifyAccount(body: any): any {
const acctNo = body.accountNumber || '';
const match = complaints.find((c) => c.suspectAccountNumber === acctNo);
return {
accountNumber: acctNo,
exists: !!match,
accountName: match?.suspectAccountName || null,
bankIfsc: match?.suspectBankIfsc || null,
bankName: match?.suspectBankName || null,
isFlagged: !!match,
flagReason: match ? `Linked to CFCFRMS ticket ${match.ticketId}` : null
};
}
function handleSimVerifyTransaction(body: any): any {
return {
transactionId: body.transactionId || `TXN-${Date.now()}`,
accountNumber: body.accountNumber || '',
amount: body.amount || 0,
status: 'VERIFIED',
timestamp: now(),
riskScore: Math.floor(Math.random() * 40 + 60),
flagged: true,
remarks: 'Transaction linked to suspected fraud account.'
};
}
function handleSimComplaintStatus(ref: string): any {
const complaint = complaints.find((c) => c.ticketId === ref || c.acknowledgementNumber === ref);
if (!complaint) return { found: false, message: 'Complaint not found' };
return {
found: true,
ticketId: complaint.ticketId,
acknowledgementNumber: complaint.acknowledgementNumber,
status: complaint.status,
bankAction: complaint.actionNote || 'Pending',
lastUpdated: complaint.updatedAt,
assignedOfficer: complaint.assignedOfficer || 'Unassigned'
};
}
function handleSimBankPerformance(): any {
const total = complaints.length;
const resolved = complaints.filter((c) => c.status === 'RESOLVED').length;
const rejected = complaints.filter((c) => c.status === 'REJECTED').length;
const frozen = complaints.filter((c) => c.status === 'DEBIT_FROZEN' || c.status === 'LIEN_MARKED').length;
const pending = complaints.filter((c) => c.status === 'RECEIVED' || c.status === 'UNDER_REVIEW').length;
return {
bankIfsc: 'LINC0000001',
bankName: 'LincPay Digital Bank',
totalTickets: total,
resolvedCount: resolved,
rejectedCount: rejected,
frozenCount: frozen,
pendingCount: pending,
averageTATHours: 18.5,
complianceScore: 92.4,
responseRate: total > 0 ? Math.round(((total - pending) / total) * 100 * 10) / 10 : 0,
periodFrom: new Date(Date.now() - 30 * 86400000).toISOString().split('T')[0],
periodTo: new Date().toISOString().split('T')[0]
};
}
// ─── Bank Proactive Reporting handlers ───
// In-memory stores for reported items
const reportedSuspects: any[] = [];
const reportedMules: any[] = [];
const reportedFraudTxns: any[] = [];
const fmrSubmissions: any[] = [];
// Dummy CFR registry data
const cfrRegistry: { [acctNo: string]: any[] } = {
'50100268347291': [
{
fraudReferenceNumber: 'CFR-2024-00891',
fraudCategory: 'UPI_FRAUD',
fraudAmount: 250000,
reportingBank: 'LincPay Digital Bank',
reportingBankIfsc: 'LINC0000001',
dateReported: '2024-01-10',
status: 'CONFIRMED',
remarks: 'Account involved in multiple UPI fraud complaints.'
}
],
'50100637291845': [
{
fraudReferenceNumber: 'CFR-2024-01234',
fraudCategory: 'INVESTMENT_FRAUD',
fraudAmount: 890000,
reportingBank: 'LincPay Digital Bank',
reportingBankIfsc: 'LINC0000001',
dateReported: '2024-01-08',
status: 'CONFIRMED',
remarks: 'Mule account used for crypto investment scam.'
},
{
fraudReferenceNumber: 'CFR-2024-01235',
fraudCategory: 'INVESTMENT_FRAUD',
fraudAmount: 450000,
reportingBank: 'State Bank of India',
reportingBankIfsc: 'SBIN0001234',
dateReported: '2023-12-20',
status: 'CONFIRMED',
remarks: 'Cross-bank fraud trail from SBI complaint.'
}
],
'50100745829361': [
{
fraudReferenceNumber: 'CFR-2024-00567',
fraudCategory: 'PART_TIME_JOB_FRAUD',
fraudAmount: 320000,
reportingBank: 'LincPay Digital Bank',
reportingBankIfsc: 'LINC0000001',
dateReported: '2024-01-05',
status: 'RESOLVED',
remarks: 'Funds recovered. Account closed.'
}
],
'40120098765432': [
{
fraudReferenceNumber: 'CFR-2024-00999',
fraudCategory: 'SEXTORTION',
fraudAmount: 210000,
reportingBank: 'HDFC Bank',
reportingBankIfsc: 'HDFC0001234',
dateReported: '2024-01-02',
status: 'UNDER_INVESTIGATION',
remarks: 'Next-hop account in sextortion chain.'
}
]
};
function handleReportSuspect(body: any): any {
const ref = `SUS-${Date.now()}-${Math.floor(Math.random() * 9000 + 1000)}`;
const record = { ...body, referenceNumber: ref, status: 'REPORTED', reportedAt: now() };
reportedSuspects.push(record);
return {
success: true,
referenceNumber: ref,
accountNumber: body.accountNumber,
status: 'REPORTED',
reportedAt: record.reportedAt,
message: 'Suspect account reported to I4C successfully.'
};
}
function handleReportMuleAccount(body: any): any {
const ref = `MULE-${Date.now()}-${Math.floor(Math.random() * 9000 + 1000)}`;
const record = { ...body, referenceNumber: ref, status: 'FLAGGED', reportedAt: now() };
reportedMules.push(record);
return {
success: true,
referenceNumber: ref,
accountNumber: body.accountNumber,
muleType: body.muleType,
status: 'FLAGGED',
reportedAt: record.reportedAt,
message: 'Mule account flagged and reported to I4C/CFCFRMS.'
};
}
function handleReportFraudTransaction(body: any): any {
const ref = `FTX-${Date.now()}-${Math.floor(Math.random() * 9000 + 1000)}`;
const record = { ...body, referenceNumber: ref, status: 'REPORTED', reportedAt: now() };
reportedFraudTxns.push(record);
return {
success: true,
referenceNumber: ref,
transactionId: body.transactionId,
status: 'REPORTED',
reportedAt: record.reportedAt,
message: 'Fraud transaction reported to I4C successfully.'
};
}
function handleFmrSubmission(body: any): any {
const ref = `FMR-${new Date().getFullYear()}-${String(fmrSubmissions.length + 1).padStart(6, '0')}`;
const nextDue = new Date();
nextDue.setDate(nextDue.getDate() + 90);
const record = {
...body,
fmrReferenceNumber: ref,
status: 'SUBMITTED',
submittedAt: now(),
nextDueDate: nextDue.toISOString().split('T')[0]
};
fmrSubmissions.push(record);
return {
success: true,
fmrReferenceNumber: ref,
reportingBankIfsc: body.reportingBankIfsc,
fraudCategory: body.fraudCategory,
fraudAmount: body.fraudAmount,
status: 'SUBMITTED',
submittedAt: record.submittedAt,
nextDueDate: record.nextDueDate,
message: 'FMR submitted to RBI FUA system successfully.'
};
}
function handleCfrLookup(url: URL): any {
const accountNumber = url.searchParams.get('accountNumber') || '';
const records = cfrRegistry[accountNumber] || [];
return { accountNumber, found: records.length > 0, records, queryTimestamp: now() };
}
// ─── Route matcher utility ───
function matchPath(pathname: string, pattern: string): { match: boolean; params: { [k: string]: string } } {
const patParts = pattern.split('/').filter(Boolean);
const urlParts = pathname.split('/').filter(Boolean);
if (patParts.length !== urlParts.length) return { match: false, params: {} };
const params: { [k: string]: string } = {};
for (let i = 0; i < patParts.length; i++) {
if (patParts[i].startsWith(':')) {
params[patParts[i].slice(1)] = urlParts[i];
} else if (patParts[i] !== urlParts[i]) {
return { match: false, params: {} };
}
}
return { match: true, params };
}
function parseUrl(raw: string): URL {
try {
return new URL(raw);
} catch {
return new URL(raw, 'http://localhost');
}
}
// ─── Class-based Interceptor ───
@Injectable()
export class CfcfrmsMockInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const url = parseUrl(req.url);
const path = url.pathname;
const method = req.method;
const isCfcfrms =
path.includes('/cfcfrms/complaints') || path.includes('/cfcfrms/complaint') || path.includes('/cfcfrms/report-');
const isSimulator = path.includes('/simulator/cfcfrms');
const isRbi = path.includes('/rbi/fmr') || path.includes('/rbi/cfr');
if (!isCfcfrms && !isSimulator && !isRbi) {
return next.handle(req);
}
// Extract relative path
let relativePath = '';
if (isRbi) {
const idx = path.indexOf('/rbi');
relativePath = path.substring(idx);
} else if (isSimulator) {
const idx = path.indexOf('/simulator/cfcfrms');
relativePath = path.substring(idx + '/simulator/cfcfrms'.length);
} else {
const idx = path.indexOf('/cfcfrms');
relativePath = path.substring(idx + '/cfcfrms'.length);
}
let responseBody: any = { error: 'Not Found' };
let status = 200;
const mockDelay = Math.floor(Math.random() * 300 + 150);
// ──── Bank-side routes ────
if (!isSimulator) {
if (method === 'POST' && relativePath === '/complaints') {
responseBody = handleCreateComplaint(req.body);
} else if (method === 'GET' && relativePath === '/complaints/stats') {
responseBody = computeMockStats();
} else {
const timelineMatch = matchPath(relativePath, '/complaints/:ticketId/timeline');
if (method === 'GET' && timelineMatch.match) {
responseBody = handleGetTimeline(timelineMatch.params['ticketId']);
} else {
const actionMatch = matchPath(relativePath, '/complaints/:ticketId/action');
if (method === 'PUT' && actionMatch.match) {
const result = handleSubmitAction(actionMatch.params['ticketId'], req.body);
if (!result) {
responseBody = { error: 'Ticket not found' };
status = 404;
} else responseBody = result;
} else {
const escMatch = matchPath(relativePath, '/complaints/:ticketId/escalate');
if (method === 'POST' && escMatch.match) {
const result = handleEscalate(escMatch.params['ticketId'], req.body);
if (!result) {
responseBody = { error: 'Ticket not found' };
status = 404;
} else responseBody = result;
} else {
const detailMatch = matchPath(relativePath, '/complaints/:ticketId');
if (method === 'GET' && detailMatch.match) {
const result = handleGetComplaintById(detailMatch.params['ticketId']);
if (!result) {
responseBody = { error: 'Ticket not found' };
status = 404;
} else responseBody = result;
} else if (method === 'GET' && (relativePath === '/complaints' || relativePath === '/complaints/')) {
responseBody = handleGetComplaints(url);
}
}
}
}
}
// Bank proactive reporting routes
if (method === 'POST' && relativePath === '/report-suspect') {
responseBody = handleReportSuspect(req.body);
} else if (method === 'POST' && relativePath === '/report-mule-account') {
responseBody = handleReportMuleAccount(req.body);
} else if (method === 'POST' && relativePath === '/report-fraud-transaction') {
responseBody = handleReportFraudTransaction(req.body);
}
}
// ──── RBI routes ────
if (isRbi) {
if (method === 'POST' && relativePath === '/rbi/fmr') {
responseBody = handleFmrSubmission(req.body);
} else if (method === 'GET' && relativePath.startsWith('/rbi/cfr/lookup')) {
responseBody = handleCfrLookup(url);
}
}
// ──── Simulator routes ────
if (isSimulator) {
if (method === 'POST' && relativePath === '/push-complaint') {
responseBody = handleSimPushComplaint(req.body);
} else if (method === 'POST' && relativePath === '/push-hold-directive') {
responseBody = handleSimPushHoldDirective(req.body);
} else if (method === 'POST' && relativePath === '/push-bulk-complaints') {
responseBody = handleSimPushBulk(req.body);
} else if (method === 'POST' && relativePath === '/receive-bank-update') {
responseBody = handleSimReceiveBankUpdate(req.body);
} else if (method === 'POST' && relativePath === '/receive-escalation') {
responseBody = handleSimReceiveEscalation(req.body);
} else if (method === 'POST' && relativePath === '/verify-account') {
responseBody = handleSimVerifyAccount(req.body);
} else if (method === 'POST' && relativePath === '/verify-transaction') {
responseBody = handleSimVerifyTransaction(req.body);
} else {
const statusMatch = matchPath(relativePath, '/complaint-status/:ref');
if (method === 'GET' && statusMatch.match) {
responseBody = handleSimComplaintStatus(statusMatch.params['ref']);
} else if (method === 'GET' && relativePath === '/bank-performance') {
responseBody = handleSimBankPerformance();
}
}
}
return of(new HttpResponse({ status, body: responseBody })).pipe(delay(mockDelay));
}
}