diff --git a/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js b/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js new file mode 100644 index 0000000..2ec2323 --- /dev/null +++ b/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js @@ -0,0 +1,215 @@ +/** + * Generated by Gemini AI - Attempt 1 + * 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, + }; + + let headerRowIndex = -1; + let keyMapping = {}; + const transactions = []; + let voucher_number = 1; + + // Bank Details Extraction + for (let i = 0; i < Math.min(15, rawData.length); i++) { + const row = rawData[i]; + for (const key in row) { + if (row.hasOwnProperty(key)) { + const value = row[key]; + if (typeof value === 'string') { + const lowerValue = value.toLowerCase(); + if (lowerValue.includes("bank name")) { + const adjacentKey = Object.keys(row).find(k => k !== key && row[k] != null); + if (adjacentKey) { + bank_details.bank_name = row[adjacentKey] || null; + } + } else if (lowerValue.includes("account holder name") || lowerValue.includes("account name")) { + const adjacentKey = Object.keys(row).find(k => k !== key && row[k] != null); + if (adjacentKey) { + bank_details.account_holder_name = row[adjacentKey] || null; + } + } else if (lowerValue.includes("account no") || lowerValue.includes("account number")) { + const adjacentKey = Object.keys(row).find(k => k !== key && row[k] != null); + if (adjacentKey) { + bank_details.account_no = row[adjacentKey] || null; + } + } else if (lowerValue.includes("ifsc") || lowerValue.includes("ifs")) { + const adjacentKey = Object.keys(row).find(k => k !== key && row[k] != null); + if (adjacentKey) { + bank_details.ifsc = row[adjacentKey] || null; + } else { + bank_details.ifsc = value + } + } else if (lowerValue.includes("branch name")) { + const adjacentKey = Object.keys(row).find(k => k !== key && row[k] != null); + if (adjacentKey) { + bank_details.branch_name = row[adjacentKey] || null; + } + } else if (lowerValue.includes("branch code")) { + const adjacentKey = Object.keys(row).find(k => k !== key && row[k] != null); + if (adjacentKey) { + bank_details.branch_code = row[adjacentKey] || null; + } + } else if (lowerValue.includes("address")) { + const adjacentKey = Object.keys(row).find(k => k !== key && row[k] != null); + if (adjacentKey && typeof row[adjacentKey] === 'string') { + bank_details.address = (bank_details.address || '') + (row[adjacentKey] || ''); + } else if (typeof value === 'string') { + bank_details.address = (bank_details.address || '') + value; + } + } + } + } + } + } + + // Header Row Identification & Key Mapping + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + let hasDate = false; + let hasNarration = false; + let hasDebit = false; + let hasCredit = false; + let hasBalance = false; + + for (const key in row) { + if (row.hasOwnProperty(key) && typeof row[key] === 'string') { + const lowerValue = row[key].toLowerCase(); + if (lowerValue.includes("date")) { + keyMapping.dateKey = key; + hasDate = true; + } else if (lowerValue.includes("narration") || lowerValue.includes("description") || lowerValue.includes("details")) { + keyMapping.narrationKey = key; + hasNarration = true; + } else if (lowerValue.includes("debit") || lowerValue.includes("withdrawal")) { + keyMapping.debitKey = key; + hasDebit = true; + } else if (lowerValue.includes("credit") || lowerValue.includes("deposit")) { + keyMapping.creditKey = key; + hasCredit = true; + } else if (lowerValue.includes("balance") || lowerValue.includes("closing balance")) { + keyMapping.balanceKey = key; + hasBalance = true; + } + } + } + + if (hasDate && hasNarration && (hasDebit || hasCredit) && hasBalance) { + headerRowIndex = i; + break; + } + } + + // Transaction Processing + if (headerRowIndex !== -1) { + for (let i = headerRowIndex + 1; i < rawData.length; i++) { + const row = rawData[i]; + if (!row || Object.keys(row).length === 0) { + continue; // Skip empty rows + } + let date = row[keyMapping.dateKey] || null; + let desc = row[keyMapping.narrationKey] ? (typeof row[keyMapping.narrationKey] === 'string' ? row[keyMapping.narrationKey].trim() : String(row[keyMapping.narrationKey])) : null; + let debit = row[keyMapping.debitKey] || null; + let credit = row[keyMapping.creditKey] || null; + let balance = row[keyMapping.balanceKey] || null; + + if (!date && !desc && !debit && !credit && !balance) continue; + + let amount = null; + let type = null; + + if (debit && !isNaN(Number(debit))) { + amount = Math.abs(Number(debit.toString().replace(/[^0-9.-]+/g,""))); + type = "withdrawal"; + } else if (credit && !isNaN(Number(credit))) { + amount = Math.abs(Number(credit.toString().replace(/[^0-9.-]+/g,""))); + type = "deposit"; + } + if (!amount && amount !== 0) continue; + + if (balance && !isNaN(Number(balance))) { + balance = Number(balance.toString().replace(/[^0-9.-]+/g,"")); + } else { + continue + } + + if (date) { + try { + if (typeof date === 'number') { + // Excel date serial number + const excelEpoch = new Date(Date.UTC(1899, 11, 31)); + const javascriptDate = new Date(excelEpoch.getTime() + (date * 24 * 60 * 60 * 1000)); + const year = javascriptDate.getFullYear(); + const month = String(javascriptDate.getMonth() + 1).padStart(2, '0'); + const day = String(javascriptDate.getDate()).padStart(2, '0'); + date = `${year}-${month}-${day}`; + } else { + const dateFormats = [ + /^\d{2}[./-]\d{2}[./-]\d{4}$/, + /^\d{4}[./-]\d{2}[./-]\d{2}$/, + /^\d{2}-\w{3}-\d{4}$/i + ]; + + if (dateFormats[0].test(date)) { + const parts = date.split(/[-/.]/); + const day = parts[0]; + const month = parts[1]; + const year = parts[2]; + date = `${year}-${month}-${day}`; + } else if (dateFormats[1].test(date)) { + const parts = date.split(/[-/.]/); + const year = parts[0]; + const month = parts[1]; + const day = parts[2]; + date = `${year}-${month}-${day}`; + } + else if (dateFormats[2].test(date)) { + const parts = date.split('-'); + const day = parts[0]; + const month = parts[1]; + const year = parts[2]; + const monthNumber = new Date(Date.parse(month +" 1, 2000")).getMonth() + 1 + date = `${year}-${String(monthNumber).padStart(2,'0')}-${day}`; + } else { + date = null + } + } + } + catch (e) { + date = null + } + } + if (!date) continue; + const transaction = { + date: date, + voucher_number: voucher_number++, + amount: amount, + desc: desc, + from: null, + to: null, + type: type, + balance: balance + }; + transactions.push(transaction); + } + } + + 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;