├── .gitignore ├── screenshot.png ├── .claspignore ├── src ├── Main.js ├── UserInterface.js ├── HelperFunctions.js └── CreditCardParsers.js ├── Main.js ├── UserInterface.js ├── README.md ├── AnalysisSpreadsheetExplained.md ├── HelperFunctions.js └── CreditCardParsers.js /.gitignore: -------------------------------------------------------------------------------- 1 | .clasp.json 2 | appsscript.json 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baruchiro/creditCardAnalysis/master/screenshot.png -------------------------------------------------------------------------------- /.claspignore: -------------------------------------------------------------------------------- 1 | # ignore all files… 2 | **/** 3 | 4 | # except the extensions… 5 | !appsscript.json 6 | !**/*.gs 7 | !**/*.js 8 | !**/*.ts 9 | !**/*.html 10 | 11 | # ignore even valid files if in… 12 | .git/** 13 | node_modules/** 14 | -------------------------------------------------------------------------------- /src/Main.js: -------------------------------------------------------------------------------- 1 | function main(){ 2 | const analysisFileId = PropertiesService.getDocumentProperties().getProperty('ID_ANALYSIS_FILE'); 3 | const analysisFile = SpreadsheetApp.openById(analysisFileId); 4 | const analysisDbSheet = analysisFile.getSheetByName('DB'); 5 | const analysisCategoriesSheet = analysisFile.getSheetByName('Categories'); 6 | const analysisStatusSheet = analysisFile.getSheetByName('_status'); 7 | const idReportFolder = PropertiesService.getDocumentProperties().getProperty('ID_REPORTS_FOLDER'); 8 | const newFiles = get_new_files_by_list(idReportFolder, analysisStatusSheet); 9 | var categoriesTable = analysisCategoriesSheet.getDataRange().getValues(); 10 | 11 | for (var i=0; iBuy Me A Coffee 2 | 3 | # Description 4 | This is a project that aims to use files produced by Israeli credit card companies, organizes them in a neat table and provide a basic dashboard (see screenshot below). 5 | 6 | ![Google DataStudio Screenshot](/screenshot.png) 7 | 8 | # Installation 9 | 10 | 1. Create a new folder on Google Drive where you will store the exported reports. Call it however you like (mine is called "Credit Card Exported Files"). 11 | Enter the folder and make sure to copy the folder ID from the URL (should look like https://drive.google.com/drive/folders/) 12 | 2. Make your own copy of the [template sheet file][https://docs.google.com/spreadsheets/d/1cFWcpH2fhjfQh6ziOo9KEUYCI86uA7WOyUWJMKHLTSM/edit#gid=733610508] 13 | Note: make sure the duplicated file is NOT inside the folder created on stage 1. 14 | 3. Cope the new spreadhseet file ID (should look like https://docs.google.com/spreadsheets/d//...) 15 | 4. Inside the sheet file, select "Tools->Script Editor" from the menu 16 | 5. Look for the following parameters and paste the relevant ID from the steps above: 17 | ID_ANALYSIS_FILE = '' 18 | ID_REPORTS_FOLDER = '' 19 | Make sure to select "File->Save" from the menu and close the window. 20 | 21 | That's it! the file should be ready now. 22 | 23 | # Usage 24 | A. Place an exported report inside the folder created on stage 1 of the installation. 25 | Make sure to convert the file to a google sheet format. 26 | B. Inside the sheet file, select "Credit Card Analysis->Check For New Files" 27 | C. Wait for the "DB" sheet to fill up with the recent data 28 | D. Under "Categories" sheet, all the businesses that are unclassified will appear under the "ללא סיווג" column. 29 | Copy each business to the relevant category. 30 | Once you do - the business name should disappear from the "ללא סיווג" column and the chosen categoory will appear near every relevant transaction in the "DB " sheet. 31 | 32 | # Tips And Tricks 33 | - Instead of manually converting each file to Google Sheet, you may configure your GDrive to automatically convert every office files to Google format. It will save some tedious work 34 | -------------------------------------------------------------------------------- /AnalysisSpreadsheetExplained.md: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | This script analyzes Israeli credit cards 4 | and builds a db people can build nice dashboards from. 5 | Instructions for a new Sheet: 6 | 1. make sure the new spreadsheet has the following tabs: 7 | - DB 8 | - Categories 9 | - _status (a utility tab to summarize some data for control purposes) 10 | - _categories_flattened (a utility tab to hold temporary calculations) 11 | - _conversion_rates (a utility tab to hold temporary calculations) 12 | 13 | 2. Inside DB sheet (tab) - make sure the following headers appear on the first row: 14 | - Date Added 15 | - File ID 16 | - File Name 17 | - Card Type 18 | - Card Number 19 | - Billing Date 20 | - Transaction Date 21 | - Business Name 22 | - Amount 23 | - Currency 24 | - Category 25 | -- this column is created automatically by using the following formula: 26 | "=arrayformula(iferror(vlookup(F2:F,'_categories_flattened'!A2:B,2,FALSE),"ללא סיווג"))" 27 | - Conversion Rate 28 | -- this column is created automatically by using the following formula: 29 | "=arrayformula(if(J2:J="ILS",1,IF(J2:J="USD",VLOOKUP(G2:G+1,'_conversion_rates'!A4:B,2,TRUE),IF(J2:J="EUR",VLOOKUP(G2:G+1,'_conversion_rates'!C4:D,2,TRUE),"ERR"))))" 30 | - Amount ILS 31 | -- this column is created automatically by using the following formula: 32 | "=arrayformula(I2:I*L2:L)" 33 | 34 | 3. Categories sheet 35 | - This tab is handled manually (you can choose your own categories and where each business goes) 36 | - The only mandatory column is the last one which is "ללא סיווג". Its first row should run the following formula: 37 | "=QUERY(DB!F2:I,"select F where I='ללא סיווג'",0)" 38 | 39 | 4. Inside _status sheet - make sure the following headers appear: 40 | -File Name 41 | -File ID 42 | 43 | 5. _categories_flattened sheet 44 | This tab organizes the categories table in a more processing-efficient way. 45 | Headers: 46 | - Business name 47 | -- "=filter(flatten(Categories!A2:O),NOT(ISBLANK(flatten(Categories!A2:O))))" 48 | - Category 49 | -- "=arrayformula(reverseLookup(A2:A,transpose(Categories!A:O)))" 50 | 51 | 6. _conversion_rates sheet 52 | used formulas: 53 | A1: "=min(DB!G:G)" 54 | A2: "=MAX(DB!G:G)" 55 | A5: "=query(GOOGLEFINANCE("CURRENCY:USDILS","price",A1, A2, "DAILY"),"select * label Col1 '', Col2''")" 56 | C5: "=query(GOOGLEFINANCE("CURRENCY:EURILS","price",A1, A2, "DAILY"),"select * label Col1 '', Col2''")" 57 | 58 | */ -------------------------------------------------------------------------------- /HelperFunctions.js: -------------------------------------------------------------------------------- 1 | //================================== 2 | function get_column_letter_from_name(row, name){ 3 | return String.fromCharCode(65 + row.indexOf(name)); 4 | } 5 | 6 | //================================== 7 | function charToCurrencyCode(c){ 8 | var code; 9 | 10 | switch(c){ 11 | case String.fromCharCode(0x20aa): // Shekels 12 | code = "ILS"; 13 | break; 14 | case String.fromCharCode(0x24): // USD 15 | code = "USD"; 16 | break; 17 | case String.fromCharCode(0x20AC): // Euro 18 | code = "EUR"; 19 | break; 20 | default: 21 | code = "NA"; 22 | break; 23 | } 24 | return code; 25 | } 26 | 27 | //================================== 28 | /** 29 | * For a value in a table, teturn the column header where the value is found. 30 | * 31 | * @param {value} value. 32 | * @param {in_range} range to look. 33 | * @return The index. 34 | * @customfunction 35 | */ 36 | function REVERSELOOKUP(input, in_range){ 37 | function map_value(value, range){ 38 | return value.length > 0 ? 39 | range.filter(row => row.indexOf(value) > -1)[0][0] : 40 | ""; 41 | } 42 | return Array.isArray(input) ? 43 | input.map(cell => map_value(cell[0], in_range)) : 44 | map_value(input, in_range); 45 | } 46 | 47 | //================================== 48 | function get_row_values(sheet, rowNum){ 49 | return sheet.getRange([rowNum, rowNum].join(':')).getValues()[0]; 50 | } 51 | 52 | //================================== 53 | //Get the list of files from a folder 54 | function get_file_list(folderID){ 55 | var folderHandle = DriveApp.getFolderById(folderID); 56 | return folderHandle.getFiles(); 57 | } 58 | 59 | //================================== 60 | //Create an arbitrary struct (for building the rows in the db) 61 | function makeStruct(names = transactionDetailsTemplate) { 62 | var names = names.split(', '); 63 | var count = names.length; 64 | function constructor() { 65 | for (var i = 0; i < count; i++) { 66 | this[names[i]] = arguments[i]; 67 | } 68 | } 69 | return constructor; 70 | } 71 | 72 | //================================== 73 | //Compare google folder content to a table and return files not appearing in table 74 | function get_new_files_by_list(folderID, statusSheet){ 75 | 76 | var FilesIdColumnLetter = get_column_letter_from_name(get_row_values(statusSheet,1), 'File ID'); 77 | var listOfProcessesFiles = statusSheet.getRange([FilesIdColumnLetter, FilesIdColumnLetter].join(':')).getValues(); 78 | var folderFileList = get_file_list(folderID); 79 | var listOfProcessesFiles1d = listOfProcessesFiles.map(x => x[0]); 80 | var newFilesArray = []; 81 | while(folderFileList.hasNext()){ 82 | var fileID = folderFileList.next().getId(); 83 | var exist = listOfProcessesFiles1d.indexOf(fileID); 84 | if (exist > -1){ 85 | continue; 86 | } 87 | else{ 88 | newFilesArray.push(fileID); 89 | } 90 | } 91 | return newFilesArray; 92 | } -------------------------------------------------------------------------------- /src/HelperFunctions.js: -------------------------------------------------------------------------------- 1 | //================================== 2 | function get_column_letter_from_name(row, name){ 3 | return String.fromCharCode(65 + row.indexOf(name)); 4 | } 5 | 6 | //================================== 7 | function charToCurrencyCode(c){ 8 | var code; 9 | 10 | switch(c){ 11 | case String.fromCharCode(0x20aa): // Shekels 12 | code = "ILS"; 13 | break; 14 | case String.fromCharCode(0x24): // USD 15 | code = "USD"; 16 | break; 17 | case String.fromCharCode(0x20AC): // Euro 18 | code = "EUR"; 19 | break; 20 | default: 21 | code = "NA"; 22 | break; 23 | } 24 | return code; 25 | } 26 | 27 | //================================== 28 | /** 29 | * For a value in a table, teturn the column header where the value is found. 30 | * 31 | * @param {value} value. 32 | * @param {in_range} range to look. 33 | * @return The index. 34 | * @customfunction 35 | */ 36 | function REVERSELOOKUP(input, in_range){ 37 | function map_value(value, range){ 38 | return value.length > 0 ? 39 | range.filter(row => row.indexOf(value) > -1)[0][0] : 40 | ""; 41 | } 42 | return Array.isArray(input) ? 43 | input.map(cell => map_value(cell[0], in_range)) : 44 | map_value(input, in_range); 45 | } 46 | 47 | //================================== 48 | function get_row_values(sheet, rowNum){ 49 | return sheet.getRange([rowNum, rowNum].join(':')).getValues()[0]; 50 | } 51 | 52 | //================================== 53 | //Get the list of files from a folder 54 | function get_file_list(folderID){ 55 | var folderHandle = DriveApp.getFolderById(folderID); 56 | return folderHandle.getFiles(); 57 | } 58 | 59 | //================================== 60 | //Create an arbitrary struct (for building the rows in the db) 61 | function makeStruct(names = transactionDetailsTemplate) { 62 | var names = names.split(', '); 63 | var count = names.length; 64 | function constructor() { 65 | for (var i = 0; i < count; i++) { 66 | this[names[i]] = arguments[i]; 67 | } 68 | } 69 | return constructor; 70 | } 71 | 72 | //================================== 73 | //Compare google folder content to a table and return files not appearing in table 74 | function get_new_files_by_list(folderID, statusSheet){ 75 | 76 | var FilesIdColumnLetter = get_column_letter_from_name(get_row_values(statusSheet,1), 'File ID'); 77 | var listOfProcessesFiles = statusSheet.getRange([FilesIdColumnLetter, FilesIdColumnLetter].join(':')).getValues(); 78 | var folderFileList = get_file_list(folderID); 79 | var listOfProcessesFiles1d = listOfProcessesFiles.map(x => x[0]); 80 | var newFilesArray = []; 81 | while(folderFileList.hasNext()){ 82 | var fileID = folderFileList.next().getId(); 83 | var exist = listOfProcessesFiles1d.indexOf(fileID); 84 | if (exist > -1){ 85 | continue; 86 | } 87 | else{ 88 | newFilesArray.push(fileID); 89 | } 90 | } 91 | return newFilesArray; 92 | } -------------------------------------------------------------------------------- /CreditCardParsers.js: -------------------------------------------------------------------------------- 1 | //================================== 2 | //GLOBAL PARAMETERS 3 | FILENAME_PREFIX_VISA = 'Transactions' 4 | FILENAME_PREFIX_ISRACARD = 'Export' 5 | FILENAME_PREFIX_MAX = 'transaction' 6 | 7 | const dateRegex = new RegExp(/^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/g); 8 | 9 | //================================== 10 | // Detect credit card type by file name 11 | function get_credit_card_type(fid){ 12 | var file = DriveApp.getFileById(fid); 13 | var filename = file.getName(); 14 | 15 | if(filename.startsWith(FILENAME_PREFIX_ISRACARD)){ 16 | return 'Isracard'; 17 | } 18 | else if(filename.startsWith(FILENAME_PREFIX_VISA)){ 19 | return 'Visa'; 20 | } 21 | else if(filename.startsWith(FILENAME_PREFIX_MAX)){ 22 | return 'Max'; 23 | } 24 | else{ 25 | return 'Unknown'; 26 | } 27 | } 28 | 29 | //================================== 30 | // Parse MAX credit card files 31 | function get_max_data(f_handler, fid, categoriesTable){ 32 | sheets = f_handler.getSheets(); 33 | var out_data = []; 34 | var nCard = ''; 35 | var billingMonth = new Date(0); 36 | var inputDate = new Date(); 37 | var fname = SpreadsheetApp.openById(fid).getName() 38 | 39 | for (var s = 0; s < sheets.length; s++){ 40 | lastRow = sheets[s].getLastRow(); 41 | lastCol = sheets[s].getLastColumn(); 42 | var data = sheets[s].getRange(1,1,lastRow,lastCol).getValues(); 43 | 44 | 45 | for (var r=0; r < lastRow; r++){ 46 | var row = data[r]; 47 | 48 | if (row[0].split('-').length == 3){ 49 | var formattedRow = new transactionDetailsTemplate(); 50 | formattedRow.inputDate = inputDate; 51 | formattedRow.type = 'Max'; 52 | formattedRow.nCard = row[3]; 53 | 54 | formattedRow.billingMonth = new Date(row[9].split('-')[2], row[9].split('-')[1]-1, row[9].split('-')[0]); //month is zero-based 55 | 56 | formattedRow.transactionDate = new Date(row[0].split('-')[2], row[0].split('-')[1]-1, row[0].split('-')[0]); //month is zero-based 57 | 58 | formattedRow.name = row[1]; 59 | formattedRow.amount = row[5] 60 | formattedRow.currency = charToCurrencyCode(row[6]); 61 | formattedRow.fid = fid; 62 | formattedRow.fname = fname; 63 | 64 | out_data.push(formattedRow); 65 | continue; 66 | } 67 | else{ continue;} 68 | } 69 | } 70 | return {'data': out_data, 'nRow': out_data.length, 'nCol': Object.keys(out_data[0]).length}; 71 | } 72 | 73 | //================================== 74 | // Parse Isracard credit card files 75 | function get_isracard_data(f_handler, fid, categoriesTable){ 76 | sheet = f_handler.getSheets()[0]; 77 | lastRow = sheet.getLastRow(); 78 | lastCol = sheet.getLastColumn(); 79 | var data = sheet.getRange(1,1,lastRow,lastCol).getValues(); 80 | var out_data = []; 81 | var nCard = ''; 82 | var billingMonth = new Date(0); 83 | var inputDate = new Date(); 84 | var abroadCharges = 0; 85 | var fname = SpreadsheetApp.openById(fid).getName() 86 | 87 | for (var r=0; r < lastRow; r++){ 88 | var row = data[r]; 89 | 90 | if (row[1] == 'מועד חיוב'){ 91 | nCard = row[0].split(' - ')[row[0].split(' - ').length -1]; 92 | billingMonth.setFullYear(['20', row[2].split('/')[2]].join('')); 93 | billingMonth.setMonth(row[2].split('/')[1]-1); 94 | //billingMonth.setMonth(billingMonth.getMonth()-1); // because JS Date months are zero-based 95 | continue; 96 | } 97 | else if (row[0] == 'עסקאות בארץ'){ 98 | abroadCharges = 0; 99 | continue; 100 | } 101 | else if (row[0] == 'עסקאות בחו˝ל'){ 102 | abroadCharges = 1; 103 | continue; 104 | } 105 | else if (row[0].split('/').length == 3){ 106 | var formattedRow = new transactionDetailsTemplate(); 107 | formattedRow.inputDate = inputDate; 108 | formattedRow.type = 'Isracard'; 109 | formattedRow.nCard = nCard; 110 | formattedRow.billingMonth = billingMonth; 111 | formattedRow.fid = fid; 112 | formattedRow.fname = fname; 113 | 114 | formattedRow.transactionDate = new Date(row[0].split('/')[2], row[0].split('/')[1]-1, row[0].split('/')[0]); //month is zero-based 115 | 116 | if (abroadCharges == 1){ 117 | formattedRow.name = row[2]; 118 | formattedRow.amount = row[5]; 119 | formattedRow.currency = charToCurrencyCode(row[6]); 120 | } 121 | else{ 122 | formattedRow.name = row[1]; 123 | formattedRow.amount = row[4]; 124 | formattedRow.currency = charToCurrencyCode(row[5]); 125 | } 126 | 127 | if(formattedRow.name == 'TOTAL FOR DATE'){ continue;} 128 | 129 | out_data.push(formattedRow); 130 | 131 | continue; 132 | 133 | } 134 | else{ continue;} 135 | } 136 | return {'data': out_data, 'nRow': out_data.length, 'nCol': Object.keys(out_data[0]).length}; 137 | } 138 | 139 | //================================== 140 | // Parse Visa credit card files 141 | function get_visa_data(f_handler, fid, categoriesTable){ 142 | sheets = f_handler.getSheets(); 143 | var out_data = []; 144 | var nCard = ''; 145 | var billingMonth = new Date(0); 146 | var inputDate = new Date(); 147 | var fname = SpreadsheetApp.openById(fid).getName() 148 | 149 | const billMonthRegex = new RegExp(/\s(?[0-9]+\/[0-9]+)[^0-9\/]/); 150 | const nCardRegex = new RegExp(/\s([0-9]{4})/); 151 | 152 | for (var s = 0; s < sheets.length; s++){ 153 | lastRow = sheets[s].getLastRow(); 154 | lastCol = sheets[s].getLastColumn(); 155 | var data = sheets[s].getRange(1,1,lastRow,lastCol).getValues(); 156 | 157 | for (var r=0; r < lastRow - 1; r++){ 158 | var row = data[r]; 159 | //var isFullDate = dateRegex.exec(row[0]); 160 | const dateCellFormat = typeof row[0]; 161 | 162 | if (r < 3){ // first lines contain the billing date and card number 163 | const billDate = billMonthRegex.exec(row[0]); 164 | const nCardRe = nCardRegex.exec(row[0]); 165 | 166 | if (billDate != null){ 167 | billingMonth.setYear((parseInt(billDate.groups.billMonth.split('/')[1]))); 168 | billingMonth.setMonth(parseInt(billDate.groups.billMonth.split('/')[0])-1); 169 | } 170 | if (nCardRe != null ){ 171 | nCard = nCardRe[0]; 172 | } 173 | } 174 | //else if {dateCellFormat == "object" || isFullDate != null){ 175 | else{ 176 | 177 | var formattedRow = new transactionDetailsTemplate(); 178 | 179 | //determine transaction date 180 | if (dateCellFormat == "object"){ 181 | formattedRow.transactionDate = row[0].toLocaleDateString("en-IL"); 182 | } 183 | else{ 184 | var rSplit = row[0].split('/'); 185 | formattedRow.transactionDate = new Date("20" + rSplit[2], rSplit[1]-1, rSplit[0]); //month is zero-based 186 | } 187 | 188 | formattedRow.inputDate = inputDate; 189 | formattedRow.type = 'Visa'; 190 | formattedRow.nCard = nCard; 191 | formattedRow.billingMonth = billingMonth.toLocaleDateString("en-US"); 192 | 193 | formattedRow.name = row[1]; 194 | var amountRegex = new RegExp(/[0-9].{0,10}\.[0-9]{0,5}/); 195 | 196 | formattedRow.amount = amountRegex.exec(row[3])[0]; 197 | 198 | formattedRow.currency = charToCurrencyCode('₪'); 199 | formattedRow.fid = fid; 200 | formattedRow.fname = fname; 201 | 202 | out_data.push(formattedRow); 203 | continue; 204 | } 205 | } 206 | } 207 | return {'data': out_data, 'nRow': out_data.length, 'nCol': Object.keys(out_data[0]).length}; 208 | } 209 | 210 | //================================== 211 | function update_status_sheet(files, sheet){ 212 | for (m = 0; m < files.length; m++){ 213 | fileId = files[m]; 214 | fileName = SpreadsheetApp.openById(fileId).getName(); 215 | sheet.appendRow([fileName, fileId]); 216 | } 217 | return; 218 | } -------------------------------------------------------------------------------- /src/CreditCardParsers.js: -------------------------------------------------------------------------------- 1 | //================================== 2 | //GLOBAL PARAMETERS 3 | FILENAME_PREFIX_VISA = 'Transactions' 4 | FILENAME_PREFIX_ISRACARD = 'Export' 5 | FILENAME_PREFIX_MAX = 'transaction' 6 | 7 | const transactionDetailsTemplate = new makeStruct("inputDate, fid, fname, type, nCard, billingMonth, transactionDate, name, amount, currency"); 8 | const dateRegex = new RegExp(/^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[13-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/g); 9 | 10 | //================================== 11 | // Detect credit card type by file name 12 | function get_credit_card_type(fid){ 13 | var file = DriveApp.getFileById(fid); 14 | var filename = file.getName(); 15 | 16 | if(filename.startsWith(FILENAME_PREFIX_ISRACARD)){ 17 | return 'Isracard'; 18 | } 19 | else if(filename.startsWith(FILENAME_PREFIX_VISA)){ 20 | return 'Visa'; 21 | } 22 | else if(filename.startsWith(FILENAME_PREFIX_MAX)){ 23 | return 'Max'; 24 | } 25 | else{ 26 | return 'Unknown'; 27 | } 28 | } 29 | 30 | //================================== 31 | // Parse MAX credit card files 32 | function get_max_data(f_handler, fid, categoriesTable){ 33 | sheets = f_handler.getSheets(); 34 | var out_data = []; 35 | var nCard = ''; 36 | var billingMonth = new Date(0); 37 | var inputDate = new Date(); 38 | var fname = SpreadsheetApp.openById(fid).getName() 39 | 40 | for (var s = 0; s < sheets.length; s++){ 41 | lastRow = sheets[s].getLastRow(); 42 | lastCol = sheets[s].getLastColumn(); 43 | var data = sheets[s].getRange(1,1,lastRow,lastCol).getValues(); 44 | 45 | 46 | for (var r=0; r < lastRow; r++){ 47 | var row = data[r]; 48 | 49 | if (row[0].split('-').length == 3){ 50 | var formattedRow = new transactionDetailsTemplate(); 51 | formattedRow.inputDate = inputDate; 52 | formattedRow.type = 'Max'; 53 | formattedRow.nCard = row[3]; 54 | 55 | formattedRow.billingMonth = new Date(row[9].split('-')[2], row[9].split('-')[1]-1, row[9].split('-')[0]); //month is zero-based 56 | 57 | formattedRow.transactionDate = new Date(row[0].split('-')[2], row[0].split('-')[1]-1, row[0].split('-')[0]); //month is zero-based 58 | 59 | formattedRow.name = row[1]; 60 | formattedRow.amount = row[5] 61 | formattedRow.currency = charToCurrencyCode(row[6]); 62 | formattedRow.fid = fid; 63 | formattedRow.fname = fname; 64 | 65 | out_data.push(formattedRow); 66 | continue; 67 | } 68 | else{ continue;} 69 | } 70 | } 71 | return {'data': out_data, 'nRow': out_data.length, 'nCol': Object.keys(out_data[0]).length}; 72 | } 73 | 74 | //================================== 75 | // Parse Isracard credit card files 76 | function get_isracard_data(f_handler, fid, categoriesTable){ 77 | sheet = f_handler.getSheets()[0]; 78 | lastRow = sheet.getLastRow(); 79 | lastCol = sheet.getLastColumn(); 80 | var data = sheet.getRange(1,1,lastRow,lastCol).getValues(); 81 | var out_data = []; 82 | var nCard = ''; 83 | var billingMonth = new Date(0); 84 | var inputDate = new Date(); 85 | var abroadCharges = 0; 86 | var fname = SpreadsheetApp.openById(fid).getName() 87 | 88 | for (var r=0; r < lastRow; r++){ 89 | var row = data[r]; 90 | 91 | if (row[1] == 'מועד חיוב'){ 92 | nCard = row[0].split(' - ')[row[0].split(' - ').length -1]; 93 | billingMonth.setFullYear(['20', row[2].split('/')[2]].join('')); 94 | billingMonth.setMonth(row[2].split('/')[1]-1); 95 | //billingMonth.setMonth(billingMonth.getMonth()-1); // because JS Date months are zero-based 96 | continue; 97 | } 98 | else if (row[0] == 'עסקאות בארץ'){ 99 | abroadCharges = 0; 100 | continue; 101 | } 102 | else if (row[0] == 'עסקאות בחו˝ל'){ 103 | abroadCharges = 1; 104 | continue; 105 | } 106 | else if (row[0].split('/').length == 3){ 107 | var formattedRow = new transactionDetailsTemplate(); 108 | formattedRow.inputDate = inputDate; 109 | formattedRow.type = 'Isracard'; 110 | formattedRow.nCard = nCard; 111 | formattedRow.billingMonth = billingMonth; 112 | formattedRow.fid = fid; 113 | formattedRow.fname = fname; 114 | 115 | formattedRow.transactionDate = new Date(row[0].split('/')[2], row[0].split('/')[1]-1, row[0].split('/')[0]); //month is zero-based 116 | 117 | if (abroadCharges == 1){ 118 | formattedRow.name = row[2]; 119 | formattedRow.amount = row[5]; 120 | formattedRow.currency = charToCurrencyCode(row[6]); 121 | } 122 | else{ 123 | formattedRow.name = row[1]; 124 | formattedRow.amount = row[4]; 125 | formattedRow.currency = charToCurrencyCode(row[5]); 126 | } 127 | 128 | if(formattedRow.name == 'TOTAL FOR DATE'){ continue;} 129 | 130 | out_data.push(formattedRow); 131 | 132 | continue; 133 | 134 | } 135 | else{ continue;} 136 | } 137 | return {'data': out_data, 'nRow': out_data.length, 'nCol': Object.keys(out_data[0]).length}; 138 | } 139 | 140 | //================================== 141 | // Parse Visa credit card files 142 | function get_visa_data(f_handler, fid, categoriesTable){ 143 | sheets = f_handler.getSheets(); 144 | var out_data = []; 145 | var nCard = ''; 146 | var billingMonth = new Date(0); 147 | var inputDate = new Date(); 148 | var fname = SpreadsheetApp.openById(fid).getName() 149 | 150 | const billMonthRegex = new RegExp(/\s(?[0-9]+\/[0-9]+)[^0-9\/]/); 151 | const nCardRegex = new RegExp(/\s([0-9]{4})/); 152 | 153 | for (var s = 0; s < sheets.length; s++){ 154 | lastRow = sheets[s].getLastRow(); 155 | lastCol = sheets[s].getLastColumn(); 156 | var data = sheets[s].getRange(1,1,lastRow,lastCol).getValues(); 157 | 158 | for (var r=0; r < lastRow - 1; r++){ 159 | var row = data[r]; 160 | //var isFullDate = dateRegex.exec(row[0]); 161 | const dateCellFormat = typeof row[0]; 162 | 163 | if (r < 3){ // first lines contain the billing date and card number 164 | const billDate = billMonthRegex.exec(row[0]); 165 | const nCardRe = nCardRegex.exec(row[0]); 166 | 167 | if (billDate != null){ 168 | billingMonth.setYear((parseInt(billDate.groups.billMonth.split('/')[1]))); 169 | billingMonth.setMonth(parseInt(billDate.groups.billMonth.split('/')[0])-1); 170 | } 171 | if (nCardRe != null ){ 172 | nCard = nCardRe[0]; 173 | } 174 | } 175 | //else if {dateCellFormat == "object" || isFullDate != null){ 176 | else{ 177 | 178 | var formattedRow = new transactionDetailsTemplate(); 179 | 180 | //determine transaction date 181 | if (dateCellFormat == "object"){ 182 | formattedRow.transactionDate = row[0].toLocaleDateString("en-IL"); 183 | } 184 | else{ 185 | var rSplit = row[0].split('/'); 186 | formattedRow.transactionDate = new Date("20" + rSplit[2], rSplit[1]-1, rSplit[0]); //month is zero-based 187 | } 188 | 189 | formattedRow.inputDate = inputDate; 190 | formattedRow.type = 'Visa'; 191 | formattedRow.nCard = nCard; 192 | formattedRow.billingMonth = billingMonth.toLocaleDateString("en-US"); 193 | 194 | formattedRow.name = row[1]; 195 | var amountRegex = new RegExp(/[0-9].{0,10}\.[0-9]{0,5}/); 196 | 197 | formattedRow.amount = amountRegex.exec(row[3])[0]; 198 | 199 | formattedRow.currency = charToCurrencyCode('₪'); 200 | formattedRow.fid = fid; 201 | formattedRow.fname = fname; 202 | 203 | out_data.push(formattedRow); 204 | continue; 205 | } 206 | } 207 | } 208 | return {'data': out_data, 'nRow': out_data.length, 'nCol': Object.keys(out_data[0]).length}; 209 | } 210 | 211 | //================================== 212 | function update_status_sheet(files, sheet){ 213 | for (m = 0; m < files.length; m++){ 214 | fileId = files[m]; 215 | fileName = SpreadsheetApp.openById(fileId).getName(); 216 | sheet.appendRow([fileName, fileId]); 217 | } 218 | return; 219 | } --------------------------------------------------------------------------------