~akretion-team/banking-addons/70-fully-handle-payment-types

« back to all changes in this revision

Viewing changes to account_banking_mt940/mt940.py

  • Committer: Raphaël Valyi
  • Date: 2014-03-21 19:12:47 UTC
  • mfrom: (225.1.11 7.0)
  • Revision ID: rvalyi@gmail.com-20140321191247-zqytcf091nlbxboo
[MERGE] merged HEAD into current feature branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
"""
 
24
Parser for MT940 format files
 
25
"""
 
26
import re
 
27
import datetime
 
28
import logging
 
29
try:
 
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
 
33
except 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
 
 
42
class 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'
 
197
def str2date(string, fmt='%y%m%d'):
 
198
    return datetime.datetime.strptime(string, fmt)
 
199
 
 
200
def str2float(string):
 
201
    return float(string.replace(',', '.'))
 
202
 
 
203
'testing'
 
204
def 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
 
 
215
if __name__ == '__main__':
 
216
    import sys
 
217
    main(*sys.argv[1:])