2
# -*- coding: utf-8 -*-
3
##############################################################################
5
# OpenERP, Open Source Management Solution
6
# This module copyright (C) 2014 Therp BV (<http://therp.nl>).
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.
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.
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/>.
21
##############################################################################
24
Parser for MT940 format files
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
34
#this allows us to run this file standalone, see __main__ at the end
35
class mem_bank_statement:
37
self.transactions = []
38
class mem_bank_transaction:
40
DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d"
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
47
Note that order matters: You need to do your_parser(MT940, parser), not the
50
At least, you should override handle_tag_61 and handle_tag_86. Don't forget
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'''
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'''
60
footer_regex = '^-XXX$'
61
'The line that denotes end of message, we need to create a new statement'
63
tag_regex = '^:[0-9]{2}[A-Z]*:'
64
'The beginning of a record, should be anchored to beginning of the line'
66
def __init__(self, *args, **kwargs):
67
super(MT940, self).__init__(*args, **kwargs)
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'
74
'parsed statements up to now'
76
def parse(self, cr, data):
77
'implements account_banking.parsers.models.parser.parse()'
78
iterator = data.split('\r\n').__iter__()
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)
91
self.handle_record(cr, record_line)
92
if self.is_footer(cr, line):
93
self.handle_footer(cr, line, iterator)
99
return self.statements
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
106
def create_statement(self, cr):
107
'''create a mem_bank_statement - override if you need a custom
109
return mem_bank_statement()
111
def create_transaction(self, cr):
112
'''create a mem_bank_transaction - override if you need a custom
114
return mem_bank_transaction()
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))
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))
124
def handle_header(self, cr, line, iterator):
125
'''skip header lines, create current statement'''
126
for i in range(self.header_lines):
128
self.current_statement = self.create_statement(cr)
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
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)
143
handler = getattr(self, 'handle_tag_%s' % tag)
144
handler(cr, line[tag_match.end():])
146
def handle_tag_20(self, cr, data):
147
'''ignore reference number'''
150
def handle_tag_25(self, cr, data):
151
'''get account owner information'''
152
self.current_statement.local_account = data
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
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)
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:])
174
def handle_tag_64(self, cr, data):
175
'''get current balance in currency'''
178
def handle_tag_65(self, cr, data):
179
'''get future balance in currency'''
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'
191
def handle_tag_86(self, cr, data):
192
'''details for previous transaction, here most differences between
197
def str2date(string, fmt='%y%m%d'):
198
return datetime.datetime.strptime(string, fmt)
200
def str2float(string):
201
return float(string.replace(',', '.'))
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:
213
transaction on %(execution_date)s''' % transaction.__dict__
215
if __name__ == '__main__':