Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 273 additions & 0 deletions Services/BankStatementProcessor/bankStatementProcessor-HDFC.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
/**
* 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 keyMap = {};
let voucherNumber = 1;

// Helper function to parse date strings
function parseDate(dateString) {
if (!dateString) return null;

const excelSerialDate = parseInt(dateString, 10);
if (!isNaN(excelSerialDate)) {
const excelEpoch = new Date(Date.UTC(1899, 11, 30));
const javascriptDate = new Date(excelEpoch.getTime() + excelSerialDate * 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');
return `${year}-${month}-${day}`;
}

const dateFormats = [
[/^(\d{2})\/(\d{2})\/(\d{4})$/, '$3-$2-$1'], // DD/MM/YYYY
[/^(\d{2})-(\w{3})-(\d{4})$/, (match) => {
const monthMap = {
'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
};
const month = monthMap[match[2]];
return `${match[3]}-${month}-${match[1]}`;
}],
[/^(\d{2})\/(\d{2})\/(\d{2})$/, (match) => { // DD/MM/YY
const year = `20${match[3]}`;
return `${year}-${match[2]}-${match[1]}`;
}],
[/^(\d{2})-(\d{2})-(\d{4})$/, '$3-$2-$1'], // DD-MM-YYYY
[/^(\d{4})-(\d{2})-(\d{2})$/, '$1-$2-$3'], // YYYY-MM-DD
[/^(\d{2})-(\d{2})-(\d{2})$/, (match) => { // DD-MM-YY
const year = `20${match[3]}`;
return `${year}-${match[2]}-${match[1]}`;
}],
[/^(\d{2})-(\w{3})-(\d{2})$/, (match) => {
const monthMap = {
'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'
};
const month = monthMap[match[2]];
const year = `20${match[3]}`;
return `${year}-${month}-${match[1]}`;
}],

[/^(\d{2})\.(\d{2})\.(\d{4})$/, '$3-$2-$1'], // DD.MM.YYYY
[/^(\d{2})\.(\d{2})\.(\d{2})$/, (match) => { // DD.MM.YY
const year = `20${match[3]}`;
return `${year}-${match[2]}-${match[1]}`;
}],
[/^(\d{4})\/(\d{2})\/(\d{2})$/, '$1-$2-$3'], // YYYY/MM/DD
[/^(\d{1})\/(\d{1})\/(\d{4})$/, (match) => {
const day = String(parseInt(match[1])).padStart(2, '0');
const month = String(parseInt(match[2])).padStart(2, '0');
return `${match[3]}-${month}-${day}`
}],
[/^(\d{1})\/(\d{2})\/(\d{4})$/, (match) => {
const day = String(parseInt(match[1])).padStart(2, '0');
return `${match[3]}-${match[2]}-${day}`
}],
[/^(\d{2})\/(\d{1})\/(\d{4})$/, (match) => {
const month = String(parseInt(match[2])).padStart(2, '0');
return `${match[3]}-${month}-${match[1]}`
}],
[/^(\d{1})-(\d{1})-(\d{4})$/, (match) => {
const day = String(parseInt(match[1])).padStart(2, '0');
const month = String(parseInt(match[2])).padStart(2, '0');
return `${match[3]}-${month}-${day}`
}],
[/^(\d{1})-(\d{2})-(\d{4})$/, (match) => {
const day = String(parseInt(match[1])).padStart(2, '0');
return `${match[3]}-${match[2]}-${day}`
}],
[/^(\d{2})-(\d{1})-(\d{4})$/, (match) => {
const month = String(parseInt(match[2])).padStart(2, '0');
return `${match[3]}-${month}-${match[1]}`
}],
];

for (const [format, replacement] of dateFormats) {
const match = dateString.match(format);
if (match) {
return typeof replacement === 'string' ? dateString.replace(format, replacement) : replacement(match);
}
}

return null;
}

// Helper function to extract a number from a string, handling commas and currency symbols
function extractNumber(str) {
if (typeof str === 'number') return str;
if (!str) return null;

const cleanedStr = str.toString().replace(/[^\d.-]/g, ''); // Remove currency symbols and commas, keeping only digits, dots, and hyphens
const num = parseFloat(cleanedStr);
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.hasOwnProperty(key) && row[key] !== null) {
const value = row[key].toString().trim();
const nextKey = Object.keys(row)[Object.keys(row).indexOf(key) + 1]; //get adjacent cell

if (value.toLowerCase().includes("bank name")) {
bank_details.bank_name = row[nextKey] ? row[nextKey].toString().trim() : null;
} else if (value.toLowerCase().includes("account holder name") || value.toLowerCase().includes("account name")) {
bank_details.account_holder_name = row[nextKey] ? row[nextKey].toString().trim() : null;
} else if (value.toLowerCase().includes("account no") || value.toLowerCase().includes("account number")) {
bank_details.account_no = row[nextKey] ? row[nextKey].toString().trim() : null;
} else if (value.toLowerCase().includes("ifsc") || value.toLowerCase().includes("ifs")) {
bank_details.ifsc = row[nextKey] ? row[nextKey].toString().trim() : null;
} else if (value.toLowerCase().includes("branch name")) {
bank_details.branch_name = row[nextKey] ? row[nextKey].toString().trim() : null;
}
else if (value.toLowerCase().includes("branch code")) {
bank_details.branch_code = row[nextKey] ? row[nextKey].toString().trim() : null;
}
else if (value.toLowerCase().includes("address")) {
bank_details.address = (bank_details.address || "") + (row[nextKey] ? row[nextKey].toString().trim() : "");
} else if (value.toLowerCase().includes("city")) {
bank_details.city = row[nextKey] ? row[nextKey].toString().trim() : null;
}
else if (value.toLowerCase().includes("opening balance")) {
const balanceValue = row[nextKey];
bank_details.opening_balance = extractNumber(balanceValue) || 0;
}
}
}
}
if (!bank_details.ifsc) {
for (let i = 0; i < Math.min(15, rawData.length); i++) {
const row = rawData[i];
for (const key in row) {
if (row.hasOwnProperty(key) && row[key] !== null) {
const value = row[key].toString().trim();
if (value.toLowerCase().includes("rtgs/neft ifsc")) {
const parts = value.split(':');
if (parts.length > 1) {
bank_details.ifsc = parts[1].trim().split(' ')[0];
break;
}
}
}
}
if(bank_details.ifsc) break;
}
}
// 2. Identify Header Row & Create Key Mapping
for (let i = 0; i < rawData.length; i++) {
const row = rawData[i];
let isHeaderRow = false;
const headerValues = ["date", "narration", "description",
"details", "withdrawal", "debit",
"deposit", "credit", "balance",
"closing balance"];

let foundCount = 0;
for (const key in row) {
if (row.hasOwnProperty(key) && row[key] !== null) {
const value = row[key].toString().toLowerCase().trim();
if (headerValues.some(hv => value.includes(hv))) {
foundCount++;
}
}
}

if (foundCount >= 5) { //Adjust threshold if needed
isHeaderRow = true;
}


if (isHeaderRow) {
headerRowIndex = i;
for (const key in row) {
if (row.hasOwnProperty(key) && row[key] !== null) {
const value = row[key].toString().toLowerCase().trim();
if (value.includes("date")) {
keyMap.dateKey = key;
} else if (value.includes("narration") || value.includes("description") || value.includes("details")) {
keyMap.narrationKey = key;
} else if (value.includes("debit") || value.includes("withdrawal")) {
keyMap.debitKey = key;
} else if (value.includes("credit") || value.includes("deposit")) {
keyMap.creditKey = key;
} else if (value.includes("balance") || value.includes("closing balance")) {
keyMap.balanceKey = key;
}
}
}
break;
}
}

// 3. Process Transactions
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

const dateString = keyMap.dateKey ? row[keyMap.dateKey] : null;
const desc = keyMap.narrationKey ? (row[keyMap.narrationKey] || '').toString().trim() : null;
const debitStr = keyMap.debitKey ? row[keyMap.debitKey] : null;
const creditStr = keyMap.creditKey ? row[keyMap.creditKey] : null;
const balanceStr = keyMap.balanceKey ? row[keyMap.balanceKey] : null;

const date = parseDate(dateString);
const debit = extractNumber(debitStr);
const credit = extractNumber(creditStr);
const balance = extractNumber(balanceStr);

if (!date && !desc && debit === null && credit === null && balance === null) continue; // Skip non-transaction rows

let type = null;
let amount = null;

if (debit !== null) {
type = "withdrawal";
amount = Math.abs(debit);
} else if (credit !== null) {
type = "deposit";
amount = Math.abs(credit);
}

if (type && amount !== null) {
const transaction = {
date: date,
voucher_number: voucherNumber++,
amount: amount,
desc: desc,
from: null,
to: null,
type: type,
balance: balance !== null ? balance : null
};

transactions.push(transaction);
}
}
}

return {
bank_details: bank_details,
transactions: transactions
};
}

module.exports = processBankStatement;
1 change: 1 addition & 0 deletions src/BANK.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const BANK = {
BOB: "bob",
IOB: "iob",
HDFC: "hdfc",
}

module.exports = BANK;