def parse_record(self, line): if line[0] == "Kontonummer": # it's the table header return None if len(line) < 3: """e.g.: ['# 1 vorgemerkte Umsätze nicht angezeigt']""" return None if not line[2]: return None if self.statement.account_id is None: self.statement.account_id = line[0] sl = StatementLine() sl.id = line[1] sl.date = self.parse_datetime(line[2]) sl.date_avail = self.parse_datetime(line[3]) sl.amount = self.parse_float(line[4]) sl.trntype = TMAPPINGS.get(line[5], 'DEBIT' if sl.amount < 0 else 'CREDIT') sl.payee = line[7][:32] sl.memo = "%s: %s" % (line[6], " ".join(x for x in line[13:31] if len(x) > 0)) if len(line[8]) > 0 and len(line[9]) > 0: # additional bank information is present splitted = self.parse_iban(line[8]) if splitted: sl.bank_account_to = BankAccount(**splitted) else: sl.bank_account_to = BankAccount(line[9], line[8]) return sl
def parse_record(self, line): # FIXME: add header validation if self.cur_record < 2: return None if len(line) < 3: """e.g.: ['# 1 vorgemerkte Umsätze nicht angezeigt']""" return None if not line[2]: return None sl = StatementLine() sl.id = line[1] sl.date = self.parse_datetime(line[2]) sl.amount = self.parse_float(line[4]) sl.trntype = 'DEBIT' if sl.amount < 0 else 'CREDIT' sl.payee = line[7] # check for special transactions if line[6] == "Entgeltabschluss": sl.memo = "%s: %s %s" % (line[6], line[13], line[14]) elif line[6] == "Wertpapiere" or line[7] == "KREDITKARTENABRECHNUNG": sl.memo = "(%s/%s): %s" % (line[8], line[9], " ".join(line[15:]).strip()) elif not line[8] and not line[9]: # empty transaction print("empty", line) return None else: sl.memo = "(%s/%s): %s" % (line[8], line[9], " ".join(e for e in line[13:] if e).strip()) return sl
def parse_record(self, rec): MultilineRecRE = re.compile(r"\n\s+") SplitItemRE = re.compile(r":\s+") # join together multiline items rec = MultilineRecRE.sub(" ", rec) res_iter = (SplitItemRE.split(line) for line in rec.split("\n")) res_dict = dict((item[0].strip(), item[1].strip()) for item in res_iter) res_key_set = set(res_dict.keys()) # Insurance against unknown keys in the statement assert res_key_set <= self._keys, \ "Unknown keys in the transaction: %s" % (res_key_set - self._keys) # no idea, how to make this unique and monotonically increasing trans_ID = res_dict['datum zaúčtování'] + res_dict['částka'] + \ res_dict['variabilní symbol'] + res_dict['protiúčet'] date_str = res_dict['datum zaúčtování'].split('.') stat_line = StatementLine(id=trans_ID, date=datetime.date(int(date_str[2]), int(date_str[1]), int(date_str[0])), memo="%s\nozn. operace: %s" % (res_dict['poznámka'], res_dict['označení operace']), amount=float(res_dict['částka'])) stat_line.payee = res_dict['název protiúčtu'] # According to OFX spec this could be "check (or other reference) no." # I don't see any requirement on monotonicity or uniqueness, but # I wonder how GnuCash understands that stat_line.check_no = res_dict['variabilní symbol'] return stat_line
def testAllFilled(self): """All of the fields used in the hash have a value""" sline = StatementLine() sline.date = date(2015, 10, 2) sline.memo = "Test statement line" sline.amount = 123.45 self.assertEqual(self.all_hash, generate_stable_transaction_id(sline))
def parse_record(self, line): san_line = {} for lkey, lval in line.items(): san_line[lkey] = re.sub("\$", "", lval) stmt_line = StatementLine() for field, col in self.mappings.items(): rawvalue = san_line[col] try: value = self.parse_value(rawvalue, field) except ValueError as e: # Invalid data line; skip it warning("Error parsing value '{}' on line '{}': {}".format(rawvalue, san_line, e)) return None setattr(stmt_line, field, value) if self.statement.filter_zeros and is_zero(stmt_line.amount): return None try: stmt_line.end_balance = float(san_line['Ending Balance']) except ValueError: # Usually indicates a pending transaction return None # generate transaction id out of available data stmt_line.id = generate_stable_transaction_id(stmt_line) return stmt_line
def parse_record(self, row): """Parse given table row and return StatementLine object """ # if we're still in header, no transactions yet, just collect account data if self.in_header: m = match( r"""(Prometi\ za\ razdoblje\ od\ (?P<start>[0-9.]+)\ do\ (?P<end>[0-9.]+)) |(Račun:\ (?P<acct>\w+)) # account ^- start&end dates |(Valuta:\ (?P<curr>\w+)) # currency |(?P<eoh>Datum) # end of headers""", row[0].value, VERBOSE) if m: # start/end if m['start']: self.statement.start_date = datetime.strptime( m['start'], self.header_date_format) self.statement.end_date = datetime.strptime( m['end'], self.header_date_format) # account number if m['acct']: self.statement.account_id = m['acct'] # currency elif m['curr']: self.statement.currency = m['curr'] # end of headers elif m['eoh']: self.in_header = False self.row_nr += 1 return None # main body of transactions after headers stmt_line = StatementLine(amount=0) for field, col in self.mappings.items(): if col >= len(row): raise ValueError( "Cannot find column %s in a row of %s items " % (col, len(row))) cell = row[col] value = self.parse_value(cell) # calculate debits and credits to amount if field == 'debit' and value != 0: stmt_line.amount += value stmt_line.trntype = 'DEBIT' elif field == 'credit' and value != 0: stmt_line.amount -= value stmt_line.trntype = 'CREDIT' else: setattr(stmt_line, field, value) # apply generated transaction id stmt_line.id = self.gen_id(stmt_line) # overwrite end balance with current balance self.statement.end_balance = stmt_line.balance # next row self.row_nr += 1 return stmt_line
def parse_record(self, record): P = self.P sl = StatementLine() sl.date = self.parse_datetime(record.findall(P('ValDt', 'Dt'))[0].text) sl.amount = float(record.findall(P('Amt'))[0].text) sl.trntype = 'DEBIT' if (record.findall(P('CdtDbtInd'))[0].text == "DBIT") else 'CREDIT' if sl.trntype == 'DEBIT' and sl.amount > 0: sl.amount = -sl.amount sl.payee = record.findall( P('NtryDtls', 'TxDtls', 'RltdPties', 'Cdtr', 'Nm'))[0].text sl.memo = ' '.join([ e.text for e in record.findall(P('NtryDtls', 'TxDtls', 'RmtInf', 'Ustrd')) ]) # generate unique id h = hashlib.sha256() h.update(str(sl.date).encode('utf-8')) h.update(str(sl.amount).encode('utf-8')) h.update(str(sl.trntype).encode('utf-8')) h.update(str(sl.payee).encode('utf-8')) h.update(str(sl.memo).encode('utf-8')) sl.id = h.hexdigest() return sl
def parse_record(self, df_row): """Parse given transaction line and return StatementLine object """ stmt_line = StatementLine() # date field stmt_line.date = self.xls_date( self.df['Data Contabile'][self.df_row_idx]) # amount field stmt_line.amount = self.df['Importo'][self.df_row_idx] # transaction type field if(stmt_line.amount < 0): stmt_line.trntype = "DEBIT" else: stmt_line.trntype = "CREDIT" # memo field stmt_line.memo = self.df['Causale / Descrizione'][self.df_row_idx] if(pd.isnull(stmt_line.memo)): stmt_line.memo = '' # id field stmt_line.id = generate_transaction_id(stmt_line) #print(str(stmt_line)) return stmt_line
def to_statement_line(self, raw_records): ret =[] for record in raw_records: date = dateparser.parse(record['Data']) memo = record['Detalii tranzactie'] line = StatementLine(date=date, memo=memo) if 'Credit' in record: line.amount = D(record['Credit'].replace('.','').replace(',','.')) line.trntype = 'CREDIT' elif 'Debit' in record: line.amount = D(record['Debit'].replace('.','').replace(',','.')) line.trntype='DEBIT' else: raise ArgumentError if line.trntype=='CREDIT': r = re.compile('ordonator:?(?P<payee>.*)$', re.MULTILINE| re.IGNORECASE) m = r.search( memo) if m: d = m.groupdict() line.payee = d['payee'] line.trntype='XFER' #r = re.compile('din contul:?(?P<payee>.*)$', re.MULTILINE| re.IGNORECASE) #m = r.search( memo) #if m: # d = m.groupdict() # line.payee = d['payee'] r = re.compile('referinta:?(?P<refnum>.*)$', re.MULTILINE| re.IGNORECASE) m = r.search( memo) if m: d = m.groupdict() line.refnum = line.check_no = d['refnum'] line.id= generate_transaction_id(line) ret.append(line) return ret
def parse_record(self, mov): stat_line = StatementLine(None, mov.data_contabile, mov.descrizione_estesa, Decimal(mov.accrediti) if mov.accrediti else Decimal(mov.addebiti)) stat_line.id = generate_transaction_id(stat_line) stat_line.date_user = mov.data_valuta stat_line.trntype = IntesaSanPaoloXlsxParser._get_transaction_type(mov) logging.debug(stat_line) return stat_line
def parse_record(self, line): if len(line) < 5: return None elif len(line) < 12: # possibly meta information about the account if "BLZ" in line[0]: self.statement.bank_id = line[1] elif "Konto" in line[0]: self.statement.account_id = line[1] return None if line[9] == "Anfangssaldo": self.statement.start_date = self.parse_datetime(line[0]) self.statement.start_balance = self.parse_float(line[11]) return None elif line[9] == "Endsaldo": self.statement.end_date = self.parse_datetime(line[0]) self.statement.end_balance = self.parse_float(line[11]) return None elif line[0] == "Buchungstag": # it's the table header return None sl = StatementLine() sl.date = self.parse_datetime(line[0]) sl.date_avail = self.parse_datetime(line[1]) # Note: amount has no sign. We need to guess it later... sl.amount = self.parse_float(line[11]) info = self.parse_transaction_info(line) sl.amount *= info["sign"] sl.trntype = info["ttype"] if "iban" in info: # additional bank information if present sl.bank_account_to = BankAccount(**self.parse_iban(info["iban"])) if line[10] != self.statement.currency: # different currency is used sl.currency = line[10] # remove additional spaces in the payee sl.payee = re.sub(" +", " ", line[3].replace("\n", " ").strip())[:32] # remove additional spaces in the memo sl.memo = re.sub(" +", " ", info["memo"].strip()) # we need to generate an ID because nothing is given sl.id = generate_stable_transaction_id(sl) return sl
def parse_record(self, line): """ Parse given transaction line and return StatementLine object """ sl = StatementLine() sl.amount = Decimal(line['Betrag'].replace(',', '.')) # TODO trntype could be improved using 'Buchungstext' if sl.amount.is_signed(): sl.trntype = 'DEBIT' else: sl.trntype = 'CREDIT' # .date: It is debatable whether to use 'Buchungstag' or 'Valutadatum' sl.date = self.parse_datetime(line['Valutadatum']) # .date_user is not contained in the original CSV # .payee becomes OFX.NAME which becomes "Description" in gnuCash # .memo becomes OFX.MEMO which becomes "Notes" in gnuCash # When .payee is empty, GnuCash imports .memo to "Description" and # keeps "Notes" empty # # OFX's <NAME> and <PAYEE> are distinct fields. But ofxstatement's # .payee is translated to OFX's <NAME> # # According to the OFX spec (version 2.1.1): # <NAME> Name of payee or description of transaction, A-32 # Note: Provide NAME or PAYEE, not both # <MEMO> Extra information (not in <NAME>) # # I prefer to have a description in .payee because that's what it ends # up being in gnuCash. recipient = _truncate_str(line['Beguenstigter/Zahlungspflichtiger']) if not recipient: recipient = 'UNBEKANNT' sl.payee = "{}; {}".format( _truncate_str(line['Verwendungszweck']), recipient, ) sl.memo = "{}; IBAN: {}; BIC: {}".format( line['Buchungstext'].strip(), line['Kontonummer/IBAN'].strip(), line['BIC (SWIFT-Code)'].strip(), ) m = sha256() m.update(str(sl.date).encode('utf-8')) m.update(sl.payee.encode('utf-8')) m.update(sl.memo.encode('utf-8')) m.update(str(sl.amount).encode('utf-8')) # Shorten the hash to the first 16 digits just to make it more # manageable. It should still be enough. sl.id = str(abs(int(m.hexdigest(), 16)))[:16] return sl
def parse_record(self, row): row = take(5, row) stmt_line = StatementLine() stmt_line.date = self.parse_datetime(row[0]) _ = self.parse_datetime(row[1]) # TODO: ??? stmt_line.refnum = row[2] stmt_line.memo = row[3] stmt_line.amount = row[4] # # Looks like SEB formats description for card transactions so it includes the actual purchase date # within e.g. 'WIRSTRÖMS PU/14-12-31' and it means that description is 'WIRSTRÖMS PU' while the actual # card operation is 2014-12-31. # # P.S. Wirströms Irish Pub is our favorite pub in Stockholm: http://www.wirstromspub.se # m = re.match('(.*)/([0-9]{2}-[0-9]{2}-[0-9]{2})$', stmt_line.memo) if m: card_memo, card_date = m.groups() if self.brief: stmt_line.memo = card_memo stmt_line.date_user = datetime.strptime(card_date, '%y-%m-%d') stmt_line.id = generate_transaction_id(stmt_line) return stmt_line
def parse_record(self, row): id_idx = self.valid_header.index("Transaction ID") date_idx = self.valid_header.index("Date") memo_idx = self.valid_header.index("Name") refnum_idx = self.valid_header.index("Reference Txn ID") amount_idx = self.valid_header.index("Gross") payee_idx = self.valid_header.index("To Email Address") title_idx = self.valid_header.index("Item Title") stmt_line = StatementLine() stmt_line.id = row[id_idx] stmt_line.date = datetime.strptime(row[date_idx], self.date_format) stmt_line.memo = row[memo_idx] if self.analyze: memo_parts = [row[memo_idx]] payee = row[payee_idx] if payee and (payee.lower() == '*****@*****.**'): memo_parts.append(row[title_idx]) stmt_line.memo = ' / '.join(filter(bool, memo_parts)) stmt_line.refnum = row[refnum_idx] stmt_line.amount = atof(row[amount_idx].replace(" ", ""), self.locale) return stmt_line
def _parse_line(self, ntry): sline = StatementLine() crdeb = _find(ntry, 'CdtDbtInd').text amtnode = _find(ntry, 'Amt') amt = self._parse_amount(amtnode, self.statement.currency) if crdeb == CD_DEBIT: amt = -amt payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Cdtr/Nm') else: payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Dbtr/Nm') sline.payee = payee.text sline.amount = amt dt = _find(ntry, 'ValDt') sline.date = self._parse_date(dt) bookdt = _find(ntry, 'BookgDt') sline.date_user = self._parse_date(bookdt) svcref = _find(ntry, 'NtryDtls/TxDtls/Refs/AcctSvcrRef') sline.refnum = svcref.text rmtinf = _find(ntry, 'NtryDtls/TxDtls/RmtInf/Ustrd') sline.memo = rmtinf.text return sline
def parse_record(self, line): """Parse given transaction line and return StatementLine object """ #print(line) sl = StatementLine() sl = super(PostFinanceBankParser, self).parse_record(line) if not line[3]: sl.amount = self.parse_float(line[2]) sl.trntype = 'CREDIT' else: sl.amount = self.parse_float("-" + line[3]) sl.trntype = 'DEBIT' sl.payee = sl.memo if payeesplit: #TODO: enable via settings found = False for regex in payeefinder: m = regex.match(sl.payee) if m: found = True sl.payee = m.group(1) #if not found: # print ("Found No PAYEE!" + sl.memo) return sl
def parse_record(self, row): self.row_num += 1 line = StatementLine() line.date = self.parse_datetime(row[0].value) line.date_user = self.parse_datetime(row[1].value) line.refnum = str(self.row_num) line.memo = row[2].value line.amount = row[3].value line.trntype = self.get_type(line) line.id = generate_transaction_id(line) return line
def parse_record(self, line): # Free Headerline if self.cur_record <= 1: return None stmt_line = StatementLine() stmt_line.date = self.parse_datetime(line[0].strip()) # Amount paid_out = -self.parse_amount(line[2]) paid_in = self.parse_amount(line[3]) stmt_line.amount = paid_out or paid_in reference = line[1].strip() trntype = False for prefix, transaction_type in TRANSACTION_TYPES.items(): if reference.startswith(prefix): trntype = transaction_type break if not trntype: trntype = 'POS' # Default: Debit card payment # It's ... pretty ugly, but I see no other way to do this than parse # the reference string because that's all the data we have. stmt_line.trntype = trntype if trntype == 'POS': stmt_line.payee, stmt_line.memo = self.parse_payee_memo(reference) elif reference.startswith('Cash at '): stmt_line.payee, stmt_line.memo = self.parse_payee_memo( reference[8:]) elif reference.startswith('To ') or reference.startswith('From '): stmt_line.payee = self.parse_value( reference[reference.find(' '):], 'payee' ) else: stmt_line.memo = self.parse_value(reference, 'memo') # Notes (from Apr-2018) if len(line) > 8 and line[8].strip(): if not stmt_line.memo: stmt_line.memo = u'' elif len(stmt_line.memo.strip()) > 0: stmt_line.memo += u' ' stmt_line.memo += u'({})'.format(line[8].strip()) return stmt_line
def _parse_line(self, ntry: ET.Element) -> Optional[StatementLine]: sline = StatementLine() crdeb = self._findstrict(ntry, "CdtDbtInd").text amtnode = self._findstrict(ntry, "Amt") amt_ccy = amtnode.get("Ccy") if amt_ccy != self.statement.currency: # We can't include amounts with incompatible currencies into the # statement. return None amt = self._parse_amount(amtnode) if crdeb == CD_DEBIT: amt = -amt payee = self._find(ntry, "NtryDtls/TxDtls/RltdPties/Cdtr/Nm") else: payee = self._find(ntry, "NtryDtls/TxDtls/RltdPties/Dbtr/Nm") sline.payee = payee.text if payee is not None else None sline.amount = amt dt = self._find(ntry, "ValDt") sline.date = self._parse_date(dt) bookdt = self._find(ntry, "BookgDt") sline.date_user = self._parse_date(bookdt) svcref = self._find(ntry, "NtryDtls/TxDtls/Refs/AcctSvcrRef") if svcref is None: svcref = self._find(ntry, "AcctSvcrRef") if svcref is not None: sline.refnum = svcref.text # Try to find memo from different possible locations refinf = self._find(ntry, "NtryDtls/TxDtls/RmtInf/Strd/CdtrRefInf/Ref") rmtinf = self._find(ntry, "NtryDtls/TxDtls/RmtInf/Ustrd") addinf = self._find(ntry, "AddtlNtryInf") if refinf is not None: sline.memo = refinf.text elif rmtinf is not None: sline.memo = rmtinf.text elif addinf is not None: sline.memo = addinf.text return sline
def parse_record(self, line): # Namespace stuff namespaces = {'ns': line.tag[1:].partition("}")[0]} # Get all fields type_code = line.find('ns:TypeCode', namespaces=namespaces).text date = line.find('ns:BookDate', namespaces=namespaces).text c_or_d = line.find('ns:CorD', namespaces=namespaces).text amount = line.find('ns:AccAmt', namespaces=namespaces).text id = line.find('ns:BankRef', namespaces=namespaces).text note = line.find('ns:PmtInfo', namespaces=namespaces).text # Payee name payee_name = None payee = line.find('ns:CPartySet', namespaces=namespaces) if payee: payee_account = payee.find('ns:AccHolder', namespaces=namespaces) if payee_account: payee_name = payee_account.find('ns:Name', namespaces=namespaces).text # Create statement line stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount)) stmt_line.payee = payee_name # Credit & Debit stuff stmt_line.trntype = "DEP" if c_or_d == 'D': stmt_line.amount = -stmt_line.amount stmt_line.trntype = "DEBIT" # Various types if type_code == 'CHOU': stmt_line.trntype = "ATM" elif type_code == 'MEMD': stmt_line.trntype = "SRVCHG" elif type_code == 'OUTP': stmt_line.trntype = "PAYMENT" elif type_code == 'INP': stmt_line.trntype = "XFER" # # Check if paid by card # m = CARD_PURCHASE_RE.match(stmt_line.memo) # if m: # # this is an electronic purchase. extract some useful # # information from memo field # date = m.group(1).split('/') # date = '%s-%s-%s' % (date[2], date[1], date[0]) # stmt_line.date_user = self.parse_datetime(date) print(stmt_line, stmt_line.trntype) return stmt_line
def parse(self): statement = Statement(bank_id='321081669', currency='USD') with open(self.filename) as f: for row in csv.DictReader(f): line = StatementLine(id=row['Transaction Number'], date=self.parse_datetime(row['Date']), memo=row['Statement Description'], amount=Decimal(row['Debit'] or row['Credit'])) line.payee = row['Description'] line.check_no = row['Check Number'] line.trntype = self.guess_type(line.payee, line.amount) statement.lines.append(line) return statement
def parse_record(self, line): """Extracts the transaction data from the given line parsed from the JSON file. :param line: A transaction line from the parsed JSON file. :return: A new StatementLine filled by the given line's data. """ amount = to_float(line['amounts']['amount']) if self.statement.filter_zeros and is_zero(amount): return None memo = line['description'] datetime = ts_to_datetime(line['times']['when_recorded']) id = line['uuid'] ledger_amount = convert_debit(amount, line['bookkeeping_type']) stmt_line = StatementLine(id=id, date=datetime, memo=memo, amount=ledger_amount) stmt_line.end_balance = to_float(line['running_balance']) return stmt_line
def parse_record(self, line): # Namespace stuff namespaces = {'ns': line.tag[1:].partition("}")[0]} # Get all fields type_code = line.find('ns:TypeCode', namespaces=namespaces).text date = line.find('ns:BookDate', namespaces=namespaces).text c_or_d = line.find('ns:CorD', namespaces=namespaces).text amount = line.find('ns:AccAmt', namespaces=namespaces).text id = line.find('ns:BankRef', namespaces=namespaces).text note = line.find('ns:PmtInfo', namespaces=namespaces).text # Payee name payee_name = None payee = line.find('ns:CPartySet', namespaces=namespaces) if payee: payee_account = payee.find('ns:AccHolder', namespaces=namespaces) if payee_account: payee_name = payee_account.find('ns:Name', namespaces=namespaces).text # Create statement line stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount)) stmt_line.payee = payee_name # Credit & Debit stuff stmt_line.trntype = "DEP" if c_or_d == 'D': stmt_line.amount = -stmt_line.amount stmt_line.trntype = "DEBIT" # Various types if type_code == 'MEMD': stmt_line.trntype = "SRVCHG" elif type_code == 'OUTP': stmt_line.trntype = "PAYMENT" # Check if paid by card m = CARD_PURCHASE_RE.match(stmt_line.memo) if m: # this is an electronic purchase. extract some useful # information from memo field date = m.group(1).split('/') date = '%s-%s-%s' % (date[2], date[1], date[0]) stmt_line.date_user = self.parse_datetime(date) # DEBUG if self.debug: print(stmt_line, stmt_line.trntype) return stmt_line
def parse_record(self, line): # Namespace stuff namespaces = {'ns': line.tag[1:].partition("}")[0]} # Get all fields type_code = line.find('ns:TypeCode', namespaces=namespaces).text date = line.find('ns:BookDate', namespaces=namespaces).text c_or_d = line.find('ns:CorD', namespaces=namespaces).text amount = line.find('ns:AccAmt', namespaces=namespaces).text id = line.find('ns:BankRef', namespaces=namespaces).text note = line.find('ns:PmtInfo', namespaces=namespaces).text # Payee name payee_name = None payee = line.find('ns:CPartySet', namespaces=namespaces) if payee: payee_account = payee.find('ns:AccHolder', namespaces=namespaces) if payee_account: payee_name = payee_account.find('ns:Name', namespaces=namespaces).text # Create statement line stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount)) stmt_line.payee = payee_name # Credit & Debit stuff stmt_line.trntype = "DEP" if c_or_d == 'D': stmt_line.amount = -stmt_line.amount stmt_line.trntype = "DEBIT" # Various types if type_code == 'CHOU': stmt_line.trntype = "ATM" elif type_code == 'MEMD': stmt_line.trntype = "SRVCHG" elif type_code == 'OUTP': stmt_line.trntype = "PAYMENT" elif type_code == 'INP': stmt_line.trntype = "XFER" # DEBUG if self.debug: print(stmt_line, stmt_line.trntype) return stmt_line
def parse_record(self, line): statement_line = StatementLine() for field, col_number in self.mappings.items(): state = State(field, self.parse_value) raw_value = self.get_nth_value(line, col_number) value = raw_value.flatmap(state.execute) setattr(statement_line, field, value) return statement_line
def parse_record(self, line): stmt_line = StatementLine() for field, col in self.mappings.items(): if col >= len(line): raise ValueError("Cannot find column %s in line of %s items " % (col, len(line))) rawvalue = line[col] value = self.parse_value(rawvalue, field) setattr(stmt_line, field, value) return stmt_line
def parse_record(self, row): stmt_line = StatementLine() stmt_line.date = self.parse_datetime(row[0]) stmt_line.date_user = self.parse_datetime(row[1]) stmt_line.memo = row[2] stmt_line.amount = self.parse_float(row[3]) stmt_line.id = generate_transaction_id(stmt_line) return stmt_line
def test_ofxWriter(self): # Create sample statement: statement = Statement("BID", "ACCID", "LTL") statement.lines.append(StatementLine( "1", datetime(2012, 2, 12), "Sample 1", 15.4)) line = StatementLine("2", datetime(2012, 2, 12), "Sample 2", 25.0) line.payee = '' line.bank_account_to = BankAccount("SNORAS", "LT1232") line.bank_account_to.branch_id = "VNO" statement.lines.append(line) # Create writer: writer = ofx.OfxWriter(statement) # Set the generation time so it is always predictable writer.genTime = datetime(2012, 3, 3, 0, 0, 0) assert prettyPrint(writer.toxml()) == SIMPLE_OFX
def _parse_line(self, ntry): sline = StatementLine() crdeb = self._find(ntry, 'CdtDbtInd').text amtnode = self._find(ntry, 'Amt') amt_ccy = amtnode.get('Ccy') if amt_ccy != self.statement.currency: # We can't include amounts with incompatible currencies into the # statement. return None amt = self._parse_amount(amtnode) if crdeb == CD_DEBIT: amt = -amt payee = self._find(ntry, 'NtryDtls/TxDtls/RltdPties/Cdtr/Nm') else: payee = self._find(ntry, 'NtryDtls/TxDtls/RltdPties/Dbtr/Nm') sline.payee = payee.text if payee is not None else None sline.amount = amt dt = self._find(ntry, 'ValDt') sline.date = self._parse_date(dt) bookdt = self._find(ntry, 'BookgDt') sline.date_user = self._parse_date(bookdt) svcref = self._find(ntry, 'NtryDtls/TxDtls/Refs/AcctSvcrRef') if svcref is None: svcref = self._find(ntry, 'AcctSvcrRef') if svcref is None: svcref = self._find(ntry, 'NtryDtls/TxDtls/Refs/MsgId') if svcref is not None: sline.refnum = svcref.text # Try to find memo from different possible locations rmtinf = self._find(ntry, 'NtryDtls/TxDtls/RmtInf/Ustrd') addinf = self._find(ntry, 'AddtlNtryInf') if rmtinf is not None: sline.memo = rmtinf.text elif addinf is not None: sline.memo = addinf.text return sline
def try_cache(self, stmt_line: StatementLine) -> None: account_id: str = str(self.statement.account_id) key: TransactionKey data: TransactionData # The PDF may differ from the OFX by a different date # or a switch from payee to memo or otherwise: forget them for dt in [ stmt_line.accounting_date, stmt_line.operation_date, stmt_line.value_date ]: check_no: Optional[str] name: Optional[str] if stmt_line.payee == 'VIREMENT SEPA' and not (stmt_line.check_no): check_no = None name = stmt_line.memo else: check_no = stmt_line.check_no name = None key = Parser.transaction_key(account_id, check_no, dt, stmt_line.amount, name) if key in self.cache: data = self.cache[key] logger.debug('Found data %r for key %r', data, key) stmt_line.date = dt stmt_line.id = data.id stmt_line.payee = data.name stmt_line.memo = data.memo return else: logger.debug('Did not find value for key %r', key) if not self.cache_printed: self.print_cache('try_cache: no data found') self.cache_printed = True return
def parse_record(self, line): """Parse given transaction line and return StatementLine object """ stmt_line = StatementLine() # date field stmt_line.date = self.xls_date(int(line[0])) # amount field if line[2]: income = line[2] outcome = 0 elif line[3]: outcome = line[3] income = 0 stmt_line.amount = income - outcome # transaction type field if (stmt_line.amount < 0): stmt_line.trntype = "DEBIT" else: stmt_line.trntype = "CREDIT" # name field # set <NAME> field with content of column 'Descrizione' # only if proper option is active if self.info2name: stmt_line.payee = line[4] # memo field stmt_line.memo = line[5] # concat "Descrizione" column at the end of <MEMO> field # if proper option is present if self.info2memo: if stmt_line.memo != '' and line[2] != '': stmt_line.memo += ' - ' stmt_line.memo += line[2] # id field stmt_line.id = generate_transaction_id(stmt_line) #print(str(stmt_line)) return stmt_line
def parse_record(self, line): # Namespace stuff namespaces = {"ns": line.tag[1:].partition("}")[0]} # Get all fields type_code = line.find("ns:TypeCode", namespaces=namespaces).text date = line.find("ns:BookDate", namespaces=namespaces).text c_or_d = line.find("ns:CorD", namespaces=namespaces).text amount = line.find("ns:AccAmt", namespaces=namespaces).text id = line.find("ns:BankRef", namespaces=namespaces).text note = line.find("ns:PmtInfo", namespaces=namespaces).text # Payee name payee_name = None payee = line.find("ns:CPartySet", namespaces=namespaces) if payee: payee_account = payee.find("ns:AccHolder", namespaces=namespaces) if payee_account: payee_name = payee_account.find("ns:Name", namespaces=namespaces).text # Create statement line stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount)) stmt_line.payee = payee_name # Credit & Debit stuff stmt_line.trntype = "DEP" if c_or_d == "D": stmt_line.amount = -stmt_line.amount stmt_line.trntype = "DEBIT" # Various types if type_code == "MEMD": stmt_line.trntype = "SRVCHG" elif type_code == "OUTP": stmt_line.trntype = "PAYMENT" # Check if paid by card m = CARD_PURCHASE_RE.match(stmt_line.memo) if m: # this is an electronic purchase. extract some useful # information from memo field date = m.group(1).split("/") date = "%s-%s-%s" % (date[2], date[1], date[0]) stmt_line.date_user = self.parse_datetime(date) # print(stmt_line) return stmt_line
def parse_record(self, row): self.row_num += 1 line = StatementLine() line.date = self.parse_datetime(row[0].value) line.date_user = self.parse_datetime(row[1].value) line.refnum = str(self.row_num) line.memo = row[2].value line.amount = row[3].value line.trntype = self.get_type(line) if self.statement.start_balance is None and self.row_num == 1: self.statement.start_balance = row[4].value - line.amount self.statement.start_date = line.date_user self.statement.end_balance = row[4].value line.id = self.generate_transaction_id(line) if line.id in self.seen: log.warn( "Transaction with duplicate FITID generated:\n%s\n%s\n\n" % (line, self.seen[line.id])) else: self.seen[line.id] = line return line
def parse_record(self, line): """Parse given transaction line and return StatementLine object """ #print(line) sl = StatementLine() sl = super(PostFinanceCreditParser, self).parse_record(line) #print(line) if not line[3]: sl.amount = self.parse_float(line[4]) sl.trntype = 'CREDIT' else: sl.amount = self.parse_float("-" + line[3]) sl.trntype = 'DEBIT' sl.payee = sl.memo return sl
def parse_record(self, stmt_line: StatementLine) -> StatementLine: """Parse given transaction line and return StatementLine object """ def add_years(d: date, years: int) -> date: """Return a date that's `years` years after the date object `d`. Return the same calendar date (month and day) in the destination year, if it exists, otherwise use the following day (thus changing February 29 to March 1). """ return d.replace(year=d.year + years, month=3, day=1) \ if d.month == 2 and d.day == 29 \ else d.replace(year=d.year + years) def get_date(d_m: str) -> date: assert self.statement.end_date # Without a year it will be 1900 so add the year d_m_y: str = "{}/{}".format(d_m, self.statement.end_date.year) d: date = datetime.strptime(d_m_y, '%d/%m/%Y').date() if d > self.statement.end_date: d = add_years(d, -1) assert d <= self.statement.end_date return d logger.debug('Statement line: %r', stmt_line) # Remove zero-value notifications if stmt_line.amount == 0: # pragma: no cover return None stmt_line.accounting_date = get_date(stmt_line.accounting_date) stmt_line.operation_date = get_date(stmt_line.operation_date) stmt_line.value_date = get_date(stmt_line.value_date) self.try_cache(stmt_line) if not stmt_line.id: stmt_line.date = stmt_line.accounting_date account_id = self.statement.account_id stmt_line.id = \ generate_unique_transaction_id(stmt_line, self.unique_id_sets[account_id]) m = re.match(r'([0-9a-f]+)(-\d+)?$', stmt_line.id) assert m, "Id should match hexadecimal digits, \ optionally followed by a minus and a counter: '{}'".format(stmt_line.id) if m.group(2): counter = int(m.group(2)[1:]) # include counter so the memo gets unique stmt_line.memo = stmt_line.memo + ' #' + str(counter + 1) return stmt_line
def _parse_line(self, ntry): sline = StatementLine() crdeb = _find(ntry, 'CdtDbtInd').text amtnode = _find(ntry, 'Amt') amt = self._parse_amount(amtnode) if crdeb == CD_DEBIT: amt = -amt payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Cdtr/Nm') else: payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Dbtr/Nm') if payee is not None: payee = payee.text sline.payee = payee sline.amount = amt dt = _find(ntry, 'ValDt') sline.date = self._parse_date(dt) bookdt = _find(ntry, 'BookgDt') sline.date_user = self._parse_date(bookdt) svcref = _find(ntry, 'NtryDtls/TxDtls/Refs/AcctSvcrRef') sline.refnum = getattr(svcref, 'text', None) rmtinf = _find(ntry, 'NtryDtls/TxDtls/RmtInf/Ustrd') sline.memo = rmtinf.text if rmtinf.text else '' addtlinf_node = _find(ntry, 'NtryDtls/TxDtls/AddtlTxInf') addtlinf = self._parse_addtlinf(addtlinf_node) if 'VÁSÁRLÁS KÁRTYÁVAL' == addtlinf and not sline.payee: sline.payee = _trim_payee(sline.memo) sline.memo += ' ' + addtlinf return sline
def test_ofxWriter(self) -> None: # Create sample statement: statement = Statement("BID", "ACCID", "LTL") statement.lines.append( StatementLine("1", datetime(2012, 2, 12), "Sample 1", Decimal("15.4"))) line = StatementLine("2", datetime(2012, 2, 12), "Sample 2", Decimal("25.0")) line.payee = "" line.bank_account_to = BankAccount("SNORAS", "LT1232") line.bank_account_to.branch_id = "VNO" line.currency = Currency("USD") line.orig_currency = Currency("EUR", Decimal("3.4543")) statement.lines.append(line) # Create writer: writer = ofx.OfxWriter(statement) # Set the generation time so it is always predictable writer.genTime = datetime(2012, 3, 3, 0, 0, 0) assert prettyPrint(writer.toxml()) == SIMPLE_OFX
def parse_record(self, transaction: Transaction) -> StatementLine: """Parse given transaction line and return StatementLine object """ logger.debug('transaction:\n' + pformat(transaction, indent=4)) stmt_line = None # Use str() to prevent rounding errors bank_account_to = transaction.data.get('customer_reference') amount = Decimal(str(transaction.data['amount'].amount)) memo = transaction.data['transaction_details'] memo = memo.replace("\n", '') memo = memo.replace(transaction.data['customer_reference'], '', 1) memo = memo.replace(transaction.data['extra_details'], '', 1).strip() memo = memo if memo != '' else 'UNKNOWN' payee = None if transaction.data['customer_reference'] != ''\ and transaction.data['extra_details'] != '': payee = "{1} ({0})".format(transaction.data['customer_reference'], transaction.data['extra_details']) date = transaction.data['date'] # Remove zero-value notifications if amount != 0: stmt_line = StatementLine(date=date, memo=memo, amount=amount) stmt_line.id = \ generate_unique_transaction_id(stmt_line, self.unique_id_set) m = re.match(r'([0-9a-f]+)(-\d+)?$', stmt_line.id) assert m, "Id should match hexadecimal digits, \ optionally followed by a minus and a counter: '{}'".format(stmt_line.id) if m.group(2): counter = int(m.group(2)[1:]) # include counter so the memo gets unique stmt_line.memo = stmt_line.memo + ' #' + str(counter + 1) stmt_line.payee = payee if bank_account_to: stmt_line.bank_account_to = \ BankAccount(bank_id=None, acct_id=bank_account_to) return stmt_line
def parse(self): DATA_DATE = 'data_date' DATA_AMT = 'data_amt' DATA_DSC = 'data_dsc' DATA_MEMO = 'data_mem' DATA_BAL = 'data_bal' def parser_old(filename): def parse_tr(tr): def get_header(tr, n): return str( tr.find_all(headers='header' + str(n))[0].string) hdrs = tr.find_all(headers=True) assert len(hdrs) in [0, 7] if len(hdrs) == 0: cont = True date_or_comm = str(tr.find_all(colspan='5')[0].string) desc = None paym = None depo = None bal = None else: cont = False date_or_comm = dateutil.parser.parse(get_header(tr, 1), dayfirst=True) desc = get_header(tr, 2) paym = self.get_float(get_header(tr, 5)) depo = self.get_float(get_header(tr, 6)) assert (depo is None) != ( paym is None ), 'Either depo or paym need to exist, but not both: ' + str( tr) bal = self.get_float(get_header(tr, 7)) return cont, date_or_comm, desc, paym, depo, bal with open(filename, 'r', encoding='iso-8859-8') as f: soup = BeautifulSoup(f, 'lxml') statement = Statement(currency='ILS') data = [] for tr in soup.find_all( 'table', id='mytable_body')[0].find_all(id='TR_ROW_BANKTABLE'): cont, date_or_comm, desc, paym, depo, bal = parse_tr(tr) if cont: data[-1][DATA_MEMO] = date_or_comm else: new_line = {} new_line[DATA_DATE] = date_or_comm new_line[DATA_DSC] = desc new_line[ DATA_AMT] = -paym if paym is not None else depo new_line[DATA_BAL] = bal new_line[DATA_MEMO] = None data.append(new_line) return data def parser_new(filename): with open(filename, 'r', encoding='iso-8859-8') as f: bs = BeautifulSoup(f, 'lxml') trs = bs.find_all('table', i__d='trBlueOnWhite12')[0].find_all( 'tr', recursive=False) data = [] for tr in trs[1:]: new_line = {} tds = tr.find_all('td') if len(tds) != 7: continue new_line[DATA_DATE] = dateutil.parser.parse(str(tds[0].string), dayfirst=True) new_line[DATA_DSC] = str(tds[1].string) paym = self.get_float(str(tds[4].string)) depo = self.get_float(str(tds[5].string)) assert (depo is None) != ( paym is None ), 'Either depo or paym need to exist, but not both: ' + str( tds) new_line[DATA_AMT] = -paym if paym is not None else depo new_line[DATA_BAL] = self.get_float(str(tds[6].string)) new_line[DATA_MEMO] = None data.append(new_line) return data def parser_xslx(filename): with open(filename, 'rb') as f: ws = openpyxl.load_workbook(f).worksheets[0] iterrows = iter(ws.rows) for _ in range(6): next(iterrows) data = [] for row in iterrows: new_line = {} new_line[DATA_DATE] = dateutil.parser.parse(str(row[0].value), dayfirst=False) new_line[DATA_DSC] = str(row[1].value) paym = self.get_float(str(row[3].value)) depo = self.get_float(str(row[4].value)) assert (depo is None) != ( paym is None), 'Either depo or paym need to exist, but not both: ' new_line[DATA_AMT] = -paym if paym is not None else depo new_line[DATA_BAL] = self.get_float(str(row[5].value)) new_line[DATA_MEMO] = " ".join( [str(c.value) for c in row[7:] if c.value is not None]) data.append(new_line) return data PARSERS = [parser_old, parser_new, parser_xslx] v = self.detect_version() assert v is not None, "Unsupported file %s" % self.filename self.log("Detected file of version %d" % v) data = PARSERS[v](self.filename) self.log('Found %d transactions' % len(data)) stmnt = Statement(currency='ILS') for d in data: stmt_line = StatementLine(date=d[DATA_DATE], amount=d[DATA_AMT]) #stmt_line.end_balance = d[DATA_BAL] #TODO: conf stmt_line.payee = d[DATA_DSC] if d[DATA_MEMO] is not None: stmt_line.memo = d[DATA_MEMO] if stmt_line.payee.startswith('משיכה'): stmt_line.trntype = 'ATM' elif stmt_line.payee.startswith('שיק'): stmt_line.trntype = 'CHECK' else: stmt_line.trntype = "CASH" if d[DATA_AMT] < 0 else "DEP" stmt_line.assert_valid() stmnt.lines.append(stmt_line) return stmnt
def parse_record(self, line): if self.cur_record < 2: return None m = None parse_info = None if len(line) == 11: m = self.mt940_mappings parse_info = self.parse_transaction_info_mt940 elif len(line) == 17: m = self.camt_mappings parse_info = self.parse_transaction_info_camt else: raise ValueError("invalid input line: '%s'" % line) if self.statement.account_id is None: self.statement.account_id = line[m["accid"]] sl = StatementLine() sl.date_avail = self.parse_datetime(line[m["valdate"]]) if len(line[m["date"]]) > 0: sl.date = self.parse_datetime(line[m["date"]]) else sl.date = sl.date_avail sl.amount = self.parse_float(line[m["amount"]]) sl.trntype = self.parse_transaction_type(sl.amount, line[m["btext"]]) # remove leading or all) zeros line[m["toaccid"]] = line[m["toaccid"]].lstrip('0') if len(line[m["toaccid"]]) > 0 and len(line[m["tobankid"]]) > 0: # additional bank information is present splitted = self.parse_iban(line[m["toaccid"]]) if splitted: sl.bank_account_to = BankAccount(**splitted) else: sl.bank_account_to = BankAccount(line[m["tobankid"]], line[m["toaccid"]]) if line[m["currency"]] != self.statement.currency: # different currency is used sl.currency = line[m["currency"]] # remove additional spaces in the payee sl.payee = re.sub(' +', ' ', line[m["payee"]])[:32] info = parse_info(line) # remove additional spaces in the memo sl.memo = "%s: %s" % (line[m["btext"]], re.sub(' +', ' ', info["memo"].strip())) # we need to generate an ID because nothing is given sl.id = generate_stable_transaction_id(sl) return sl
def testAmount(self): """Only amount is set""" sline = StatementLine() sline.amount = 123.45 self.assertEqual(self.amt_hash, generate_stable_transaction_id(sline))
def parse(self): DATA_DATE = 'data_date' DATA_AMT = 'data_amt' DATA_DSC = 'data_dsc' DATA_MEMO = 'data_mem' DATA_BAL = 'data_bal' def parser_old(filename): def parse_tr(tr): def get_header(tr, n): return str(tr.find_all(headers='header'+str(n))[0].string) hdrs = tr.find_all(headers=True) assert len(hdrs) in [0, 7] if len(hdrs) == 0: cont = True date_or_comm = str(tr.find_all(colspan='5')[0].string) desc = None paym = None depo = None bal = None else: cont = False date_or_comm = dateutil.parser.parse(get_header(tr, 1), dayfirst=True) desc = get_header(tr, 2) paym = self.get_float(get_header(tr, 5)) depo = self.get_float(get_header(tr, 6)) assert (depo is None) != (paym is None), 'Either depo or paym need to exist, but not both: ' + str(tr) bal = self.get_float(get_header(tr, 7)) return cont, date_or_comm, desc, paym, depo, bal with open(filename, 'r', encoding='iso-8859-8') as f: soup = BeautifulSoup(f, 'lxml') statement = Statement(currency='ILS') data = [] for tr in soup.find_all('table', id='mytable_body')[0].find_all(id='TR_ROW_BANKTABLE'): cont, date_or_comm, desc, paym, depo, bal = parse_tr(tr) if cont: data[-1][DATA_MEMO] = date_or_comm else: new_line = {} new_line[DATA_DATE] = date_or_comm new_line[DATA_DSC] = desc new_line[DATA_AMT] = -paym if paym is not None else depo new_line[DATA_BAL] = bal new_line[DATA_MEMO] = None data.append(new_line) return data def parser_new(filename): with open(filename, 'r', encoding='iso-8859-8') as f: bs = BeautifulSoup(f, 'lxml') trs = bs.find_all('table', i__d='trBlueOnWhite12')[0].find_all('tr', recursive=False) data = [] for tr in trs[1:]: new_line = {} tds = tr.find_all('td') if len(tds) != 7: continue new_line[DATA_DATE] = dateutil.parser.parse(str(tds[0].string), dayfirst=True) new_line[DATA_DSC] = str(tds[1].string) paym = self.get_float(str(tds[4].string)) depo = self.get_float(str(tds[5].string)) assert (depo is None) != (paym is None), 'Either depo or paym need to exist, but not both: ' + str(tds) new_line[DATA_AMT] = -paym if paym is not None else depo new_line[DATA_BAL] = self.get_float(str(tds[6].string)) new_line[DATA_MEMO] = None data.append(new_line) return data def parser_xslx(filename): with open(filename, 'rb') as f: ws = openpyxl.load_workbook(f).worksheets[0] iterrows = iter(ws.rows) for _ in range(6): next(iterrows) data = [] for row in iterrows: new_line = {} new_line[DATA_DATE] = dateutil.parser.parse(str(row[0].value), dayfirst=False) new_line[DATA_DSC] = str(row[1].value) paym = self.get_float(str(row[3].value)) depo = self.get_float(str(row[4].value)) assert (depo is None) != (paym is None), 'Either depo or paym need to exist, but not both: ' new_line[DATA_AMT] = -paym if paym is not None else depo new_line[DATA_BAL] = self.get_float(str(row[5].value)) new_line[DATA_MEMO] = " ".join([str(c.value) for c in row[7:] if c.value is not None]) data.append(new_line) return data PARSERS = [parser_old, parser_new, parser_xslx] v = self.detect_version() assert v is not None, "Unsupported file %s" % self.filename self.log("Detected file of version %d" % v) data = PARSERS[v](self.filename) self.log('Found %d transactions' % len(data)) stmnt = Statement(currency='ILS') for d in data: stmt_line = StatementLine(date=d[DATA_DATE], amount=d[DATA_AMT]) #stmt_line.end_balance = d[DATA_BAL] #TODO: conf stmt_line.payee = d[DATA_DSC] if d[DATA_MEMO] is not None: stmt_line.memo = d[DATA_MEMO] if stmt_line.payee.startswith('משיכה'): stmt_line.trntype = 'ATM' elif stmt_line.payee.startswith('שיק'): stmt_line.trntype = 'CHECK' else: stmt_line.trntype = "CASH" if d[DATA_AMT] < 0 else "DEP" stmt_line.assert_valid() stmnt.lines.append(stmt_line) return stmnt