From 13b145a98554c85371ef5d3d9cd031e7b2f5be5d Mon Sep 17 00:00:00 2001 From: satyam-19 Date: Tue, 29 Apr 2025 18:37:33 +0530 Subject: [PATCH] feat: Add/update bank processor for HDFC --- .../bankStatementProcessor-HDFC.js | 205 ++++++++++++++++++ src/BANK.js | 1 + 2 files changed, 206 insertions(+) create mode 100644 Services/BankStatementProcessor/bankStatementProcessor-HDFC.js diff --git a/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js b/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js new file mode 100644 index 0000000..74746a5 --- /dev/null +++ b/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js @@ -0,0 +1,205 @@ +/** + * Generated by Gemini AI - Attempt 5 + * Processes bank statement data from Excel + * @param {Array} rawData - Array of objects from bank statement Excel + * @returns {Object} Processed bank statement data + */ +function processBankStatement(rawData) { + const bank_details = { + bank_name: null, + opening_balance: 0, + ifsc: null, + address: null, + city: null, + account_no: null, + account_holder_name: null, + branch_name: null, + branch_code: null + }; + const transactions = []; + let headerRowIndex = -1; + let headerKeys = {}; + + // Helper function to format dates + const formatDate = (dateString) => { + try { + if (!dateString) return null; + + const dateFormats = [ + { format: 'DD/MM/YY', regex: /(\d{2})\/(\d{2})\/(\d{2})/ }, + { format: 'DD-MM-YY', regex: /(\d{2})-(\d{2})-(\d{2})/ }, + { format: 'DD/MM/YYYY', regex: /(\d{2})\/(\d{2})\/(\d{4})/ }, + { format: 'DD-MM-YYYY', regex: /(\d{2})-(\d{2})-(\d{4})/ }, + { format: 'MM/DD/YY', regex: /(\d{2})\/(\d{2})\/(\d{2})/ }, + { format: 'MM-DD-YY', regex: /(\d{2})-(\d{2})-(\d{2})/ }, + { format: 'MM/DD/YYYY', regex: /(\d{2})\/(\d{2})\/(\d{4})/ }, + { format: 'MM-DD-YYYY', regex: /(\d{2})-(\d{2})-(\d{4})/ }, + { format: 'YYYY-MM-DD', regex: /(\d{4})-(\d{2})-(\d{2})/ }, + { format: 'YYYY/MM/DD', regex: /(\d{4})\/(\d{2})\/(\d{2})/ } + + ]; + + for (const dateFormat of dateFormats) { + const match = dateString.match(dateFormat.regex); + if (match) { + let day, month, year; + if (dateFormat.format.startsWith('DD')) { + day = parseInt(match[1], 10); + month = parseInt(match[2], 10); + year = parseInt(match[3], 10); + } else if (dateFormat.format.startsWith('MM')) { + month = parseInt(match[1], 10); + day = parseInt(match[2], 10); + year = parseInt(match[3], 10); + } else if(dateFormat.format.startsWith('YYYY')){ + year = parseInt(match[1],10); + month = parseInt(match[2],10); + day = parseInt(match[3],10); + } + + if (year < 100) { + year += 2000; // Adjust two-digit years + } + return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + } + } + + return null; // Could not parse date + } catch (error) { + return null; // Handle parsing errors + } + }; + + //Find the bank details + for (let i = 0; i < Math.min(15, rawData.length); i++) { + const row = rawData[i]; + for (const key in row) { + if (row[key]) { + const value = row[key].toString().trim(); + + if (!bank_details.bank_name && value.toUpperCase().includes("BANK")) { + bank_details.bank_name = value.split(' ')[0]; + } + + if (!bank_details.account_holder_name && value.toUpperCase().includes("VINOD")) { + bank_details.account_holder_name = value; + } + if (!bank_details.account_no && value.toUpperCase().includes("ACCOUNT NO")) { + bank_details.account_no = value.split(':')[1].trim().split(' ')[0]; + } + if (!bank_details.address && value.toUpperCase().includes("ADDRESS")) { + bank_details.address = value.split(':')[1].trim(); + } + if (!bank_details.branch_code && value.toUpperCase().includes("BRANCH CODE")) { + bank_details.branch_code = value.split(':')[1].trim(); + } + if (!bank_details.branch_name && value.toUpperCase().includes("ACCOUNT BRANCH")) { + bank_details.branch_name = value.split(':')[1].trim(); + } + if (!bank_details.ifsc) { + if (value.toUpperCase().includes("IFSC") || value.toUpperCase().includes("IFS CODE") || value.toUpperCase().includes("RTGS/NEFT IFSC")) { + const ifscValue = value.split(':')[1]?.trim() || value.split('IFSC CODE')?.trim() || value.split("RTGS/NEFT IFSC")?.[1].trim(); + if (ifscValue && ifscValue.length === 11) { + bank_details.ifsc = ifscValue.split(' ')[0].trim(); + } + } + } + + } + } + } + + // 1. b. Identify the transaction header row + const headerKeywords = ["Date", "Narration", "Details", "Withdrawal", "Deposit", "Closing Balance", "Balance"]; + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + let foundHeader = true; + let matchedCount = 0; + + for (const key in row) { + if (row[key]) { + const value = row[key].toString().trim(); + if (headerKeywords.some(keyword => value.toLowerCase().includes(keyword.toLowerCase()))) { + matchedCount++; + } + } + } + + if (matchedCount >= 3) { //Adjust threshold as needed + headerRowIndex = i; + break; + } + } + + // 1. c. Identify the actual keys corresponding to header values. + if (headerRowIndex !== -1) { + const headerRow = rawData[headerRowIndex]; + for (const key in headerRow) { + if (headerRow[key]) { + const value = headerRow[key].toString().trim(); + if (value.toLowerCase().includes("date")) { + headerKeys.date = key; + } else if (value.toLowerCase().includes("narration") || value.toLowerCase().includes("details")) { + headerKeys.narration = key; + } else if (value.toLowerCase().includes("withdrawal") || value.toLowerCase().includes("debit")) { + headerKeys.withdrawal = key; + } else if (value.toLowerCase().includes("deposit") || value.toLowerCase().includes("credit")) { + headerKeys.deposit = key; + } else if (value.toLowerCase().includes("closing balance") || value.toLowerCase().includes("balance")) { + headerKeys.balance = key; + } + } + } + } + + // 1. d. Process the rows *after* the header row as transactions. + if (headerRowIndex !== -1) { + let voucher_number = 1; + for (let i = headerRowIndex + 1; i < rawData.length; i++) { + const row = rawData[i]; + if (!row || typeof row !== 'object') continue; + + const date = formatDate(row[headerKeys.date]); + const desc = row[headerKeys.narration] ? row[headerKeys.narration].toString().trim() : null; + let amount = 0; + let type = null; + + if (headerKeys.withdrawal && row[headerKeys.withdrawal] !== null && row[headerKeys.withdrawal] !== undefined) { + amount = Number(row[headerKeys.withdrawal]); + if (!isNaN(amount)) { + type = "withdrawal"; + } + } + + if (headerKeys.deposit && row[headerKeys.deposit] !== null && row[headerKeys.deposit] !== undefined) { + const depositAmount = Number(row[headerKeys.deposit]); + if (!isNaN(depositAmount)) { + amount = depositAmount; + type = "deposit"; + } + } + + const balance = row[headerKeys.balance] !== null && row[headerKeys.balance] !== undefined ? Number(row[headerKeys.balance]) : null; + + if (date && (type === "withdrawal" || type === "deposit")) { + transactions.push({ + date, + voucher_number: voucher_number++, + amount, + desc, + from: null, + to: null, + type, + balance + }); + } + } + } + + return { + bank_details: bank_details, + transactions: transactions + }; +} + +module.exports = processBankStatement; \ No newline at end of file diff --git a/src/BANK.js b/src/BANK.js index 3da59a8..f375135 100644 --- a/src/BANK.js +++ b/src/BANK.js @@ -1,6 +1,7 @@ const BANK = { BOB: "bob", IOB: "iob", + HDFC: "hdfc", } module.exports = BANK;