UNPKG

statement-parser-fab

Version:

Parse bank and credit card statements. Updated fork with FAB (First Abu Dhabi Bank) support and maintained dependencies.

160 lines (159 loc) 6.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.usaaVisaCreditCardStatementParser = void 0; const augment_vir_1 = require("augment-vir"); const date_1 = require("../../augments/date"); const statement_parser_1 = require("../statement-parser"); var State; (function (State) { State["Header"] = "header"; State["PaymentHeader"] = "payment-header"; State["Payment"] = "payment"; State["PaymentFiller"] = "payment-filler"; State["CreditHeader"] = "credit-header"; State["Credit"] = "credit"; State["CreditFiller"] = "credit-filler"; State["CreditStartedFiller"] = "credit-started-filler"; State["End"] = "end"; })(State || (State = {})); const PreserveKeywords = { TotalPayments: 'total payments and credits for this period', AccountNumber: /^Account Number\s+/, Payments: /^\s*payments and credits\s*$/i, TransactionsContinued: /^\s*transactions \(continued\)\s*$/i, Transactions: /^\s*transactions\s*$/i, StatementClosingDate: 'statement closing date', }; const tableHeadersRegExp = /^trans date\s*post date/i; const creditsEndRegExp = /^\s*total transactions for/i; const paymentsEndRegExp = new RegExp(`(?:^${PreserveKeywords.TotalPayments}\\s+\\$)|(?:^$)`, 'i'); const extractAccountNumberRegExp = new RegExp(`${PreserveKeywords.AccountNumber.source}.+(\\d{1,4})$`, 'i'); const closingDateRegExp = new RegExp(`${PreserveKeywords.StatementClosingDate}\\s+(\\d{1,2}/\\d{1,2}/\\d{1,2})`, 'i'); const feesRegExp = /^\s*fees\s*$/i; exports.usaaVisaCreditCardStatementParser = (0, statement_parser_1.createStatementParser)({ action: performStateAction, next: nextState, initialState: State.Header, endState: State.End, parserKeywords: [ // most of the RegExps are not included here because they capture sensitive information ...Object.values(PreserveKeywords), tableHeadersRegExp, creditsEndRegExp, feesRegExp, ], }); const transactionRegExp = /^(\d{1,2}\/\d{1,2})\s+(\d{1,2}\/\d{1,2})\s+(\S.*?)\s+?(\S.*?)\s+\$((?:\d+|,|\.)+)\-?$/; function processTransactionLine(line, endDate) { const [, transactionDate, postDate, referenceNumber, description, amount,] = (0, augment_vir_1.safeMatch)(line, transactionRegExp); if (transactionDate && postDate && referenceNumber && description && amount) { const [transactionMonth, transactionDay,] = transactionDate.split('/'); const [postMonth, postDay,] = postDate.split('/'); return { date: (0, date_1.dateWithinRange)(undefined, endDate, Number(transactionMonth), Number(transactionDay)), postDate: (0, date_1.dateWithinRange)(undefined, endDate, Number(postMonth), Number(postDay)), amount: Number((0, augment_vir_1.removeCommasFromNumberString)(amount)), description, referenceNumber, originalText: [line], }; } else { return line; } } function performStateAction(currentState, line, output, parserOptions) { if ((currentState === State.Credit && !line.match(creditsEndRegExp)) || (currentState === State.Payment && !line.match(paymentsEndRegExp)) || // read expenses if in this state and the line matches a transaction (currentState === State.CreditStartedFiller && line.match(transactionRegExp))) { if (!output.endDate) { throw new Error('Started reading transactions but got no statement close date.'); } // Critical ternary here that sets the array to expenses even if the above State.CREDIT_STARTED_FILLER condition // is true const array = currentState === State.Payment ? output.incomes : output.expenses; const result = processTransactionLine(line, output.endDate); if (typeof result === 'string') { const lastTransaction = array[array.length - 1]; if (result && lastTransaction) { lastTransaction.description += '\n' + result; lastTransaction.originalText.push(line); } } else { array.push(result); } } else if (currentState === State.Header) { const [, closingDateString,] = (0, augment_vir_1.safeMatch)(line, closingDateRegExp); const [, accountNumberString,] = (0, augment_vir_1.safeMatch)(line, extractAccountNumberRegExp); if (closingDateString) { output.endDate = (0, augment_vir_1.createDateFromSlashFormat)(closingDateString, parserOptions.yearPrefix); } else if (accountNumberString && !output.accountSuffix) { output.accountSuffix = accountNumberString; } } return output; } function nextState(currentState, line) { line = line.toLowerCase(); switch (currentState) { case State.Header: if (line.match(PreserveKeywords.Payments)) { return State.PaymentHeader; } break; case State.PaymentHeader: if (line.match(tableHeadersRegExp)) { return State.Payment; } break; case State.Payment: // use this regex here so that it can be shared with performStateAction if (line.match(paymentsEndRegExp)) { if (line === '') { return State.PaymentFiller; } else { return State.CreditFiller; } } break; case State.PaymentFiller: if (line.match(PreserveKeywords.TransactionsContinued)) { return State.Payment; } break; case State.CreditFiller: if (line.match(PreserveKeywords.Transactions)) { return State.CreditHeader; } else if (line.match(feesRegExp)) { return State.End; } break; case State.CreditStartedFiller: if (line.match(PreserveKeywords.TransactionsContinued) || line.match(transactionRegExp)) { return State.Credit; } break; case State.CreditHeader: if (line.match(tableHeadersRegExp)) { return State.Credit; } break; case State.Credit: if (line.match(creditsEndRegExp)) { return State.CreditFiller; } else if (line === '') { return State.CreditStartedFiller; } case State.End: break; } return currentState; }