1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# Copyright (C) 2011 Therp BV (<http://therp.nl>).
6
# Based on the multibank module by EduSense BV
7
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>),
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.
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.
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/>.
24
##############################################################################
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 _
36
bt = models.mem_bank_transaction
39
First line states the legend
40
"Datum","Naam / Omschrijving","Rekening","Tegenrekening","Code","Af Bij","Bedrag (EUR)","MutatieSoort","Mededelingen
44
class transaction_message(object):
46
A auxiliary class to validate and coerce read values
49
'date', 'remote_owner', 'local_account', 'remote_account',
50
'transfer_type', 'debcred', 'transferred_amount',
51
'transfer_type_verbose', 'message'
53
# 'date', 'local_account', 'transferred_amount', 'debcred',
54
# 'remote_owner', 'remote_account', 'transfer_type', 'reference',
57
ref_expr = re.compile('REF[\*:]([0-9A-Z-z_-]+)')
59
def __init__(self, values, subno):
61
Initialize own dict with attributes and coerce values to right type
63
if len(self.attrnames) != len(values):
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)
81
class transaction(models.mem_bank_transaction):
83
Implementation of transaction communication class for account_banking.
85
attrnames = ['local_account', 'remote_account',
86
'remote_owner', 'transferred_amount',
87
'execution_date', 'effective_date', 'transfer_type',
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
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
114
def __init__(self, line, *args, **kwargs):
116
Initialize own dict with read values.
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))
123
# Decompose structured messages
125
if (self.transfer_type == 'OV' and
126
not self.remote_account and
127
not self.remote_owner):
128
self.transfer_type = 'KN'
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'
139
self.error_message = (
140
"No remote account for transaction type %s" %
142
return not self.error_message
144
def parse_message(self):
146
Parse structured message parts into appropriate attributes.
147
No processing done here for Triodos, maybe later.
149
if self.transfer_type == 'DV':
150
res = self.ref_expr.search(self.remote_owner)
152
self.reference = res.group(1)
153
self.remote_owner = False
155
res = self.ref_expr.search(self.message)
157
self.reference = res.group(1)
159
class statement(models.mem_bank_statement):
161
Implementation of bank_statement communication class of account_banking
163
def __init__(self, msg, *args, **kwargs):
165
Set decent start values based on first transaction read
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)
174
def import_transaction(self, msg):
176
Import a transaction and keep some house holding in the mean time.
178
trans = transaction(msg)
179
self.end_balance += trans.transferred_amount
180
self.transactions.append(trans)
182
class parser(models.parser):
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
192
def parse(self, data):
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
201
for line in csv.reader(lines, dialect=dialect):
202
# Skip empty (last) lines
206
msg = transaction_message(line, subno)
207
if stmnt and stmnt.id != msg.statement_id:
212
stmnt = statement(msg)
214
stmnt.import_transaction(msg)
218
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: