Skip to content

Commit 5691881

Browse files
committed
Add Bank Austria
1 parent 2537d1f commit 5691881

2 files changed

Lines changed: 223 additions & 2 deletions

File tree

setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
license="GPLv3",
2121
keywords=[
2222
"ofxstatement", "easybank", "ing-diba",
23-
"livebank", "raiffeisen", "oberbank"],
23+
"livebank", "raiffeisen", "oberbank", "bankaustria"],
2424
classifiers=[
2525
"Development Status :: 3 - Alpha",
2626
"Programming Language :: Python :: 3",
@@ -44,7 +44,8 @@
4444
"ing-diba = ofxstatement.plugins.ingdiba:IngDiBaPlugin",
4545
"livebank = ofxstatement.plugins.livebank:LivebankPlugin",
4646
"raiffeisen = ofxstatement.plugins.raiffeisen:RaiffeisenPlugin",
47-
"oberbank = ofxstatement.plugins.oberbank:OberbankPlugin"
47+
"oberbank = ofxstatement.plugins.oberbank:OberbankPlugin",
48+
"bankaustria = ofxstatement.plugins.bankaustria:BankAustriaPlugin",
4849
]
4950
},
5051
install_requires=["ofxstatement"],
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#!/usr/bin/env python3
2+
# This file is part of ofxstatement-austrian.
3+
# See README.rst for more information.
4+
5+
import csv
6+
import re
7+
from datetime import datetime
8+
from ofxstatement.plugin import Plugin
9+
from ofxstatement.parser import CsvStatementParser
10+
from ofxstatement.statement import generate_transaction_id
11+
from ofxstatement import statement
12+
from ofxstatement.plugins.utils import \
13+
clean_multiple_whitespaces, fix_amount_string
14+
15+
16+
# TODO
17+
# 1 Check account data, see https://github.com/kedder/ofxstatement/blob/master/src/ofxstatement/ofx.py#L117
18+
# * account.bank_id
19+
# * account.acct_id
20+
# * account.acct_type
21+
# * ...
22+
# 2. Parse more data i.e. BANKOMAT?
23+
24+
class BankAustriaCsvParser(CsvStatementParser):
25+
"""The csv parser for Bank Austria."""
26+
27+
date_format = "%d.%m.%Y"
28+
29+
# 0 Buchungsdatum
30+
# 1 Valutadatum
31+
# 2 Buchungstext
32+
# 3 Interne Notiz
33+
# 4 Waehrung
34+
# 5 Betrag
35+
# 6 Belegdaten
36+
# 7 Belegnummer
37+
# 8 Auftraggebername
38+
# 9 Auftraggeberkonto
39+
# 10 Auftraggeber BLZ
40+
# 11 Empfaengername
41+
# 12 Empfaengerkonto
42+
# 13 Empfaenger BLZ
43+
# 14 Zahlungsgrund
44+
45+
mappings = {
46+
"date": 1,
47+
"date_user": 0,
48+
"memo": 14,
49+
"amount": 5,
50+
"check_no": 7,
51+
"payee": 11,
52+
}
53+
54+
def parse(self):
55+
"""Parse."""
56+
stmt = super(BankAustriaCsvParser, self).parse()
57+
statement.recalculate_balance(stmt)
58+
return stmt
59+
60+
def split_records(self):
61+
"""Split records using a custom dialect."""
62+
return csv.reader(self.fin, delimiter=';', quotechar='"')
63+
64+
def parse_record(self, line):
65+
"""Parse a single record."""
66+
# Skip header line
67+
if self.cur_record == 1:
68+
return None
69+
70+
# Fix German number format prior to parsing
71+
line[5] = format(fix_amount_string(line[5])) # German number format
72+
73+
# Create statement
74+
# parse line elements using the mappings defined above (call parse_record() from parent class)
75+
stmtline = super(BankAustriaCsvParser, self).parse_record(line)
76+
77+
stmtline.id = generate_transaction_id(stmtline)
78+
79+
# TODO remove me when https://github.com/kedder/ofxstatement/commit/38af84d525f5c47c7fab67c02b36c32dcfc805b3
80+
stmtline.date_user = datetime.strptime(line[1], self.date_format) # manual date_user conversion as date_user has wrong format
81+
82+
stmtline.trntype = 'DEBIT' if stmtline.amount < 0 else 'CREDIT'
83+
84+
# Account id
85+
#if not self.statement.account_id:
86+
# self.statement.account_id = line[9]
87+
88+
# Currency
89+
if not self.statement.currency:
90+
self.statement.currency = line[4]
91+
92+
# .payee is imported as "Description" in GnuCash
93+
# .memo is imported as "Notes" in GnuCash
94+
# When .payee is empty, GnuCash imports .memo to "Description" and keeps "Notes" empty
95+
# @see https://github.com/archont00/ofxstatement-unicreditcz/blob/master/src/ofxstatement/plugins/unicreditcz.py#L100
96+
97+
# Fixup Memo, Payee, and TRXTYPE
98+
if line[2].startswith('POS'):
99+
stmtline.trntype = 'POS'
100+
stmtline.memo = self.parsePosAtm(line[2])
101+
102+
elif line[2].startswith('ATM'):
103+
stmtline.trntype = 'ATM'
104+
stmtline.memo = self.parsePosAtm(line[2])
105+
106+
elif line[2].startswith('AUTOMAT') or line[2].startswith('BANKOMAT'):
107+
# > AUTOMAT 00011942 K1 14.01. 13:47 O
108+
# > BANKOMAT 00021241 K4 08.03. 09:43 O
109+
stmtline.trntype = 'ATM'
110+
# TODO stmtline.memo = self.parsePosAtm(line[2]) ?
111+
stmtline.memo = line[2]
112+
113+
elif line[2].startswith('ABHEBUNG AUTOMAT'):
114+
# > ABHEBUNG AUTOMAT NR. 14547 AM 31.01. UM 15.53 UHR Fil.ABC BANKCARD 2
115+
# TODO stmtline.memo = self.parsePosAtm(line[2]) ?
116+
stmtline.trntype = 'ATM'
117+
stmtline.memo = line[2]
118+
119+
elif line[2].startswith('EINZAHLUNG'):
120+
# > EINZAHLUNG AUTOMAT NR. 55145 AM 31.01. / 15.55 UHR Fil.ABC BANKCARD 2 EIGENERLAG
121+
stmtline.memo = line[2]
122+
123+
124+
elif line[2].startswith('Lastschrift JustinCase'):
125+
# > Lastschrift JustinCase MRefAT123123123123123123JIC Entgelt für Bank Austria 0,69 enth‰lt 20% Ust., das sind EUR 0,12.
126+
stmtline.memo = line[2]
127+
128+
elif line[6].startswith('SEPA-AUFTRAGSBESTÄTIGUNG'):
129+
if not stmtline.memo:
130+
stmtline.memo = self.parseDocument(line[6])
131+
132+
elif line[6].startswith('GUTSCHRIFT') or line[6].startswith('SEPA') or line[6].startswith('ÜBERWEISUNG'):
133+
# Auftraggebername holds the information we want
134+
stmtline.payee = line[8]
135+
if not stmtline.memo:
136+
stmtline.memo = self.parseDocument(line[6])
137+
138+
else:
139+
stmtline.memo = line[2]
140+
141+
# Simple cleanup
142+
stmtline.payee = clean_multiple_whitespaces(stmtline.payee)
143+
stmtline.memo = clean_multiple_whitespaces(stmtline.memo)
144+
145+
# Add Internal Note, if exists
146+
if line[3]:
147+
# Add trailing whitespace if memo exists
148+
if stmtline.memo:
149+
stmtline.memo = stmtline.memo + ' '
150+
stmtline.memo = stmtline.memo + '(NOTE: )' + line[3]
151+
152+
return stmtline
153+
154+
def parseDocument(self, toparse):
155+
"""Parse Belegdaten"""
156+
# 123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x
157+
# SEPA-AUFTRAGSBESTÄTIGUNG
158+
# GUTSCHRIFT
159+
# ÜBERWEISUNG
160+
# SEPA LASTSCHRIFT
161+
p = re.compile('.*Belegnr.: ([0-9.]{18}).*(?:Zahlungsempf|Zahlungspfl).: (.{56}).*Zahlungsgrund: (.{105}).*Zahlungsref.: (.{110})')
162+
mm = p.findall(toparse)
163+
if mm:
164+
m = mm[0]
165+
no = m[0]
166+
myname = m[1].strip()
167+
reason = m[2].strip()
168+
ref = m[3].strip()
169+
170+
if reason:
171+
text = reason
172+
else:
173+
text = ref
174+
175+
memo = '%s' % (text)
176+
else:
177+
memo = 'ERR: ' + toparse
178+
179+
#print(m)
180+
return memo
181+
182+
def parsePosAtm(self, toparse):
183+
"""Parse POS/ATM Lines"""
184+
185+
# POS/ATM have a fixed layout in line[2]. Some data can also be found in other columns
186+
# i.e.
187+
# > 123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x
188+
# > ATM 100,00 AT K1 15.01. 19:08 O ATM S6EE0275 KLOSTERNEUBUR 4300
189+
# > POS 11,00 NL K1 16.01. 14:46 O NS SCHIPHOL 216 LUCHTHAVEN SC 1118 AX
190+
#
191+
# Matches:
192+
# > 0 1 2 3 4 5 - 6 7 8
193+
# > TYPE AMT CC ## DATE TIME O SHOP LOCATION ZIP
194+
195+
p = re.compile('(POS|ATM) +([0-9]+,[0-9]+) ([A-Z]+) +(K[0-9]) +(......) (..:..) O (.{22}) +(.{13}) +(.*)')
196+
mm = p.findall(toparse)
197+
if mm:
198+
# ex. result from above
199+
# ATM: ATM S6EE0275, 4300 KLOSTERNEUBUR, AT; 100,00 EUR on 15.01. 19:08h
200+
# POS: NS SCHIPHOL 216, 1118 AX LUCHTHAVEN SC, NL; 11,00 EUR on 16.01. 14:46h
201+
202+
m = mm[0]
203+
memo = '%s: %s, %s %s, %s; %s %s on %s %sh' % (m[0], m[6].strip(), m[8], m[7].strip(), m[2], m[1], self.statement.currency, m[4], m[5])
204+
else:
205+
memo = 'ERR: ' + toparse
206+
207+
return memo
208+
209+
class BankAustriaPlugin(Plugin):
210+
"""Bank Austria (CSV)"""
211+
212+
def get_parser(self, filename):
213+
"""Get a parser instance."""
214+
encoding = self.settings.get('charset', 'iso-8859-1')
215+
f = open(filename, 'r', encoding=encoding)
216+
parser = BankAustriaCsvParser(f)
217+
parser.statement.bank_id = self.settings.get('bank', 'Bank-Austria')
218+
return parser
219+
220+
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 smartindent autoindent

0 commit comments

Comments
 (0)