Merge lp:~therp-nl/banking-addons/6.1-account_banking_nl_ing_mt940 into lp:banking-addons/6.1

Proposed by Holger Brunn (Therp)
Status: Merged
Merged at revision: 195
Proposed branch: lp:~therp-nl/banking-addons/6.1-account_banking_nl_ing_mt940
Merge into: lp:banking-addons/6.1
Diff against target: 502 lines (+460/-0)
6 files modified
account_banking_mt940/__init__.py (+21/-0)
account_banking_mt940/__openerp__.py (+53/-0)
account_banking_mt940/mt940.py (+217/-0)
account_banking_nl_ing_mt940/__init__.py (+21/-0)
account_banking_nl_ing_mt940/__openerp__.py (+48/-0)
account_banking_nl_ing_mt940/account_banking_nl_ing_mt940.py (+100/-0)
To merge this branch: bzr merge lp:~therp-nl/banking-addons/6.1-account_banking_nl_ing_mt940
Reviewer Review Type Date Requested Status
Guewen Baconnier @ Camptocamp Approve
Stefan Rijnhart (Opener) Approve
Review via email: mp+208430@code.launchpad.net

Description of the change

This adds a generic mt940 parser (account_banking_mt940) and on top of that a parser for ING's version of MT940 in the Netherlands.

The hope would be that account_banking_mt940 makes it very simple to support other MT940 dialects.

To post a comment you must log in.
197. By Holger Brunn (Therp)

[FIX] typos

198. By Holger Brunn (Therp)

[IMP] code formatting, forgotten comment

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

Awesome, finally a generic MT940 parser!

Just two comments from code review, I'll do some testing later on:
- l.122 You might want to add a comment that the exception handling here allows you to execute the file to test the parser outside the context of the OpenERP server (I think).
- l.165 Quite a fashion statement ;-) I know this runs, but you might want to either add two single quotes at the start or remove two at the end so that it looks like a regular docstring.

review: Needs Fixing
199. By Holger Brunn (Therp)

[FIX] typos, manifest formatting, comments
[RFR] allow using custom mem_bank_{statement,transaction}s in a
convenient way

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

thanks for the points, I fixed a typo and added a minor refactoring

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

Thanks. I have not been able to find a sample statement file for this particular bank, so I won't be able to test. But the code looks excellent so it will have to do. Maybe there are other reviewers who have access to ING structured MT940 statement files?

review: Approve
200. By Holger Brunn (Therp)

[FIX] pass cr to refactored method

201. By Holger Brunn (Therp)

[ADD] allow transaction without remote_account
[ADD] get some information for unstructured mt940

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

> 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.

hmmm I trust you and will try to stay away of this evil dialect.

review: Approve
Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

well, fortunately you have account_banking_mt940 now to use as a pitchfork if you ever face a situation where you can't avoid it

Revision history for this message
Guewen Baconnier @ Camptocamp (gbaconnier-c2c) wrote :

I will remember of that, thanks ;-)

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

Merged into 6.1, and cherrypicked unmodified in 7.0.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added directory 'account_banking_mt940'
=== added file 'account_banking_mt940/__init__.py'
--- account_banking_mt940/__init__.py 1970-01-01 00:00:00 +0000
+++ account_banking_mt940/__init__.py 2014-02-28 13:34:37 +0000
@@ -0,0 +1,21 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21from . import mt940
022
=== added file 'account_banking_mt940/__openerp__.py'
--- account_banking_mt940/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_banking_mt940/__openerp__.py 2014-02-28 13:34:37 +0000
@@ -0,0 +1,53 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21{
22 "name" : "MT940",
23 "version" : "1.0",
24 "author" : "Therp BV",
25 "complexity": "expert",
26 "description": """
27This addon provides a generic parser for MT940 files. Given that MT940 is a
28non-open non-standard of pure evil in the way that every bank cooks up its own
29interpretation of it, this addon alone won't help you much. It is rather
30intended to be used by other addons to implement the dialect specific to a
31certain bank.
32
33See account_banking_nl_ing_mt940 for an example on how to use it.
34 """,
35 "category" : "Dependency",
36 "depends" : [
37 'account_banking',
38 ],
39 "data" : [
40 ],
41 "js": [
42 ],
43 "css": [
44 ],
45 "qweb": [
46 ],
47 "auto_install": False,
48 "installable": True,
49 "application": False,
50 "external_dependencies" : {
51 'python' : [],
52 },
53}
054
=== added file 'account_banking_mt940/mt940.py'
--- account_banking_mt940/mt940.py 1970-01-01 00:00:00 +0000
+++ account_banking_mt940/mt940.py 2014-02-28 13:34:37 +0000
@@ -0,0 +1,217 @@
1#!/usr/bin/env python2
2# -*- coding: utf-8 -*-
3##############################################################################
4#
5# OpenERP, Open Source Management Solution
6# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
7#
8# This program is free software: you can redistribute it and/or modify
9# it under the terms of the GNU Affero General Public License as
10# published by the Free Software Foundation, either version 3 of the
11# License, or (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Affero General Public License for more details.
17#
18# You should have received a copy of the GNU Affero General Public License
19# along with this program. If not, see <http://www.gnu.org/licenses/>.
20#
21##############################################################################
22
23"""
24Parser for MT940 format files
25"""
26import re
27import datetime
28import logging
29try:
30 from openerp.addons.account_banking.parsers.models import\
31 mem_bank_statement, mem_bank_transaction
32 from openerp.tools.misc import DEFAULT_SERVER_DATE_FORMAT
33except ImportError:
34 #this allows us to run this file standalone, see __main__ at the end
35 class mem_bank_statement:
36 def __init__(self):
37 self.transactions = []
38 class mem_bank_transaction:
39 pass
40 DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
41
42class MT940(object):
43 '''Inherit this class in your account_banking.parsers.models.parser,
44 define functions to handle the tags you need to handle and adjust static
45 variables as needed.
46
47 Note that order matters: You need to do your_parser(MT940, parser), not the
48 other way around!
49
50 At least, you should override handle_tag_61 and handle_tag_86. Don't forget
51 to call super.
52 handle_tag_* functions receive the remainder of the the line (that is,
53 without ':XX:') and are supposed to write into self.current_transaction'''
54
55 header_lines = 3
56 '''One file can contain multiple statements, each with its own poorly
57 documented header. For now, the best thing to do seems to skip that'''
58
59 footer_regex = '^-}$'
60 footer_regex = '^-XXX$'
61 'The line that denotes end of message, we need to create a new statement'
62
63 tag_regex = '^:[0-9]{2}[A-Z]*:'
64 'The beginning of a record, should be anchored to beginning of the line'
65
66 def __init__(self, *args, **kwargs):
67 super(MT940, self).__init__(*args, **kwargs)
68 'state variables'
69 self.current_statement = None
70 'type account_banking.parsers.models.mem_bank_statement'
71 self.current_transaction = None
72 'type account_banking.parsers.models.mem_bank_transaction'
73 self.statements = []
74 'parsed statements up to now'
75
76 def parse(self, cr, data):
77 'implements account_banking.parsers.models.parser.parse()'
78 iterator = data.split('\r\n').__iter__()
79 line = None
80 record_line = ''
81 try:
82 while True:
83 if not self.current_statement:
84 self.handle_header(cr, line, iterator)
85 line = iterator.next()
86 if not self.is_tag(cr, line) and not self.is_footer(cr, line):
87 record_line = self.append_continuation_line(
88 cr, record_line, line)
89 continue
90 if record_line:
91 self.handle_record(cr, record_line)
92 if self.is_footer(cr, line):
93 self.handle_footer(cr, line, iterator)
94 record_line = ''
95 continue
96 record_line = line
97 except StopIteration:
98 pass
99 return self.statements
100
101 def append_continuation_line(self, cr, line, continuation_line):
102 '''append a continuation line for a multiline record.
103 Override and do data cleanups as necessary.'''
104 return line + continuation_line
105
106 def create_statement(self, cr):
107 '''create a mem_bank_statement - override if you need a custom
108 implementation'''
109 return mem_bank_statement()
110
111 def create_transaction(self, cr):
112 '''create a mem_bank_transaction - override if you need a custom
113 implementation'''
114 return mem_bank_transaction()
115
116 def is_footer(self, cr, line):
117 '''determine if a line is the footer of a statement'''
118 return line and bool(re.match(self.footer_regex, line))
119
120 def is_tag(self, cr, line):
121 '''determine if a line has a tag'''
122 return line and bool(re.match(self.tag_regex, line))
123
124 def handle_header(self, cr, line, iterator):
125 '''skip header lines, create current statement'''
126 for i in range(self.header_lines):
127 iterator.next()
128 self.current_statement = self.create_statement(cr)
129
130 def handle_footer(self, cr, line, iterator):
131 '''add current statement to list, reset state'''
132 self.statements.append(self.current_statement)
133 self.current_statement = None
134
135 def handle_record(self, cr, line):
136 '''find a function to handle the record represented by line'''
137 tag_match = re.match(self.tag_regex, line)
138 tag = tag_match.group(0).strip(':')
139 if not hasattr(self, 'handle_tag_%s' % tag):
140 logging.error('Unknown tag %s', tag)
141 logging.error(line)
142 return
143 handler = getattr(self, 'handle_tag_%s' % tag)
144 handler(cr, line[tag_match.end():])
145
146 def handle_tag_20(self, cr, data):
147 '''ignore reference number'''
148 pass
149
150 def handle_tag_25(self, cr, data):
151 '''get account owner information'''
152 self.current_statement.local_account = data
153
154 def handle_tag_28C(self, cr, data):
155 '''get sequence number _within_this_batch_ - this alone
156 doesn't provide a unique id!'''
157 self.current_statement.id = data
158
159 def handle_tag_60F(self, cr, data):
160 '''get start balance and currency'''
161 self.current_statement.local_currency = data[7:10]
162 self.current_statement.date = str2date(data[1:7])
163 self.current_statement.start_balance = \
164 (1 if data[0] == 'C' else -1) * str2float(data[10:])
165 self.current_statement.id = '%s/%s' % (
166 self.current_statement.date.strftime('%Y'),
167 self.current_statement.id)
168
169 def handle_tag_62F(self, cr, data):
170 '''get ending balance'''
171 self.current_statement.end_balance = \
172 (1 if data[0] == 'C' else -1) * str2float(data[10:])
173
174 def handle_tag_64(self, cr, data):
175 '''get current balance in currency'''
176 pass
177
178 def handle_tag_65(self, cr, data):
179 '''get future balance in currency'''
180 pass
181
182 def handle_tag_61(self, cr, data):
183 '''get transaction values'''
184 transaction = self.create_transaction(cr)
185 self.current_statement.transactions.append(transaction)
186 self.current_transaction = transaction
187 transaction.execution_date = str2date(data[:6])
188 transaction.effective_date = str2date(data[:6])
189 '...and the rest already is highly bank dependent'
190
191 def handle_tag_86(self, cr, data):
192 '''details for previous transaction, here most differences between
193 banks occur'''
194 pass
195
196'utility functions'
197def str2date(string, fmt='%y%m%d'):
198 return datetime.datetime.strptime(string, fmt)
199
200def str2float(string):
201 return float(string.replace(',', '.'))
202
203'testing'
204def main(filename):
205 parser = MT940()
206 parser.parse(None, open(filename, 'r').read())
207 for statement in parser.statements:
208 print '''statement found for %(local_account)s at %(date)s
209 with %(local_currency)s%(start_balance)s to %(end_balance)s
210 ''' % statement.__dict__
211 for transaction in statement.transactions:
212 print '''
213 transaction on %(execution_date)s''' % transaction.__dict__
214
215if __name__ == '__main__':
216 import sys
217 main(*sys.argv[1:])
0218
=== added directory 'account_banking_mt940/static'
=== added directory 'account_banking_mt940/static/src'
=== added directory 'account_banking_mt940/static/src/img'
=== added file 'account_banking_mt940/static/src/img/icon.png'
1Binary files account_banking_mt940/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and account_banking_mt940/static/src/img/icon.png 2014-02-28 13:34:37 +0000 differ219Binary files account_banking_mt940/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and account_banking_mt940/static/src/img/icon.png 2014-02-28 13:34:37 +0000 differ
=== added directory 'account_banking_nl_ing_mt940'
=== added file 'account_banking_nl_ing_mt940/__init__.py'
--- account_banking_nl_ing_mt940/__init__.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing_mt940/__init__.py 2014-02-28 13:34:37 +0000
@@ -0,0 +1,21 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21from . import account_banking_nl_ing_mt940
022
=== added file 'account_banking_nl_ing_mt940/__openerp__.py'
--- account_banking_nl_ing_mt940/__openerp__.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing_mt940/__openerp__.py 2014-02-28 13:34:37 +0000
@@ -0,0 +1,48 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21{
22 "name" : "MT940 import for Dutch ING",
23 "version" : "1.0",
24 "author" : "Therp BV",
25 "complexity": "normal",
26 "description": """
27This addon imports the structured MT940 format as offered by the Dutch ING
28bank.
29 """,
30 "category" : "Account Banking",
31 "depends" : [
32 'account_banking_mt940',
33 ],
34 "data" : [
35 ],
36 "js": [
37 ],
38 "css": [
39 ],
40 "qweb": [
41 ],
42 "auto_install": False,
43 "installable": True,
44 "application": False,
45 "external_dependencies" : {
46 'python' : [],
47 },
48}
049
=== added file 'account_banking_nl_ing_mt940/account_banking_nl_ing_mt940.py'
--- account_banking_nl_ing_mt940/account_banking_nl_ing_mt940.py 1970-01-01 00:00:00 +0000
+++ account_banking_nl_ing_mt940/account_banking_nl_ing_mt940.py 2014-02-28 13:34:37 +0000
@@ -0,0 +1,100 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21import re
22from openerp.tools.translate import _
23from openerp.addons.account_banking.parsers.models import parser,\
24 mem_bank_transaction
25from openerp.addons.account_banking_mt940.mt940 import MT940, str2float
26
27
28class transaction(mem_bank_transaction):
29 def is_valid(self):
30 '''allow transactions without remote account'''
31 return bool(self.execution_date) and bool(self.transferred_amount)
32
33class IngMT940Parser(MT940, parser):
34 name = _('ING MT940 (structured)')
35 country_code = 'NL'
36 code = 'INT_MT940_STRUC'
37
38 tag_61_regex = re.compile(
39 '^(?P<date>\d{6})(?P<sign>[CD])(?P<amount>\d+,\d{2})N(?P<type>\d{3})'
40 '(?P<reference>\w{1,16})')
41
42 def create_transaction(self, cr):
43 return transaction()
44
45 def handle_tag_60F(self, cr, data):
46 super(IngMT940Parser, self).handle_tag_60F(cr, data)
47 self.current_statement.id = '%s-%s' % (
48 self.get_unique_account_identifier(
49 cr, self.current_statement.local_account),
50 self.current_statement.id)
51
52 def handle_tag_61(self, cr, data):
53 super(IngMT940Parser, self).handle_tag_61(cr, data)
54 parsed_data = self.tag_61_regex.match(data).groupdict()
55 self.current_transaction.transferred_amount = \
56 (-1 if parsed_data['sign'] == 'D' else 1) * str2float(
57 parsed_data['amount'])
58 self.current_transaction.reference = parsed_data['reference']
59
60 def handle_tag_86(self, cr, data):
61 if not self.current_transaction:
62 return
63 super(IngMT940Parser, self).handle_tag_86(cr, data)
64 codewords = ['RTRN', 'BENM', 'ORDP', 'CSID', 'BUSP', 'MARF', 'EREF',
65 'PREF', 'REMI', 'ID', 'PURP', 'ULTB', 'ULTD']
66 subfields = {}
67 current_codeword = None
68 for word in data.split('/'):
69 if not word and not current_codeword:
70 continue
71 if word in codewords:
72 current_codeword = word
73 subfields[current_codeword] = []
74 continue
75 subfields[current_codeword].append(word)
76
77 if 'BENM' in subfields:
78 self.current_transaction.remote_account = subfields['BENM'][0]
79 self.current_transaction.remote_bank_bic = subfields['BENM'][1]
80 self.current_transaction.remote_owner = subfields['BENM'][2]
81 self.current_transaction.remote_owner_city = subfields['BENM'][3]
82
83 if 'ORDP' in subfields:
84 self.current_transaction.remote_account = subfields['ORDP'][0]
85 self.current_transaction.remote_bank_bic = subfields['ORDP'][1]
86 self.current_transaction.remote_owner = subfields['ORDP'][2]
87 self.current_transaction.remote_owner_city = subfields['ORDP'][3]
88
89 if 'REMI' in subfields:
90 self.current_transaction.message = '/'.join(
91 filter(lambda x: bool(x), subfields['REMI']))
92
93 if self.current_transaction.reference in subfields:
94 self.current_transaction.reference = ''.join(
95 subfields[self.current_transaction.reference])
96
97 if not subfields:
98 self.current_transaction.message = data
99
100 self.current_transaction = None
0101
=== added directory 'account_banking_nl_ing_mt940/static'
=== added directory 'account_banking_nl_ing_mt940/static/src'
=== added directory 'account_banking_nl_ing_mt940/static/src/img'
=== added file 'account_banking_nl_ing_mt940/static/src/img/icon.png'
1Binary files account_banking_nl_ing_mt940/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and account_banking_nl_ing_mt940/static/src/img/icon.png 2014-02-28 13:34:37 +0000 differ102Binary files account_banking_nl_ing_mt940/static/src/img/icon.png 1970-01-01 00:00:00 +0000 and account_banking_nl_ing_mt940/static/src/img/icon.png 2014-02-28 13:34:37 +0000 differ

Subscribers

People subscribed via source and target branches