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
300 changes: 300 additions & 0 deletions Services/BankStatementProcessor/bankStatementProcessor-HDFC.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
/**
* 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;
const headerKeys = {};

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

if (typeof dateString !== 'string') {
if (typeof dateString === 'number') {
const excelEpoch = new Date(Date.UTC(1899, 11, 30));
const jsDate = new Date(excelEpoch.getTime() + (dateString * 24 * 60 * 60 * 1000));
const year = jsDate.getFullYear();
const month = String(jsDate.getMonth() + 1).padStart(2, '0');
const day = String(jsDate.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
return null;
}

const excelSerialDateRegex = /^\d+$/;
if (excelSerialDateRegex.test(dateString)) {
const excelSerial = parseInt(dateString, 10);
const excelEpoch = new Date(Date.UTC(1899, 11, 30));
const jsDate = new Date(excelEpoch.getTime() + (excelSerial * 24 * 60 * 60 * 1000));
const year = jsDate.getFullYear();
const month = String(jsDate.getMonth() + 1).padStart(2, '0');
const day = String(jsDate.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}

const dateFormats = [
[/^(\d{2})\/(\d{2})\/(\d{4})$/, (m, d, M, Y) => `${Y}-${M}-${d}`], // DD/MM/YYYY
[/^(\d{2})-(\w{3})-(\d{4})$/, (m, d, Mon, Y) => {
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'
};
return `${Y}-${monthMap[Mon] || '00'}-${d}`;
}], // DD-Mon-YYYY
[/^(\d{2})\/(\d{2})\/(\d{2})$/, (m, d, M, Y) => {
const fullYear = `20${Y}`;
return `${fullYear}-${M}-${d}`
}], // DD/MM/YY
[/^(\d{2})-(\w{3})-(\d{2})$/, (m, d, Mon, Y) => {
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 fullYear = `20${Y}`;
return `${fullYear}-${monthMap[Mon] || '00'}-${d}`;
}], // DD-Mon-YY
[/^(\d{2})\/(\d{2})\/(\d{4})$/, (m, M, d, Y) => `${Y}-${M}-${d}`], // MM/DD/YYYY
[/^(\d{4})-(\d{2})-(\d{2})$/, (m, Y, M, d) => `${Y}-${M}-${d}`], // YYYY-MM-DD
];

for (const [regex, formatter] of dateFormats) {
if (typeof dateString === 'string') {
const match = dateString.match(regex);
if (match) {
return formatter(match[0], match[1], match[2], match[3]);
}
}
}

return null; // Indicate failure to parse
}

// Helper function to convert to Number
function convertToNumber(value) {
if (value === null || value === undefined) return null;
if (typeof value === 'number') return value;
if (typeof value !== 'string') return NaN;

const cleanedValue = value.replace(/[^0-9.-]+/g, '');
const num = Number(cleanedValue);
return isNaN(num) ? null : num;
}


// 1. Extract Bank Details
for (let i = 0; i < Math.min(rawData.length, 15); 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 bankNameKey = Object.keys(row).find(k => k !== key);
if (bankNameKey && row[bankNameKey]) {
bank_details.bank_name = String(row[bankNameKey]).trim();
}
} else if (lowerValue.includes("account holder name")) {
const accountHolderNameKey = Object.keys(row).find(k => k !== key);
if (accountHolderNameKey && row[accountHolderNameKey]) {
bank_details.account_holder_name = String(row[accountHolderNameKey]).trim();
}
} else if (lowerValue.includes("account no") || lowerValue.includes("account number")) {
const accountNoKey = Object.keys(row).find(k => k !== key);
if (accountNoKey && row[accountNoKey]) {
bank_details.account_no = String(row[accountNoKey]).trim();
}
} else if (lowerValue.includes("ifsc") || lowerValue.includes("ifs")) {
const ifscKey = Object.keys(row).find(k => k !== key);
if (ifscKey && row[ifscKey]) {
bank_details.ifsc = String(row[ifscKey]).trim();
}
} else if (lowerValue.includes("branch name")) {
const branchNameKey = Object.keys(row).find(k => k !== key);
if(branchNameKey && row[branchNameKey]) {
bank_details.branch_name = String(row[branchNameKey]).trim();
}

} else if (lowerValue.includes("address")) {
const addressKey = Object.keys(row).find(k => k !== key);
if (addressKey && row[addressKey]) {
bank_details.address = String(row[addressKey]).trim();
}
}
}
}
}
}

// Extract opening balance (simplified - assuming it is in one of the rows close to bank details)
for (let i = 0; i < Math.min(rawData.length, 15); i++) {
const row = rawData[i];
for (const key in row) {
if (row.hasOwnProperty(key)) {
const value = row[key];
if (typeof value === 'string' && value.toLowerCase().includes("opening balance")) {
const openingBalanceKey = Object.keys(row).find(k => k !== key);
if (openingBalanceKey) {
const potentialBalance = convertToNumber(row[openingBalanceKey]);
if(potentialBalance !== null)
{
bank_details.opening_balance = potentialBalance;
}

}
}
}
}
}

if(bank_details.ifsc === null)
{
for (let i = 0; i < Math.min(rawData.length, 15); i++) {
const row = rawData[i];
for (const key in row) {
if (row.hasOwnProperty(key)) {
const value = row[key];
if (typeof value === 'string') {
const ifscRegex = /IFSC *: *([A-Za-z0-9]+)/i;
const match = value.match(ifscRegex);
if (match) {
bank_details.ifsc = match[1].trim();
break;
}
}
}
if(bank_details.ifsc !== null) break;
}
if(bank_details.ifsc !== null) break;
}
}


// 2. Identify Header Row & Create Key Mapping
for (let i = 0; i < rawData.length; i++) {
const row = rawData[i];
let dateFound = false;
let narrationFound = false;
let debitFound = false;
let creditFound = false;
let balanceFound = false;

for (const key in row) {
if (row.hasOwnProperty(key) && typeof row[key] === 'string') {
const value = row[key].toLowerCase();

if (value.includes("date") || value.includes("txn date")) {
headerKeys.dateKey = key;
dateFound = true;
}
if (value.includes("narration") || value.includes("description") || value.includes("details")) {
headerKeys.narrationKey = key;
narrationFound = true;
}
if (value.includes("debit") || value.includes("withdrawal")) {
headerKeys.debitKey = key;
debitFound = true;
}
if (value.includes("credit") || value.includes("deposit")) {
headerKeys.creditKey = key;
creditFound = true;
}
if (value.includes("balance") || value.includes("closing balance")) {
headerKeys.balanceKey = key;
balanceFound = true;
}
}
}

if (dateFound && narrationFound && (debitFound || creditFound) && balanceFound) {
headerRowIndex = i;
break;
}
}

// 3. Process Transactions
if (headerRowIndex !== -1) {
let voucherNumber = 1;
for (let i = headerRowIndex + 1; i < rawData.length; i++) {
const row = rawData[i];
if (!row) continue; // Skip empty rows

const dateString = row[headerKeys.dateKey];
const desc = row[headerKeys.narrationKey] ? String(row[headerKeys.narrationKey]).trim() : null;
const debitValue = convertToNumber(row[headerKeys.debitKey]);
const creditValue = convertToNumber(row[headerKeys.creditKey]);
const balanceValue = convertToNumber(row[headerKeys.balanceKey]);

if (!dateString && (!debitValue && !creditValue) && balanceValue === null) continue;

const date = parseDate(dateString);
if (!date) continue; //Skip if date parse fails

let type = null;
let amount = null;

if (debitValue !== null) {
type = "withdrawal";
amount = Math.abs(debitValue); // Store amount as positive
} else if (creditValue !== null) {
type = "deposit";
amount = Math.abs(creditValue); // Store amount as positive
} else {
continue; // Skip transactions with neither debit nor credit
}

const transaction = {
date: date,
voucher_number: voucherNumber++,
amount: amount,
desc: desc,
from: null,
to: null,
type: type,
balance: balanceValue
};

// Basic from/to parsing from desc (example, can be extended)
if (desc) {
const upiRegex = /([a-zA-Z0-9.-]+@[a-zA-Z0-9.-]+)/; //Basic UPI ID pattern
const upiMatch = desc.match(upiRegex);

if (upiMatch && upiMatch[1]) {
if (transaction.type === 'withdrawal') {
transaction.to = upiMatch[1];
} else {
transaction.from = upiMatch[1];
}
}
}
transactions.push(transaction);
}
}

if(bank_details.ifsc === null) {
bank_details.ifsc = "";
}

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;