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..5a3b698 --- /dev/null +++ b/Services/BankStatementProcessor/bankStatementProcessor-HDFC.js @@ -0,0 +1,229 @@ +/** + * Generated by Gemini AI - Attempt 2 + * 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 date as YYYY-MM-DD + function formatDate(dateString) { + if (!dateString) return null; + try { + const dateParts = dateString.split(/[-/]/); + if (dateParts.length === 3) { + const day = parseInt(dateParts[0]); + const month = parseInt(dateParts[1]); + const year = parseInt(dateParts[2]); + + if (isNaN(day) || isNaN(month) || isNaN(year)) { + return null; + } + + const formattedYear = (year < 100) ? (year < 50 ? `20${year}` : `19${year}`) : year; + + return `${formattedYear}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`; + } + return null; + } catch (error) { + return null; + } + } + + // Helper function to safely convert to number + function safeNumber(value) { + if (value === null || value === undefined) { + return null; + } + const num = Number(value); + return isNaN(num) ? null : num; + } + + // 1. Extract 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("MR") || value.toUpperCase().includes("MS"))) { + bank_details.account_holder_name = value; + } + + if (!bank_details.address && value.toUpperCase().includes("ADDRESS")) { + bank_details.address = value.replace(/Address\s*:\s*/i, ''); + } + + if (!bank_details.city && value.toUpperCase().includes("CITY")) { + bank_details.city = value.replace(/City\s*:\s*/i, ''); + } + + if (!bank_details.branch_name && value.toUpperCase().includes("Account Branch")) { + bank_details.branch_name = value.replace(/Account Branch\s*:\s*/i, ''); + } + + if (!bank_details.account_no && value.toUpperCase().includes("ACCOUNT NO")) { + bank_details.account_no = value.replace(/Account No\s*: */i, '').split(" ")[0]; + } + + const ifscKeywords = ['IFSC', 'ifsc', 'Ifsc', 'RTGS/NEFT IFSC','NEFT/RTGS IFSC', 'Neft/Rtgs IFSC', 'rtgs/neft ifsc']; + if (ifscKeywords.some(keyword => value.includes(keyword))) { + const ifscValue = value.replace(new RegExp(ifscKeywords.join('|'), 'gi'), '').replace(/:/g, '').trim().split(" ")[0]; + bank_details.ifsc = ifscValue; + if (!bank_details.address && value.toUpperCase().includes("ADDRESS")) { + bank_details.address = value.replace(/Address\s*:\s*/i, ''); + } + } + } + } + } + + // 2. Identify header row and keys + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + let hasDate = false; + let hasNarration = false; + let hasWithdrawal = false; + let hasDeposit = false; + let hasBalance = false; + + for (const key in row) { + if (row[key]) { + const value = row[key].toString().trim().toUpperCase(); + if (value === "DATE") hasDate = true; + else if (value === "NARRATION" || value === "NARRATION OR DETAILS" || value === "DETAILS") hasNarration = true; + else if (value === "WITHDRAWAL AMT." || value === "WITHDRAWAL AMT. OR DEBIT" || value === "WITHDRAWAL" || value === "DEBIT") hasWithdrawal = true; + else if (value === "DEPOSIT AMT." || value === "DEPOSIT AMT. OR CREDIT" || value === "DEPOSIT" || value === "CREDIT") hasDeposit = true; + else if (value === "CLOSING BALANCE" || value === "CLOSING BALANCE OR BALANCE" || value === "BALANCE") hasBalance = true; + } + } + + if (hasDate && hasNarration && (hasWithdrawal || hasDeposit) && hasBalance) { + headerRowIndex = i; + headerKeys = row; + break; + } + } + + if (headerRowIndex === -1) { + console.warn("Could not find header row."); + return { bank_details, transactions }; + } + + const dateKey = Object.keys(headerKeys).find(key => headerKeys[key] && headerKeys[key].toString().trim().toUpperCase() === "DATE"); + const narrationKey = Object.keys(headerKeys).find(key => headerKeys[key] && (headerKeys[key].toString().trim().toUpperCase() === "NARRATION" || headerKeys[key].toString().trim().toUpperCase() === "NARRATION OR DETAILS" || headerKeys[key].toString().trim().toUpperCase() === "DETAILS")); + const withdrawalKey = Object.keys(headerKeys).find(key => headerKeys[key] && (headerKeys[key].toString().trim().toUpperCase() === "WITHDRAWAL AMT." || headerKeys[key].toString().trim().toUpperCase() === "WITHDRAWAL AMT. OR DEBIT" || headerKeys[key].toString().trim().toUpperCase() === "WITHDRAWAL" || headerKeys[key].toString().trim().toUpperCase() === "DEBIT")); + const depositKey = Object.keys(headerKeys).find(key => headerKeys[key] && (headerKeys[key].toString().trim().toUpperCase() === "DEPOSIT AMT." || headerKeys[key].toString().trim().toUpperCase() === "DEPOSIT AMT. OR CREDIT" || headerKeys[key].toString().trim().toUpperCase() === "DEPOSIT" || headerKeys[key].toString().trim().toUpperCase() === "CREDIT")); + const balanceKey = Object.keys(headerKeys).find(key => headerKeys[key] && (headerKeys[key].toString().trim().toUpperCase() === "CLOSING BALANCE" || headerKeys[key].toString().trim().toUpperCase() === "CLOSING BALANCE OR BALANCE" || headerKeys[key].toString().trim().toUpperCase() === "BALANCE")); + + // 3. Process transaction rows + for (let i = headerRowIndex + 1; i < rawData.length; i++) { + const row = rawData[i]; + if (!row) continue; + + const dateValue = row[dateKey]; + const narration = row[narrationKey] ? row[narrationKey].toString().trim() : null; + const withdrawalAmount = safeNumber(row[withdrawalKey]); + const depositAmount = safeNumber(row[depositKey]); + const balance = safeNumber(row[balanceKey]); + + if (!dateValue && !narration && !withdrawalAmount && !depositAmount && !balance) continue; //Skip empty row + + const date = formatDate(dateValue); + + let type = null; + let amount = null; + + if (withdrawalAmount !== null) { + type = "withdrawal"; + amount = withdrawalAmount; + } else if (depositAmount !== null) { + type = "deposit"; + amount = depositAmount; + } + + if (amount === null) { + continue; // Skip if amount is null + } + + const transaction = { + date, + voucher_number: voucher_number++, + amount: amount !== null ? Number(amount) : null, + desc: narration, + from: null, + to: null, + type, + balance + }; + + transactions.push(transaction); + } + + // Try to extract opening balance from statement + let openingBalanceFound = false; + for (let i = 0; i < rawData.length; i++) { + const row = rawData[i]; + for (const key in row) { + if(row[key]){ + const value = row[key].toString().trim().toUpperCase(); + + if (value.includes('OPENING BALANCE')){ + + let value_open=rawData[i][Object.keys(rawData[i])[0]] + + if (typeof value_open === 'number'){ + bank_details.opening_balance = value_open; + openingBalanceFound = true; + break; + } + + } + } + } + if (openingBalanceFound) break; + } + + let addressString = ''; + for (let i = 0; i < Math.min(10, rawData.length); i++) { + const row = rawData[i]; + for (const key in row) { + if (row[key] && typeof row[key] === 'string') { + const value = row[key].trim(); + if (!bank_details.address && value.toUpperCase().includes("ADDRESS")) { + addressString += value.replace(/Address\s*:\s*/i, ''); + } + + if (!bank_details.city && value.toUpperCase().includes("CITY")) { + addressString += value.replace(/City\s*:\s*/i, ''); + } + } + } + } + if (!bank_details.address && addressString != ''){ + bank_details.address = addressString + } + + return { bank_details, transactions }; +} + +module.exports = processBankStatement; \ No newline at end of file