~therp-nl/banking-addons/6.1-abnamro_payment_recognition

« back to all changes in this revision

Viewing changes to account_banking_nl_ing/ing.py

  • Committer: OpenERP instance user
  • Date: 2011-12-11 16:09:20 UTC
  • Revision ID: oersmldev6@midgard.therp.nl-20111211160920-v4rac2486nr6da5m
[ADD] Parser for ING NL statements

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Copyright (C) 2011 Therp BV (<http://therp.nl>).
 
5
#
 
6
#    Based on the multibank module by EduSense BV
 
7
#    Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>),
 
8
#
 
9
#    All Rights Reserved
 
10
#
 
11
#    This program is free software: you can redistribute it and/or modify
 
12
#    it under the terms of the GNU General Public License as published by
 
13
#    the Free Software Foundation, either version 3 of the License, or
 
14
#    (at your option) any later version.
 
15
#
 
16
#    This program is distributed in the hope that it will be useful,
 
17
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
18
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
19
#    GNU General Public License for more details.
 
20
#
 
21
#    You should have received a copy of the GNU General Public License
 
22
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
23
#
 
24
##############################################################################
 
25
 
 
26
from account_banking.parsers import models
 
27
from account_banking.parsers.convert import str2date
 
28
from account_banking.sepa import postalcode
 
29
from tools.translate import _
 
30
 
 
31
import re
 
32
import csv
 
33
 
 
34
__all__ = ['parser']
 
35
 
 
36
bt = models.mem_bank_transaction
 
37
 
 
38
"""
 
39
First line states the legend
 
40
"Datum","Naam / Omschrijving","Rekening","Tegenrekening","Code","Af Bij","Bedrag (EUR)","MutatieSoort","Mededelingen
 
41
 
 
42
"""
 
43
 
 
44
class transaction_message(object):
 
45
    '''
 
46
    A auxiliary class to validate and coerce read values
 
47
    '''
 
48
    attrnames = [
 
49
        'date', 'remote_owner', 'local_account', 'remote_account',
 
50
        'transfer_type', 'debcred', 'transferred_amount',
 
51
        'transfer_type_verbose', 'message'
 
52
 
 
53
#        'date', 'local_account', 'transferred_amount', 'debcred',
 
54
#        'remote_owner', 'remote_account', 'transfer_type', 'reference',
 
55
    ]
 
56
 
 
57
    ref_expr = re.compile('REF[\*:]([0-9A-Z-z_-]+)')
 
58
 
 
59
    def __init__(self, values, subno):
 
60
        '''
 
61
        Initialize own dict with attributes and coerce values to right type
 
62
        '''
 
63
        if len(self.attrnames) != len(values):
 
64
            raise ValueError(
 
65
                _('Invalid transaction line: expected %d columns, found %d')
 
66
                % (len(self.attrnames), len(values)))
 
67
        self.__dict__.update(dict(zip(self.attrnames, values)))
 
68
        # for lack of a standardized locale function to parse amounts
 
69
        self.transferred_amount = float(
 
70
            re.sub(',', '.', self.transferred_amount))
 
71
        if self.debcred == 'Af':
 
72
            self.transferred_amount = -self.transferred_amount
 
73
        self.execution_date = self.effective_date = str2date(self.date, '%Y%m%d')
 
74
        # Set statement_id based on week number
 
75
        self.statement_id = self.effective_date.strftime('%Yw%W')
 
76
        self.id = str(subno).zfill(4)
 
77
        # Normalize basic account numbers
 
78
        self.remote_account = self.remote_account.replace('.', '').zfill(10)
 
79
        self.local_account = self.local_account.replace('.', '').zfill(10)             
 
80
 
 
81
class transaction(models.mem_bank_transaction):
 
82
    '''
 
83
    Implementation of transaction communication class for account_banking.
 
84
    '''
 
85
    attrnames = ['local_account', 'remote_account',
 
86
                 'remote_owner', 'transferred_amount',
 
87
                 'execution_date', 'effective_date', 'transfer_type',
 
88
                 'reference', 'id',
 
89
                ]
 
90
 
 
91
    """
 
92
    Presumably the same transaction types occur in the MT940 format of ING.
 
93
    From www.ing.nl/Images/MT940_Technische_handleiding_tcm7-69020.pdf
 
94
    
 
95
    """
 
96
    type_map = {
 
97
        
 
98
        'AC': bt.ORDER, # Acceptgiro
 
99
        'BA': bt.PAYMENT_TERMNINAL, # Betaalautomaattransactie
 
100
        'CH': bt.ORDER, # Cheque
 
101
        'DV': bt.ORDER, # Diversen
 
102
        'FL': bt.BANK_TERMINAL, # Filiaalboeking, concernboeking
 
103
        'GF': bt.ORDER, # Telefonisch bankieren
 
104
        'GM': bt.BANK_TERMINAL, # Geldautomaat
 
105
        'GT': bt.ORDER, # Internetbankieren
 
106
        'IC': bt.DIRECT_DEBIT, # Incasso
 
107
        'OV': bt.ORDER, # Overschrijving
 
108
        'PK': bt.BANK_TERMINAL, # Opname kantoor
 
109
        'PO': bt.ORDER, # Periodieke overschrijving
 
110
        'ST': bt.BANK_TERMINAL, # Storting (eigen rekening of derde)
 
111
        'VZ': bt.ORDER, # Verzamelbetaling
 
112
        }
 
113
 
 
114
    def __init__(self, line, *args, **kwargs):
 
115
        '''
 
116
        Initialize own dict with read values.
 
117
        '''
 
118
        super(transaction, self).__init__(*args, **kwargs)
 
119
        # Copy attributes from auxiliary class to self.
 
120
        for attr in self.attrnames:
 
121
            setattr(self, attr, getattr(line, attr))
 
122
        self.message = ''
 
123
        # Decompose structured messages
 
124
        self.parse_message()
 
125
        if (self.transfer_type == 'OV' and
 
126
            not self.remote_account and
 
127
            not self.remote_owner):
 
128
            self.transfer_type = 'KN'
 
129
 
 
130
    def is_valid(self):
 
131
        if not self.error_message:
 
132
            if not self.transferred_amount:
 
133
                self.error_message = "No transferred amount"
 
134
            elif not self.execution_date:
 
135
                self.error_message = "No execution date"
 
136
            elif not self.remote_account and self.transfer_type not in [
 
137
                'BA', 'FL', 'GM', 'IC', 'PK', 'ST'
 
138
                ]:
 
139
                self.error_message = (
 
140
                    "No remote account for transaction type %s" %
 
141
                    self.transfer_type)
 
142
        return not self.error_message
 
143
 
 
144
    def parse_message(self):
 
145
        '''
 
146
        Parse structured message parts into appropriate attributes.
 
147
        No processing done here for Triodos, maybe later.
 
148
        '''
 
149
        if self.transfer_type == 'DV':
 
150
            res = self.ref_expr.search(self.remote_owner)
 
151
            if res:
 
152
                self.reference = res.group(1)
 
153
                self.remote_owner = False
 
154
            else:
 
155
                res = self.ref_expr.search(self.message)
 
156
                if res:
 
157
                    self.reference = res.group(1)
 
158
 
 
159
class statement(models.mem_bank_statement):
 
160
    '''
 
161
    Implementation of bank_statement communication class of account_banking
 
162
    '''
 
163
    def __init__(self, msg, *args, **kwargs):
 
164
        '''
 
165
        Set decent start values based on first transaction read
 
166
        '''
 
167
        super(statement, self).__init__(*args, **kwargs)
 
168
        self.id = msg.statement_id
 
169
        self.local_account = msg.local_account
 
170
        self.date = str2date(msg.date, '%d-%m-%Y')
 
171
        self.start_balance = self.end_balance = 0 # msg.start_balance
 
172
        self.import_transaction(msg)
 
173
 
 
174
    def import_transaction(self, msg):
 
175
        '''
 
176
        Import a transaction and keep some house holding in the mean time.
 
177
        '''
 
178
        trans = transaction(msg)
 
179
        self.end_balance += trans.transferred_amount
 
180
        self.transactions.append(trans)
 
181
 
 
182
class parser(models.parser):
 
183
    code = 'ING'
 
184
    country_code = 'NL'
 
185
    name = _('ING Bank')
 
186
    doc = _('''\
 
187
The Dutch ING format is basicly a MS Excel CSV format. It is specifically
 
188
distinct from the Dutch multibank format. Transactions are not tied to Bank
 
189
Statements.
 
190
''')
 
191
 
 
192
    def parse(self, data):
 
193
        result = []
 
194
        stmnt = None
 
195
        dialect = csv.excel()
 
196
        dialect.quotechar = '"'
 
197
        dialect.delimiter = ';'
 
198
        lines = data.split('\n')
 
199
        # Transaction lines are not numbered, so keep a tracer
 
200
        subno = 0
 
201
        for line in csv.reader(lines, dialect=dialect):
 
202
            # Skip empty (last) lines
 
203
            if not line:
 
204
                continue
 
205
            subno += 1
 
206
            msg = transaction_message(line, subno)
 
207
            if stmnt and stmnt.id != msg.statement_id:
 
208
                result.append(stmnt)
 
209
                stmnt = None
 
210
                subno = 0
 
211
            if not stmnt:
 
212
                stmnt = statement(msg)
 
213
            else:
 
214
                stmnt.import_transaction(msg)
 
215
        result.append(stmnt)
 
216
        return result
 
217
 
 
218
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: