From ea5e50ab569fc01cbd53c1b5a71d26b7bc9dc636 Mon Sep 17 00:00:00 2001 From: "Ronald Portier (Therp BV)" Date: Wed, 20 May 2015 00:36:24 +0200 Subject: [PATCH] [7.0] Forward port improvements in 7.0 branch. --- bank_statement_parse_mt940/mt940.py | 77 +++++++++++++-------- bank_statement_parse_nl_ing_mt940/mt940.py | 2 +- bank_statement_parse_nl_rabo_mt940/mt940.py | 23 ++---- 3 files changed, 58 insertions(+), 44 deletions(-) diff --git a/bank_statement_parse_mt940/mt940.py b/bank_statement_parse_mt940/mt940.py index c5da11e..e8b9f73 100644 --- a/bank_statement_parse_mt940/mt940.py +++ b/bank_statement_parse_mt940/mt940.py @@ -100,6 +100,10 @@ class MT940(object): variables as needed. At least, you should override handle_tag_61 and handle_tag_86. + Don't forget to call super. + + handle_tag_* functions receive the remainder of the the line (that is, + without ':XX:') and are supposed to write into self.current_transaction """ def __init__(self): @@ -114,16 +118,14 @@ class MT940(object): self.current_transaction = None self.statements = [] - def create_transaction(self): - """Create and return BankTransaction object.""" - transaction = parserlib.BankTransaction() - return transaction - def is_mt940(self, line): """determine if a line is the header of a statement""" if not bool(re.match(self.header_regex, line)): raise ValueError( - 'This does not seem to be a MT940 format bank statement.') + 'File starting with %s does not seem to be a' + ' MT940 format bank statement.' % + data[:12] + ) def parse(self, data): """Parse mt940 bank statement file contents.""" @@ -166,6 +168,10 @@ class MT940(object): """create a BankStatement.""" return parserlib.BankStatement() + def create_transaction(self): + """Create and return BankTransaction object.""" + return parserlib.BankTransaction() + def is_footer(self, line): """determine if a line is the footer of a statement""" return line and bool(re.match(self.footer_regex, line)) @@ -206,23 +212,49 @@ class MT940(object): self.current_statement.local_account = data def handle_tag_28C(self, data): - """get sequence number _within_this_batch_ - this alone - doesn't provide a unique id!""" - self.current_statement.statement_id = data + """Sequence number within batch - normally only zeroes.""" + pass def handle_tag_60F(self, data): """get start balance and currency""" - self.current_statement.local_currency = data[7:10] - self.current_statement.date = datetime.strptime(data[1:7], '%y%m%d') - self.current_statement.start_balance = str2amount(data[0], data[10:]) - self.current_statement.statement_id = '%s/%s' % ( - self.current_statement.date.strftime('%Y-%m-%d'), - self.current_statement.statement_id, - ) + # For the moment only first 60F record + # The alternative would be to split the file and start a new + # statement for each 20: tag encountered. + stmt = self.current_statement + if not stmt.local_currency: + stmt.local_currency = data[7:10] + stmt.start_balance = str2amount(data[0], data[10:]) + + def handle_tag_61(self, data): + """get transaction values""" + transaction = self.create_transaction() + self.current_statement.transactions.append(transaction) + self.current_transaction = transaction + transaction.execution_date = datetime.strptime(data[:6], '%y%m%d') + transaction.value_date = datetime.strptime(data[:6], '%y%m%d') + # ...and the rest already is highly bank dependent def handle_tag_62F(self, data): - """get ending balance""" - self.current_statement.end_balance = str2amount(data[0], data[10:]) + """Get ending balance, statement date and id. + + We use the date on the last 62F tag as statement date, as the date + on the 60F record (previous end balance) might contain a date in + a previous period. + + We generate the statement.id from the local_account and the end-date, + this should normally be unique, provided there is a maximum of + one statement per day. + + Depending on the bank, there might be multiple 62F tags in the import + file. The last one counts. + """ + stmt = self.current_statement + stmt.end_balance = str2amount(data[0], data[10:]) + stmt.date = datetime.strptime(data[1:7], '%y%m%d') + stmt.id = '%s-%s' % ( + stmt.local_account, + stmt.date.strftime('%Y-%m-%d'), + ) def handle_tag_64(self, data): """get current balance in currency""" @@ -232,15 +264,6 @@ class MT940(object): """get future balance in currency""" pass - def handle_tag_61(self, data): - """get transaction values""" - transaction = self.create_transaction() - self.current_statement.transactions.append(transaction) - self.current_transaction = transaction - transaction.execution_date = datetime.strptime(data[:6], '%y%m%d') - transaction.value_date = datetime.strptime(data[:6], '%y%m%d') - # ...and the rest already is highly bank dependent - def handle_tag_86(self, data): """details for previous transaction, here most differences between banks occur""" diff --git a/bank_statement_parse_nl_ing_mt940/mt940.py b/bank_statement_parse_nl_ing_mt940/mt940.py index 5f8061a..18beb09 100644 --- a/bank_statement_parse_nl_ing_mt940/mt940.py +++ b/bank_statement_parse_nl_ing_mt940/mt940.py @@ -2,7 +2,7 @@ """Implement BankStatementParser for MT940 IBAN ING files.""" ############################################################################## # -# Copyright (C) 2013 Therp BV () +# Copyright (C) 2014-2015 Therp BV . # All Rights Reserved # # This program is free software: you can redistribute it and/or modify diff --git a/bank_statement_parse_nl_rabo_mt940/mt940.py b/bank_statement_parse_nl_rabo_mt940/mt940.py index 1dd223a..48172d9 100644 --- a/bank_statement_parse_nl_rabo_mt940/mt940.py +++ b/bank_statement_parse_nl_rabo_mt940/mt940.py @@ -31,7 +31,9 @@ class MT940Parser(MT940): tag_61_regex = re.compile( r'^(?P\d{6})(?P[CD])(?P\d+,\d{2})N(?P.{3})' - r'(?P\w{1,16})') + r'(?PMARF|EREF|PREF|NONREF)\s*' + r'\n?(?P\w{1,16})?' + ) def __init__(self): """Initialize parser - override at least header_regex.""" @@ -51,27 +53,16 @@ class MT940Parser(MT940): data = ''.join([x for x in data if x in printable]) return super(MT940Parser, self).parse(data) - def handle_tag_60F(self, data): - """get start balance and currency""" - # For the moment only first 60F record - # The alternative would be to split the file and start a new - # statement for each 20: tag encountered. - stmt = self.current_statement - if not stmt.local_currency: - stmt.local_currency = data[7:10] - stmt.date = datetime.strptime(data[1:7], '%y%m%d') - stmt.start_balance = str2amount(data[0], data[10:]) - stmt.statement_id = '%s-%s' % ( - self.current_statement.date.strftime('%Y-%m-%d'), - self.current_statement.statement_id) - def handle_tag_61(self, data): """Handle tag 61: transaction data.""" super(MT940Parser, self).handle_tag_61(data) parsed_data = self.tag_61_regex.match(data).groupdict() self.current_transaction.transferred_amount = ( str2amount(parsed_data['sign'], parsed_data['amount'])) - self.current_transaction.eref = parsed_data['reference'] + self.current_transaction.reference = parsed_data['reference'] + if parsed_data['remote_account']: + self.current_transaction.remote_account = ( + parsed_data['remote_account']) def handle_tag_86(self, data): """Handle tag 86: transaction details"""