From 2b825a3acb8d7137cc5a3c1c90e3bae2aa063645 Mon Sep 17 00:00:00 2001 From: satyam-19 Date: Tue, 29 Apr 2025 18:24:27 +0530 Subject: [PATCH] feat: Add/update bank processor for HDFC --- BANK.js | 7 + .../bankStatementProcessor-HDFC.js | 198 ++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 BANK.js create mode 100644 Services/BankStatementProcessor/bankStatementProcessor-HDFC.js diff --git a/BANK.js b/BANK.js new file mode 100644 index 0000000..f375135 --- /dev/null +++ b/BANK.js @@ -0,0 +1,7 @@ +const BANK = { + BOB: "bob", + IOB: "iob", + HDFC: "hdfc", +} + +module.exports = BANK; diff --git a/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js b/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js new file mode 100644 index 0000000..f424e1d --- /dev/null +++ b/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js @@ -0,0 +1,198 @@ +/** + * Generated by Gemini AI - Attempt 3 + * 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 = {}; + let voucher_number = 1; + + // Helper function to format dates to 'YYYY-MM-DD' + function formatDate(dateString) { + try { + if (!dateString) return null; + + let date; + + // Attempt to parse various date formats + if (typeof dateString === 'number') { + date = new Date((dateString - 25569) * 86400 * 1000); // Excel serial date + } + else if (typeof dateString === 'string') { + date = new Date(dateString); // Attempt standard parsing + if(isNaN(date)){ // if standard parsing fails. + const dateParts = dateString.split(/[-/]/); + if (dateParts.length === 3) { + const day = parseInt(dateParts[0], 10); + const month = parseInt(dateParts[1], 10) - 1; + const year = parseInt(dateParts[2], 10); + date = new Date(year, month, day); + } + } + } else { + return null; + } + + + if (isNaN(date)) return null; + + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } catch (e) { + console.error("Error formatting date:", dateString, e); + return null; + } + } + + // Helper function to convert to number, handling null/undefined/string values. + function convertToNumber(value) { + if (value === null || value === undefined) { + return 0; + } + const num = Number(value); + return isNaN(num) ? 0 : num; + } + + + // 1. Extract Bank Details + for (let i = 0; i < Math.min(15, rawData.length); i++) { // Check first 15 rows + const row = rawData[i]; + for (const key in row) { + const value = row[key]; + if (typeof value === 'string') { + if (!bank_details.bank_name && value.includes('BANK')) { + bank_details.bank_name = value.split('BANK')[0].trim() + " BANK"; + } + if (!bank_details.account_holder_name && value.toUpperCase().includes('MR') || value.toUpperCase().includes('MS')) { + bank_details.account_holder_name = value; + } + + if (!bank_details.address && (value.toUpperCase().includes('ADDRESS') || value.toUpperCase().includes('ADDR'))) + { + bank_details.address=value; + } + if(!bank_details.city && value.toUpperCase().includes('CITY')) + { + bank_details.city=value; + } + if (!bank_details.branch_name && value.toUpperCase().includes('ACCOUNT BRANCH')) { + bank_details.branch_name = value; + } + } + if (!bank_details.ifsc) { + const ifscKeywords = ['IFSC', 'ifsc', 'IFSC Code', 'ifsc Code','RTGS/NEFT IFSC']; + if(key.toLowerCase().includes('ifsc') || ifscKeywords.some(keyword => row[key] === keyword || (typeof row[key] === 'string' && row[key]?.toUpperCase().includes(keyword.toUpperCase())))) { + if(typeof value === 'string' && value.length > 0){ + bank_details.ifsc = value; + }else if (typeof value === 'string' && value.length==0 && typeof row[key] === 'string'){ + bank_details.ifsc = row[key]; + } else if(typeof row[key] === 'string'){ + bank_details.ifsc = row[key]; + } + } + + } + if(!bank_details.account_no && (key.toLowerCase().includes('account no') || key.toLowerCase().includes('account number'))) + { + if(typeof value === 'string' || typeof value === 'number'){ + bank_details.account_no = value + } + } + } + } + + // 2. Identify Header Row and Keys + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + const headerValues = Object.values(row).filter(value => typeof value === 'string'); + if ( + headerValues.includes("Date") && + (headerValues.includes("Narration") || headerValues.includes("Narration or Details") || headerValues.includes("Narration/Description")) && + (headerValues.includes("Withdrawal Amt.") || headerValues.includes("Withdrawal Amt. or Debit") ||headerValues.includes("Withdrawal")) && + (headerValues.includes("Deposit Amt.") || headerValues.includes("Deposit Amt. or Credit")|| headerValues.includes("Deposit")) && + (headerValues.includes("Closing Balance") || headerValues.includes("Closing Balance or Balance") || headerValues.includes("Balance")) + ) { + headerRowIndex = i; + headerKeys = row; + break; + } + } + + if (headerRowIndex === -1) { + console.warn("Could not find header row."); + return { bank_details, transactions }; // Return empty results if header not found + } + + const dateKey = Object.keys(headerKeys).find(key => headerKeys[key] === "Date"); + const narrationKey = Object.keys(headerKeys).find(key => headerKeys[key] === "Narration" || headerKeys[key] === "Narration or Details" || headerKeys[key] === "Narration/Description"); + const withdrawalKey = Object.keys(headerKeys).find(key => headerKeys[key] === "Withdrawal Amt." || headerKeys[key] === "Withdrawal Amt. or Debit" || headerKeys[key] === "Withdrawal"); + const depositKey = Object.keys(headerKeys).find(key => headerKeys[key] === "Deposit Amt." || headerKeys[key] === "Deposit Amt. or Credit" || headerKeys[key] === "Deposit"); + const balanceKey = Object.keys(headerKeys).find(key => headerKeys[key] === "Closing Balance" || headerKeys[key] === "Closing Balance or Balance" || headerKeys[key] === "Balance"); + + // 3. Process Transactions + for (let i = headerRowIndex + 1; i < rawData.length; i++) { + const row = rawData[i]; + if (!row) continue; // Skip empty rows + + const dateValue = row[dateKey]; + const desc = row[narrationKey] || ""; + let amount = 0; + let type = null; + + const withdrawalAmount = convertToNumber(row[withdrawalKey]); + const depositAmount = convertToNumber(row[depositKey]); + + if (withdrawalAmount) { + type = "withdrawal"; + amount = withdrawalAmount; + } else if (depositAmount) { + type = "deposit"; + amount = depositAmount; + } + + const balance = convertToNumber(row[balanceKey]); + + const transaction = { + date: formatDate(dateValue), + voucher_number: voucher_number++, + amount: amount, + desc: desc, + from: null, + to: null, + type: type, + balance: balance + }; + + transactions.push(transaction); + } + + const openingBalanceRow = rawData.find(row => Object.values(row).some(val => typeof val === 'string' && val.toLowerCase().includes("opening balance"))); + + if(openingBalanceRow){ + for (const key in openingBalanceRow){ + if(typeof openingBalanceRow[key] === 'number'){ + bank_details.opening_balance = openingBalanceRow[key]; + break; + } + } + } + return { bank_details, transactions }; +} + +module.exports = processBankStatement; \ No newline at end of file