~clearcorp-drivers/banking-addons/5.0-ccorp

« back to all changes in this revision

Viewing changes to account_banking_nl_triodos/triodos.py

  • Committer: Pieter J. Kersten
  • Date: 2011-04-27 15:05:32 UTC
  • mfrom: (57.2.2 nl_abnamro-triodos)
  • Revision ID: p.j.kersten@edusense.nl-20110427150532-2hmx4jak6r5t8z55
[MERGE] account_banking_nl_triodos & account_banking_nl_abnamro
        by Stefan Rijnhart, Therp

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>),
 
5
#                  2011 Therp BV (<http://therp.nl>).
 
6
#    All Rights Reserved
 
7
#
 
8
#    This program is free software: you can redistribute it and/or modify
 
9
#    it under the terms of the GNU General Public License as published by
 
10
#    the Free Software Foundation, either version 3 of the License, or
 
11
#    (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 General Public License for more details.
 
17
#
 
18
#    You should have received a copy of the GNU General Public License
 
19
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
20
#
 
21
##############################################################################
 
22
 
 
23
'''
 
24
This parser follows the Dutch Banking Tools specifications which are
 
25
empirically recreated in this module.
 
26
 
 
27
Dutch Banking Tools uses the concept of 'Afschrift' or Bank Statement.
 
28
Every transaction is bound to a Bank Statement. As such, this module generates
 
29
Bank Statements along with Bank Transactions.
 
30
'''
 
31
from account_banking.parsers import models
 
32
from account_banking.parsers.convert import str2date
 
33
from account_banking.sepa import postalcode
 
34
from tools.translate import _
 
35
 
 
36
import re
 
37
import csv
 
38
 
 
39
__all__ = ['parser']
 
40
 
 
41
bt = models.mem_bank_transaction
 
42
 
 
43
class transaction_message(object):
 
44
    '''
 
45
    A auxiliary class to validate and coerce read values
 
46
    '''
 
47
    attrnames = [
 
48
        'date', 'local_account', 'transferred_amount', 'debcred',
 
49
        'remote_owner', 'remote_account', 'transfer_type', 'reference',
 
50
    ]
 
51
 
 
52
    def __init__(self, values, subno):
 
53
        '''
 
54
        Initialize own dict with attributes and coerce values to right type
 
55
        '''
 
56
        if len(self.attrnames) != len(values):
 
57
            raise ValueError(
 
58
                _('Invalid transaction line: expected %d columns, found %d')
 
59
                % (len(self.attrnames), len(values)))
 
60
        self.__dict__.update(dict(zip(self.attrnames, values)))
 
61
        # for lack of a standardized locale function to parse amounts
 
62
        self.transferred_amount = float(
 
63
            re.sub(',', '.', re.sub('\.', '', self.transferred_amount)))
 
64
        if self.debcred == 'Debet':
 
65
            self.transferred_amount = -self.transferred_amount
 
66
        self.execution_date = str2date(self.date, '%d-%m-%Y')
 
67
        self.effective_date = str2date(self.date, '%d-%m-%Y')
 
68
        # Set statement_id based on week number
 
69
        self.statement_id = self.effective_date.strftime('%Yw%W')
 
70
        self.id = str(subno).zfill(4)
 
71
 
 
72
class transaction(models.mem_bank_transaction):
 
73
    '''
 
74
    Implementation of transaction communication class for account_banking.
 
75
    '''
 
76
    attrnames = ['local_account', 'remote_account',
 
77
                 'remote_owner', 'transferred_amount',
 
78
                 'execution_date', 'effective_date', 'transfer_type',
 
79
                 'reference', 'id',
 
80
                ]
 
81
 
 
82
    type_map = {
 
83
        # retrieved from online help in the Triodos banking application
 
84
        'AC': bt.ORDER, # Acceptgiro gecodeerd
 
85
        'AN': bt.ORDER, # Acceptgiro ongecodeerd
 
86
        'AT': bt.ORDER, # Acceptgiro via internet
 
87
        'BA': bt.PAYMENT_TERMINAL, # Betaalautomaat
 
88
        'CHIP': bt.BANK_TERMINAL, # Chipknip
 
89
        # 'CO': # Correctie
 
90
        'DB': bt.ORDER, # Diskettebetaling
 
91
        # 'DV': # Dividend
 
92
        'EI': bt.DIRECT_DEBIT, # Europese Incasso
 
93
        'EICO': bt.DIRECT_DEBIT, # Europese Incasso Correctie
 
94
        'EIST': bt.ORDER, # Europese Incasso Storno
 
95
        'ET': bt.ORDER, # Europese Transactie
 
96
        'ETST': bt.ORDER, #Europese Transactie Storno
 
97
        'GA': bt.BANK_TERMINAL, # Geldautomaat
 
98
        'IB': bt.ORDER, # Interne Boeking
 
99
        'IC': bt.DIRECT_DEBIT, # Incasso
 
100
        'ID': bt.ORDER, # iDeal-betaling
 
101
        'IT': bt.ORDER, # Internet transactie
 
102
        'KN': bt.BANK_COSTS, # Kosten
 
103
        'KO': bt.BANK_TERMINAL, # Kasopname
 
104
        # 'KS': # Kwaliteitsstoring
 
105
        'OV': bt.ORDER, # Overboeking. NB: can also be bt.BANK_COSTS
 
106
                        # when no remote_account specified!
 
107
        'PO': bt.ORDER, # Periodieke Overboeking
 
108
        'PR': bt.BANK_COSTS, # Provisie
 
109
        # 'RE': # Rente
 
110
        # 'RS': # Renteschenking
 
111
        'ST': bt.ORDER, # Storno
 
112
        'TG': bt.ORDER, # Telegiro
 
113
        # 'VL': # Vaste Lening
 
114
        'VO': bt.DIRECT_DEBIT, # Vordering overheid
 
115
        'VV': bt.ORDER, # Vreemde valuta
 
116
    }
 
117
 
 
118
    def __init__(self, line, *args, **kwargs):
 
119
        '''
 
120
        Initialize own dict with read values.
 
121
        '''
 
122
        super(transaction, self).__init__(*args, **kwargs)
 
123
        # Copy attributes from auxiliary class to self.
 
124
        for attr in self.attrnames:
 
125
            setattr(self, attr, getattr(line, attr))
 
126
        self.message = ''
 
127
        # Decompose structured messages
 
128
        self.parse_message()
 
129
        if (self.transfer_type == 'OV' and
 
130
            not self.remote_account and
 
131
            not self.remote_owner):
 
132
            self.transfer_type = 'KN'
 
133
 
 
134
    def is_valid(self):
 
135
        if not self.error_message:
 
136
            if not self.transferred_amount:
 
137
                self.error_message = "No transferred amount"
 
138
            elif not self.execution_date:
 
139
                self.error_message = "No execution date"
 
140
            elif not self.remote_account and self.transfer_type not in [
 
141
                'KN', 'TG', 'GA', 'BA', 'CHIP'
 
142
                ]:
 
143
                self.error_message = (
 
144
                    "No remote account for transaction type %s" %
 
145
                    self.transfer_type)
 
146
        return not self.error_message
 
147
 
 
148
    def parse_message(self):
 
149
        '''
 
150
        Parse structured message parts into appropriate attributes.
 
151
        No processing done here for Triodos, maybe later.
 
152
        '''
 
153
 
 
154
class statement(models.mem_bank_statement):
 
155
    '''
 
156
    Implementation of bank_statement communication class of account_banking
 
157
    '''
 
158
    def __init__(self, msg, *args, **kwargs):
 
159
        '''
 
160
        Set decent start values based on first transaction read
 
161
        '''
 
162
        super(statement, self).__init__(*args, **kwargs)
 
163
        self.id = msg.statement_id
 
164
        self.local_account = msg.local_account
 
165
        self.date = str2date(msg.date, '%d-%m-%Y')
 
166
        self.start_balance = self.end_balance = 0 # msg.start_balance
 
167
        self.import_transaction(msg)
 
168
 
 
169
    def import_transaction(self, msg):
 
170
        '''
 
171
        Import a transaction and keep some house holding in the mean time.
 
172
        '''
 
173
        trans = transaction(msg)
 
174
        self.end_balance += trans.transferred_amount
 
175
        self.transactions.append(trans)
 
176
 
 
177
class parser(models.parser):
 
178
    code = 'TRIOD'
 
179
    country_code = 'NL'
 
180
    name = _('Triodos Bank')
 
181
    doc = _('''\
 
182
The Dutch Triodos format is basicly a MS Excel CSV format. It is specifically
 
183
distinct from the Dutch multibank format. Transactions are not tied to Bank
 
184
Statements.
 
185
''')
 
186
 
 
187
    def parse(self, data):
 
188
        result = []
 
189
        stmnt = None
 
190
        dialect = csv.excel()
 
191
        dialect.quotechar = '"'
 
192
        dialect.delimiter = ','
 
193
        lines = data.split('\n')
 
194
        # Transaction lines are not numbered, so keep a tracer
 
195
        subno = 0
 
196
        for line in csv.reader(lines, dialect=dialect):
 
197
            # Skip empty (last) lines
 
198
            if not line:
 
199
                continue
 
200
            subno += 1
 
201
            msg = transaction_message(line, subno)
 
202
            if stmnt and stmnt.id != msg.statement_id:
 
203
                result.append(stmnt)
 
204
                stmnt = None
 
205
                subno = 0
 
206
            if not stmnt:
 
207
                stmnt = statement(msg)
 
208
            else:
 
209
                stmnt.import_transaction(msg)
 
210
        result.append(stmnt)
 
211
        return result
 
212
 
 
213
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: