1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>),
5
# 2011 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 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.
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.
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/>.
21
##############################################################################
24
This parser follows the Dutch Banking Tools specifications which are
25
empirically recreated in this module.
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.
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 _
41
bt = models.mem_bank_transaction
43
class transaction_message(object):
45
A auxiliary class to validate and coerce read values
48
'date', 'local_account', 'transferred_amount', 'debcred',
49
'remote_owner', 'remote_account', 'transfer_type', 'reference',
52
def __init__(self, values, subno):
54
Initialize own dict with attributes and coerce values to right type
56
if len(self.attrnames) != len(values):
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)
72
class transaction(models.mem_bank_transaction):
74
Implementation of transaction communication class for account_banking.
76
attrnames = ['local_account', 'remote_account',
77
'remote_owner', 'transferred_amount',
78
'execution_date', 'effective_date', 'transfer_type',
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
90
'DB': bt.ORDER, # Diskettebetaling
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
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
118
def __init__(self, line, *args, **kwargs):
120
Initialize own dict with read values.
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))
127
# Decompose structured messages
129
if (self.transfer_type == 'OV' and
130
not self.remote_account and
131
not self.remote_owner):
132
self.transfer_type = 'KN'
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'
143
self.error_message = (
144
"No remote account for transaction type %s" %
146
return not self.error_message
148
def parse_message(self):
150
Parse structured message parts into appropriate attributes.
151
No processing done here for Triodos, maybe later.
154
class statement(models.mem_bank_statement):
156
Implementation of bank_statement communication class of account_banking
158
def __init__(self, msg, *args, **kwargs):
160
Set decent start values based on first transaction read
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)
169
def import_transaction(self, msg):
171
Import a transaction and keep some house holding in the mean time.
173
trans = transaction(msg)
174
self.end_balance += trans.transferred_amount
175
self.transactions.append(trans)
177
class parser(models.parser):
180
name = _('Triodos Bank')
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
187
def parse(self, data):
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
196
for line in csv.reader(lines, dialect=dialect):
197
# Skip empty (last) lines
201
msg = transaction_message(line, subno)
202
if stmnt and stmnt.id != msg.statement_id:
207
stmnt = statement(msg)
209
stmnt.import_transaction(msg)
213
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: