[ADD] generic addon for MT940 parsing

[ADD] ING NL implementation of MT940
This commit is contained in:
unknown
2014-03-06 15:05:01 +01:00
committed by Stefan Rijnhart
parent c11062cac3
commit e7ec9397bb
8 changed files with 460 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import mt940

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
"name" : "MT940",
"version" : "1.0",
"author" : "Therp BV",
"complexity": "expert",
"description": """
This addon provides a generic parser for MT940 files. Given that MT940 is a
non-open non-standard of pure evil in the way that every bank cooks up its own
interpretation of it, this addon alone won't help you much. It is rather
intended to be used by other addons to implement the dialect specific to a
certain bank.
See account_banking_nl_ing_mt940 for an example on how to use it.
""",
"category" : "Dependency",
"depends" : [
'account_banking',
],
"data" : [
],
"js": [
],
"css": [
],
"qweb": [
],
"auto_install": False,
"installable": True,
"application": False,
"external_dependencies" : {
'python' : [],
},
}

View File

@@ -0,0 +1,217 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
"""
Parser for MT940 format files
"""
import re
import datetime
import logging
try:
from openerp.addons.account_banking.parsers.models import\
mem_bank_statement, mem_bank_transaction
from openerp.tools.misc import DEFAULT_SERVER_DATE_FORMAT
except ImportError:
#this allows us to run this file standalone, see __main__ at the end
class mem_bank_statement:
def __init__(self):
self.transactions = []
class mem_bank_transaction:
pass
DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
class MT940(object):
'''Inherit this class in your account_banking.parsers.models.parser,
define functions to handle the tags you need to handle and adjust static
variables as needed.
Note that order matters: You need to do your_parser(MT940, parser), not the
other way around!
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'''
header_lines = 3
'''One file can contain multiple statements, each with its own poorly
documented header. For now, the best thing to do seems to skip that'''
footer_regex = '^-}$'
footer_regex = '^-XXX$'
'The line that denotes end of message, we need to create a new statement'
tag_regex = '^:[0-9]{2}[A-Z]*:'
'The beginning of a record, should be anchored to beginning of the line'
def __init__(self, *args, **kwargs):
super(MT940, self).__init__(*args, **kwargs)
'state variables'
self.current_statement = None
'type account_banking.parsers.models.mem_bank_statement'
self.current_transaction = None
'type account_banking.parsers.models.mem_bank_transaction'
self.statements = []
'parsed statements up to now'
def parse(self, cr, data):
'implements account_banking.parsers.models.parser.parse()'
iterator = data.split('\r\n').__iter__()
line = None
record_line = ''
try:
while True:
if not self.current_statement:
self.handle_header(cr, line, iterator)
line = iterator.next()
if not self.is_tag(cr, line) and not self.is_footer(cr, line):
record_line = self.append_continuation_line(
cr, record_line, line)
continue
if record_line:
self.handle_record(cr, record_line)
if self.is_footer(cr, line):
self.handle_footer(cr, line, iterator)
record_line = ''
continue
record_line = line
except StopIteration:
pass
return self.statements
def append_continuation_line(self, cr, line, continuation_line):
'''append a continuation line for a multiline record.
Override and do data cleanups as necessary.'''
return line + continuation_line
def create_statement(self, cr):
'''create a mem_bank_statement - override if you need a custom
implementation'''
return mem_bank_statement()
def create_transaction(self, cr):
'''create a mem_bank_transaction - override if you need a custom
implementation'''
return mem_bank_transaction()
def is_footer(self, cr, line):
'''determine if a line is the footer of a statement'''
return line and bool(re.match(self.footer_regex, line))
def is_tag(self, cr, line):
'''determine if a line has a tag'''
return line and bool(re.match(self.tag_regex, line))
def handle_header(self, cr, line, iterator):
'''skip header lines, create current statement'''
for i in range(self.header_lines):
iterator.next()
self.current_statement = self.create_statement(cr)
def handle_footer(self, cr, line, iterator):
'''add current statement to list, reset state'''
self.statements.append(self.current_statement)
self.current_statement = None
def handle_record(self, cr, line):
'''find a function to handle the record represented by line'''
tag_match = re.match(self.tag_regex, line)
tag = tag_match.group(0).strip(':')
if not hasattr(self, 'handle_tag_%s' % tag):
logging.error('Unknown tag %s', tag)
logging.error(line)
return
handler = getattr(self, 'handle_tag_%s' % tag)
handler(cr, line[tag_match.end():])
def handle_tag_20(self, cr, data):
'''ignore reference number'''
pass
def handle_tag_25(self, cr, data):
'''get account owner information'''
self.current_statement.local_account = data
def handle_tag_28C(self, cr, data):
'''get sequence number _within_this_batch_ - this alone
doesn't provide a unique id!'''
self.current_statement.id = data
def handle_tag_60F(self, cr, data):
'''get start balance and currency'''
self.current_statement.local_currency = data[7:10]
self.current_statement.date = str2date(data[1:7])
self.current_statement.start_balance = \
(1 if data[0] == 'C' else -1) * str2float(data[10:])
self.current_statement.id = '%s/%s' % (
self.current_statement.date.strftime('%Y'),
self.current_statement.id)
def handle_tag_62F(self, cr, data):
'''get ending balance'''
self.current_statement.end_balance = \
(1 if data[0] == 'C' else -1) * str2float(data[10:])
def handle_tag_64(self, cr, data):
'''get current balance in currency'''
pass
def handle_tag_65(self, cr, data):
'''get future balance in currency'''
pass
def handle_tag_61(self, cr, data):
'''get transaction values'''
transaction = self.create_transaction(cr)
self.current_statement.transactions.append(transaction)
self.current_transaction = transaction
transaction.execution_date = str2date(data[:6])
transaction.effective_date = str2date(data[:6])
'...and the rest already is highly bank dependent'
def handle_tag_86(self, cr, data):
'''details for previous transaction, here most differences between
banks occur'''
pass
'utility functions'
def str2date(string, fmt='%y%m%d'):
return datetime.datetime.strptime(string, fmt)
def str2float(string):
return float(string.replace(',', '.'))
'testing'
def main(filename):
parser = MT940()
parser.parse(None, open(filename, 'r').read())
for statement in parser.statements:
print '''statement found for %(local_account)s at %(date)s
with %(local_currency)s%(start_balance)s to %(end_balance)s
''' % statement.__dict__
for transaction in statement.transactions:
print '''
transaction on %(execution_date)s''' % transaction.__dict__
if __name__ == '__main__':
import sys
main(*sys.argv[1:])

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import account_banking_nl_ing_mt940

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
"name" : "MT940 import for Dutch ING",
"version" : "1.0",
"author" : "Therp BV",
"complexity": "normal",
"description": """
This addon imports the structured MT940 format as offered by the Dutch ING
bank.
""",
"category" : "Account Banking",
"depends" : [
'account_banking_mt940',
],
"data" : [
],
"js": [
],
"css": [
],
"qweb": [
],
"auto_install": False,
"installable": True,
"application": False,
"external_dependencies" : {
'python' : [],
},
}

View File

@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import re
from openerp.tools.translate import _
from openerp.addons.account_banking.parsers.models import parser,\
mem_bank_transaction
from openerp.addons.account_banking_mt940.mt940 import MT940, str2float
class transaction(mem_bank_transaction):
def is_valid(self):
'''allow transactions without remote account'''
return bool(self.execution_date) and bool(self.transferred_amount)
class IngMT940Parser(MT940, parser):
name = _('ING MT940 (structured)')
country_code = 'NL'
code = 'INT_MT940_STRUC'
tag_61_regex = re.compile(
'^(?P<date>\d{6})(?P<sign>[CD])(?P<amount>\d+,\d{2})N(?P<type>\d{3})'
'(?P<reference>\w{1,16})')
def create_transaction(self, cr):
return transaction()
def handle_tag_60F(self, cr, data):
super(IngMT940Parser, self).handle_tag_60F(cr, data)
self.current_statement.id = '%s-%s' % (
self.get_unique_account_identifier(
cr, self.current_statement.local_account),
self.current_statement.id)
def handle_tag_61(self, cr, data):
super(IngMT940Parser, self).handle_tag_61(cr, data)
parsed_data = self.tag_61_regex.match(data).groupdict()
self.current_transaction.transferred_amount = \
(-1 if parsed_data['sign'] == 'D' else 1) * str2float(
parsed_data['amount'])
self.current_transaction.reference = parsed_data['reference']
def handle_tag_86(self, cr, data):
if not self.current_transaction:
return
super(IngMT940Parser, self).handle_tag_86(cr, data)
codewords = ['RTRN', 'BENM', 'ORDP', 'CSID', 'BUSP', 'MARF', 'EREF',
'PREF', 'REMI', 'ID', 'PURP', 'ULTB', 'ULTD']
subfields = {}
current_codeword = None
for word in data.split('/'):
if not word and not current_codeword:
continue
if word in codewords:
current_codeword = word
subfields[current_codeword] = []
continue
subfields[current_codeword].append(word)
if 'BENM' in subfields:
self.current_transaction.remote_account = subfields['BENM'][0]
self.current_transaction.remote_bank_bic = subfields['BENM'][1]
self.current_transaction.remote_owner = subfields['BENM'][2]
self.current_transaction.remote_owner_city = subfields['BENM'][3]
if 'ORDP' in subfields:
self.current_transaction.remote_account = subfields['ORDP'][0]
self.current_transaction.remote_bank_bic = subfields['ORDP'][1]
self.current_transaction.remote_owner = subfields['ORDP'][2]
self.current_transaction.remote_owner_city = subfields['ORDP'][3]
if 'REMI' in subfields:
self.current_transaction.message = '/'.join(
filter(lambda x: bool(x), subfields['REMI']))
if self.current_transaction.reference in subfields:
self.current_transaction.reference = ''.join(
subfields[self.current_transaction.reference])
if not subfields:
self.current_transaction.message = data
self.current_transaction = None

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB