Browse Source

Merge pull request #125 from onesteinbv/10_mig_account_bank_statement_import_mt940_base

[MIG][10.0] account_bank_statement_import_mt940_base: migration to 10.0
pull/155/head
Pedro M. Baeza 7 years ago
committed by GitHub
parent
commit
132a13e8de
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      account_bank_statement_import_mt940_base/README.rst
  2. 22
      account_bank_statement_import_mt940_base/__manifest__.py
  3. 172
      account_bank_statement_import_mt940_base/mt940.py
  4. 61
      account_bank_statement_import_mt940_base/test_files/test-ing.940
  5. 29
      account_bank_statement_import_mt940_base/test_files/test-rabo.swi
  6. 78
      account_bank_statement_import_mt940_base/test_files/test-sns.940
  7. 11
      account_bank_statement_import_mt940_base/test_files/test-wrong-file.940
  8. 1
      account_bank_statement_import_mt940_base/tests/__init__.py
  9. 240
      account_bank_statement_import_mt940_base/tests/test_import_bank_statement.py

22
account_bank_statement_import_mt940_base/README.rst

@ -1,6 +1,8 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3 :alt: License: AGPL-3
====================
Bank Statement MT940 Bank Statement MT940
==================== ====================
@ -10,7 +12,7 @@ interpretation of it, this addon alone won't help you much. It is rather
intended to be used by other addons to implement the dialect specific to a intended to be used by other addons to implement the dialect specific to a
certain bank. certain bank.
See bank_statement_parse_nl_ing_mt940 for an example on how to use it.
See account_bank_statement_import_mt940_nl_ing for an example on how to use it.
Known issues / Roadmap Known issues / Roadmap
====================== ======================
@ -20,11 +22,10 @@ Known issues / Roadmap
Bug Tracker Bug Tracker
=========== ===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/bank-statement-import/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
`here <https://github.com/OCA/bank-statement-import/issues/new?body=module:%20account_bank_statement_import%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Bugs are tracked on `GitHub Issues
<https://github.com/OCA/bank-statement-import/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.
Credits Credits
======= =======
@ -34,10 +35,13 @@ Contributors
* Stefan Rijnhart <srijnhart@therp.nl> * Stefan Rijnhart <srijnhart@therp.nl>
* Ronald Portier <rportier@therp.nl> * Ronald Portier <rportier@therp.nl>
* Andrea Stirpe <a.stirpe@onestein.nl>
* Fekete Mihai <mihai.fekete@forbiom.eu>
Maintainer Maintainer
---------- ----------
.. image:: https://odoo-community.org/logo.png .. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association :alt: Odoo Community Association
:target: https://odoo-community.org :target: https://odoo-community.org
@ -48,6 +52,4 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and mission is to support the collaborative development of Odoo features and
promote its widespread use. promote its widespread use.
To contribute to this module, please visit http://odoo-community.org.
This module should make it easy to migrate bank statement import
modules written for earlies versions of Odoo/OpenERP.
To contribute to this module, please visit https://odoo-community.org.

22
account_bank_statement_import_mt940_base/__manifest__.py

@ -1,25 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2013-2015 Therp BV <http://therp.nl> # Copyright (C) 2013-2015 Therp BV <http://therp.nl>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{ {
'name': 'MT940 Bank Statements Import', 'name': 'MT940 Bank Statements Import',
'version': '8.0.1.1.0',
'version': '10.0.1.0.0',
'license': 'AGPL-3', 'license': 'AGPL-3',
'author': 'Odoo Community Association (OCA), Therp BV', 'author': 'Odoo Community Association (OCA), Therp BV',
'website': 'https://github.com/OCA/bank-statement-import', 'website': 'https://github.com/OCA/bank-statement-import',
@ -27,5 +11,5 @@
'depends': [ 'depends': [
'account_bank_statement_import', 'account_bank_statement_import',
], ],
'installable': False
'installable': True
} }

172
account_bank_statement_import_mt940_base/mt940.py

@ -1,30 +1,11 @@
# -*- coding: utf-8 -*-
"""Generic parser for MT940 files, base for customized versions per bank."""
##############################################################################
#
# Copyright (C) 2014-2015 Therp BV <http://therp.nl>. # Copyright (C) 2014-2015 Therp BV <http://therp.nl>.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
"""Generic parser for MT940 files, base for customized versions per bank."""
import re import re
import logging import logging
from datetime import datetime from datetime import datetime
from openerp.addons.account_bank_statement_import.parserlib import (
BankStatement)
def str2amount(sign, amount_str): def str2amount(sign, amount_str):
"""Convert sign (C or D) and amount in string to signed amount (float).""" """Convert sign (C or D) and amount in string to signed amount (float)."""
@ -68,13 +49,11 @@ def get_counterpart(transaction, subfield):
if not subfield: if not subfield:
return # subfield is empty return # subfield is empty
if len(subfield) >= 1 and subfield[0]: if len(subfield) >= 1 and subfield[0]:
transaction.remote_account = subfield[0]
transaction.update({'account_number': subfield[0]})
if len(subfield) >= 2 and subfield[1]: if len(subfield) >= 2 and subfield[1]:
transaction.remote_bank_bic = subfield[1]
transaction.update({'account_bic': subfield[1]})
if len(subfield) >= 3 and subfield[2]: if len(subfield) >= 3 and subfield[2]:
transaction.remote_owner = subfield[2]
if len(subfield) >= 4 and subfield[3]:
transaction.remote_owner_city = subfield[3]
transaction.update({'partner_name': subfield[2]})
def handle_common_subfields(transaction, subfields): def handle_common_subfields(transaction, subfields):
@ -83,14 +62,28 @@ def handle_common_subfields(transaction, subfields):
for counterpart_field in ['CNTP', 'BENM', 'ORDP']: for counterpart_field in ['CNTP', 'BENM', 'ORDP']:
if counterpart_field in subfields: if counterpart_field in subfields:
get_counterpart(transaction, subfields[counterpart_field]) get_counterpart(transaction, subfields[counterpart_field])
if not transaction.get('name'):
transaction['name'] = ''
# REMI: Remitter information (text entered by other party on trans.): # REMI: Remitter information (text entered by other party on trans.):
if 'REMI' in subfields: if 'REMI' in subfields:
transaction.message = (
'/'.join(x for x in subfields['REMI'] if x))
transaction['name'] += (
subfields['REMI'][2]
# this might look like
# /REMI/USTD//<remittance info>/
# or
# /REMI/STRD/CUR/<betalingskenmerk>/
if len(subfields['REMI']) >= 3 and subfields['REMI'][0] in [
'STRD', 'USTD'
]
else
'/'.join(x for x in subfields['REMI'] if x)
)
# EREF: End-to-end reference
if 'EREF' in subfields:
transaction['name'] += '/'.join(filter(bool, subfields['EREF']))
# Get transaction reference subfield (might vary): # Get transaction reference subfield (might vary):
if transaction.eref in subfields:
transaction.eref = ''.join(
subfields[transaction.eref])
if transaction.get('ref') in subfields:
transaction['ref'] = ''.join(subfields[transaction['ref']])
class MT940(object): class MT940(object):
@ -111,12 +104,14 @@ class MT940(object):
This in fact uses the ING syntax, override in others.""" This in fact uses the ING syntax, override in others."""
self.mt940_type = 'General' self.mt940_type = 'General'
self.header_lines = 3 # Number of lines to skip self.header_lines = 3 # Number of lines to skip
self.header_regex = '^0000 01INGBNL2AXXXX|^{1'
self.header_regex = '^0000 01INGBNL2AXXXX|^{1' # Start of header
self.footer_regex = '^-}$|^-XXX$' # Stop processing on seeing this self.footer_regex = '^-}$|^-XXX$' # Stop processing on seeing this
self.tag_regex = '^:[0-9]{2}[A-Z]*:' # Start of new tag self.tag_regex = '^:[0-9]{2}[A-Z]*:' # Start of new tag
self.current_statement = None self.current_statement = None
self.current_transaction = None self.current_transaction = None
self.statements = [] self.statements = []
self.currency_code = None
self.account_number = None
def is_mt940(self, line): def is_mt940(self, line):
"""determine if a line is the header of a statement""" """determine if a line is the header of a statement"""
@ -127,19 +122,49 @@ class MT940(object):
(line[:12], self.mt940_type) (line[:12], self.mt940_type)
) )
def parse(self, data):
def is_mt940_statement(self, line):
"""determine if line is the start of a statement"""
if not bool(line.startswith('{4:')):
raise ValueError(
'The pre processed match %s does not seem to be a'
' valid %s MT940 format bank statement. Every statement'
' should start be a dict starting with {4:..' % line
)
def pre_process_data(self, data):
matches = []
self.is_mt940(line=data)
data = data.replace(
'-}', '}').replace('}{', '}\r\n{').replace('\r\n', '\n')
if data.startswith(':940:'):
for statement in data.replace(':940:', '').split(':20:'):
match = '{4:\n:20:' + statement + '}'
matches.append(match)
else:
tag_re = re.compile(
r'(\{4:[^{}]+\})',
re.MULTILINE)
matches = tag_re.findall(data)
return matches
def parse(self, data, header_lines=None):
"""Parse mt940 bank statement file contents.""" """Parse mt940 bank statement file contents."""
self.is_mt940(data)
iterator = data.replace('\r\n', '\n').split('\n').__iter__()
data = data.decode()
matches = self.pre_process_data(data)
for match in matches:
self.is_mt940_statement(line=match)
iterator = '\n'.join(
match.split('\n')[1:-1]).split('\n').__iter__()
line = None line = None
record_line = '' record_line = ''
try: try:
while True: while True:
if not self.current_statement: if not self.current_statement:
self.handle_header(line, iterator)
self.handle_header(line, iterator,
header_lines=header_lines)
line = iterator.next() line = iterator.next()
if not self.is_tag(line) and not self.is_footer(line): if not self.is_tag(line) and not self.is_footer(line):
record_line += line
record_line = self.add_record_line(line, record_line)
continue continue
if record_line: if record_line:
self.handle_record(record_line) self.handle_record(record_line)
@ -156,7 +181,11 @@ class MT940(object):
record_line = '' record_line = ''
self.statements.append(self.current_statement) self.statements.append(self.current_statement)
self.current_statement = None self.current_statement = None
return self.statements
return self.currency_code, self.account_number, self.statements
def add_record_line(self, line, record_line):
record_line += line
return record_line
def is_footer(self, line): def is_footer(self, line):
"""determine if a line is the footer of a statement""" """determine if a line is the footer of a statement"""
@ -166,11 +195,19 @@ class MT940(object):
"""determine if a line has a tag""" """determine if a line has a tag"""
return line and bool(re.match(self.tag_regex, line)) return line and bool(re.match(self.tag_regex, line))
def handle_header(self, dummy_line, iterator):
def handle_header(self, dummy_line, iterator, header_lines=None):
"""skip header lines, create current statement""" """skip header lines, create current statement"""
for dummy_i in range(self.header_lines):
if not header_lines:
header_lines = self.header_lines
for dummy_i in range(header_lines):
iterator.next() iterator.next()
self.current_statement = BankStatement()
self.current_statement = {
'name': None,
'date': None,
'balance_start': 0.0,
'balance_end_real': 0.0,
'transactions': []
}
def handle_footer(self, dummy_line, dummy_iterator): def handle_footer(self, dummy_line, dummy_iterator):
"""add current statement to list, reset state""" """add current statement to list, reset state"""
@ -181,7 +218,7 @@ class MT940(object):
"""find a function to handle the record represented by line""" """find a function to handle the record represented by line"""
tag_match = re.match(self.tag_regex, line) tag_match = re.match(self.tag_regex, line)
tag = tag_match.group(0).strip(':') tag = tag_match.group(0).strip(':')
if not hasattr(self, 'handle_tag_%s' % tag):
if not hasattr(self, 'handle_tag_%s' % tag): # pragma: no cover
logging.error('Unknown tag %s', tag) logging.error('Unknown tag %s', tag)
logging.error(line) logging.error(line)
return return
@ -195,7 +232,7 @@ class MT940(object):
def handle_tag_25(self, data): def handle_tag_25(self, data):
"""Handle tag 25: local bank account information.""" """Handle tag 25: local bank account information."""
data = data.replace('EUR', '').replace('.', '').strip() data = data.replace('EUR', '').replace('.', '').strip()
self.current_statement.local_account = data
self.account_number = data
def handle_tag_28C(self, data): def handle_tag_28C(self, data):
"""Sequence number within batch - normally only zeroes.""" """Sequence number within batch - normally only zeroes."""
@ -206,18 +243,24 @@ class MT940(object):
# For the moment only first 60F record # For the moment only first 60F record
# The alternative would be to split the file and start a new # The alternative would be to split the file and start a new
# statement for each 20: tag encountered. # statement for each 20: tag encountered.
stmt = self.current_statement
if not stmt.local_currency:
stmt.local_currency = data[7:10]
stmt.start_balance = str2amount(data[0], data[10:])
if not self.currency_code:
self.currency_code = data[7:10]
self.current_statement['balance_start'] = str2amount(
data[0],
data[10:]
)
if not self.current_statement['date']:
self.current_statement['date'] = datetime.strptime(data[1:7],
'%y%m%d')
def handle_tag_61(self, data): def handle_tag_61(self, data):
"""get transaction values""" """get transaction values"""
transaction = self.current_statement.create_transaction()
self.current_transaction = transaction
transaction.execution_date = datetime.strptime(data[:6], '%y%m%d')
transaction.value_date = datetime.strptime(data[:6], '%y%m%d')
# ...and the rest already is highly bank dependent
self.current_statement['transactions'].append({})
self.current_transaction = self.current_statement['transactions'][-1]
self.current_transaction['date'] = datetime.strptime(
data[:6],
'%y%m%d'
)
def handle_tag_62F(self, data): def handle_tag_62F(self, data):
"""Get ending balance, statement date and id. """Get ending balance, statement date and id.
@ -233,19 +276,24 @@ class MT940(object):
Depending on the bank, there might be multiple 62F tags in the import Depending on the bank, there might be multiple 62F tags in the import
file. The last one counts. file. The last one counts.
""" """
stmt = self.current_statement
stmt.end_balance = str2amount(data[0], data[10:])
stmt.date = datetime.strptime(data[1:7], '%y%m%d')
self.current_statement['balance_end_real'] = str2amount(
data[0],
data[10:]
)
self.current_statement['date'] = datetime.strptime(data[1:7], '%y%m%d')
# Only replace logically empty (only whitespace or zeroes) id's: # Only replace logically empty (only whitespace or zeroes) id's:
# But do replace statement_id's added before (therefore starting # But do replace statement_id's added before (therefore starting
# with local_account), because we need the date on the last 62F # with local_account), because we need the date on the last 62F
# record. # record.
test_empty_id = re.sub(r'[\s0]', '', stmt.statement_id)
if ((not test_empty_id) or
(stmt.statement_id.startswith(stmt.local_account))):
stmt.statement_id = '%s-%s' % (
stmt.local_account,
stmt.date.strftime('%Y-%m-%d'),
statement_name = self.current_statement['name'] or ''
test_empty_id = re.sub(r'[\s0]', '', statement_name)
is_account_number = statement_name.startswith(self.account_number)
if not test_empty_id or is_account_number:
self.current_statement['name'] = '%s-%s' % (
self.account_number,
self.current_statement['date'].strftime('%Y-%m-%d'),
) )
def handle_tag_64(self, data): def handle_tag_64(self, data):

61
account_bank_statement_import_mt940_base/test_files/test-ing.940

@ -0,0 +1,61 @@
{1:F01INGBNL2ABXXX0000000000}
{2:I940INGBNL2AXXXN}
{4:
:20:P140220000000001
:25:NL77INGB0574908765EUR
:28C:0000
0
:60F:C140219EUR662,23
:61:1402200220C1,56NTRFEREF//00000000001
005
/TRCD/00100/
:86:/EREF/EV12341REP1231456T1234//CNTP/NL32INGB0000012345/INGBNL2
A/ING BANK NV INZAKE WEB///REMI/USTD//EV10001REP1000000T1000/
:61:1402200220D1,57NTRFPREF//00000000001006
/TRCD/00200/
:86:/PREF/M000000003333333//REMI/USTD//TOTAAL 1 VZ/
:61:1402200220C1,57NRTIEREF//00000000001007
/TRCD/00190/
:86:/RTRN/MS03//EREF/20120123456789//CNTP/NL32INGB0000012345/INGB
NL2A/J.Janssen///REMI/USTD//Factuurnr 123456 Klantnr 00123/
:61:1402200220D1,14NDDTEREF//00000000001009
/TRCD/010
16
/
:86:/EREF/EV123R
EP123412T1234//MARF/MND
-
EV01//CSID/NL32ZZZ9999999
91234//CNTP/NL32INGB0000012345/INGBNL2A/ING Bank N.V. inzake WeB/
//REMI/USTD//EV123REP123412T1234/
:61:1402200220C1,45NDDTPREF//00000000001008
/TRCD/01000/
:86:/PREF/M000000001111111/
/CSID/
NL32ZZZ999999991234
/
/REMI/USTD//
TOTAAL 1 POSTEN/
:61:1402200220D12,75NRTIEREF//00000000001010
/TRCD/01315/
:86:/RTRN/MS03//EREF/20120501P0123478//MARF/MND
-
120123//CSID/NL32
ZZZ999999991234//CNTP/NL32INGB0000012345/INGBNL2A/J.Janssen///REM
I/USTD//CO
NTRIBUTIE FEB 2014/
:61:1402200220C32,00NTRF9001123412341234//00000000001011
/TRCD/00108/
:86:/EREF/15814016000676480//CNTP/NL32INGB0000012345/INGBNL2A/J.J
anssen///REMI/STRD/CUR/9001123412341234/
:61:1402200220D119,00NTRF1070123412341234//00000000001012
/
TRCD/00108/
:86:/EREF/15614016000384600//CNTP/NL32INGB0000012345/INGBNL2A/ING
BANK NV///REMI/STRD/CUR/1070123412341234/
:62F:C140220EUR564,35
:64:C140220EUR564,35
:65:C140221EUR564,35
:65:C140224EUR564,35
:86:/SUM/4/4/134,46/36,58/
-}

29
account_bank_statement_import_mt940_base/test_files/test-rabo.swi

@ -0,0 +1,29 @@
:940:
:20:940S140102
:25:NL34RABO0142623393 EUR
:28C:0
:60F:C131231EUR000000004433,52
:61:140102C000000000400,00N541NONREF
NL66RABO0160878799
:86:/ORDP//NAME/R. SMITH/ADDR/Green market 74 3311BE Sheepcity Nederl
and NL/REMI/Test money paid by other partner:
/ISDT/2014-01-02
:62F:C140102EUR000000004833,52
:20:940S140103
:25:NL34RABO0142623393 EUR
:28C:0
:60F:C140102EUR000000004833,52
:62F:C140103EUR000000004833,52
:20:940S140106
:25:NL34RABO0142623393 EUR
:28C:0
:60F:C140103EUR000000004833,52
:61:140101D000000000034,61N093NONREF
:86:/BENM//NAME/Kosten/REMI/Periode 01-10-2013 t/m 31-12-2013/ISDT/20
14-01-01
:62F:C140106EUR000000004798,91
:20:940S140107
:25:NL34RABO0142623393 EUR
:28C:0
:60F:C140106EUR000000004798,91
:62F:C140107EUR000000004798,91

78
account_bank_statement_import_mt940_base/test_files/test-sns.940

@ -0,0 +1,78 @@
{1:F01SNSBNL2AXXXX0000000000}{2:O940SNSBNL2AXXXXN}{3:}{4:
:20:0000000000
:25:NL05SNSB0908244436
:28C:361/1
:60F:C171227EUR3026,96
:61:1712271227D713,13NOVBNL49RABO0166416932
gerrits glas en schilderwerk
:86:NL49RABO0166416932 gerrits glas en schilderwerk
Factuur 17227/248/20
:61:1712271227D10,18NBEA//228ohu/972795
:86:
Jumbo Wijchen B.V. >WIJCHEN 27.12.2017 13U38 KV005 4XZJ4Z M
CC:5411 Contactloze betaling
:61:1712271227D13,52NINCNL94INGB0000869000
vitens nv
:86:NL94INGB0000869000 vitens nv
Europese incasso door:VITENS NV NL-Factuurnr 072304597540 VNKlant
nr 00000000000 BTW 0,77PC 6605 DW 6223, DEC-Incassant ID: NL84ZZZ0
50695810000-Kenmerk Machtiging: bla
:61:1712271227D25,61NBEA//229hro/195867
:86:
Albert Heijn 1370 >WIJCHEN 27.12.2017 19U13 KV006 70X708 M
CC:5411
:62F:C171227EUR2264,52
-}{5:}
{1:F01SNSBNL2AXXXX0000000000}{2:O940SNSBNL2AXXXXN}{3:}{4:
:20:0000000000
:25:NL05SNSB0908244436
:28C:362/1
:60F:C171228EUR2264,52
:61:1712281228D10,95NINCNL40RABO0127859497
antagonist b.v.
:86:NL40RABO0127859497 antagonist b.v.
Europese incasso door:ANTAGONIST B.V. NL-DDWPN954156 AI529942Cweb
share.nl-Incassant ID: NL65ZZZ091364410000-Kenmerk Machtiging: bla
:61:1712281228C0,00NDIV
:86:
Mobiel betalen is vanaf nu niet meer mogelijk voor B
ERG M A VAN DEN met Lenovo P2
:62F:C171228EUR2253,57
-}{5:}
{1:F01SNSBNL2AXXXX0000000000}{2:O940SNSBNL2AXXXXN}{3:}{4:
:20:0000000000
:25:NL05SNSB0908244436
:28C:363/1
:60F:C171229EUR2253,57
:61:1712291229D907,29NINCNL19ABNA0427093546
florius
:86:NL19ABNA0427093546 florius
Europese incasso door:FLORIUS NL-Verschuldigde bedragen PERIODE 1
2-2017-Incassant ID: NL42ZZZ080242850000-Kenmerk Machtiging: bla
:61:1712291229D35,00NINCNL28DEUT0265186439
stichting derdengelden bucka
:86:NL28DEUT0265186439 stichting derdengelden bucka
Europese incasso door:STICHTING DERDENGELDEN BUCKAROO-T-Mobile Th
uis B.V.: Rekening dec 2017. Bekijk je rekening op thuismy.t-mobi
le.nl-Incassant ID: NL39ZZZ302317620000-Kenmerk Machtiging: bla
-}{5:}

11
account_bank_statement_import_mt940_base/test_files/test-wrong-file.940

@ -0,0 +1,11 @@
:9401:
:20:940S140102
:25:NL34RABO0142623393 EUR
:28C:0
:60F:C131231EUR000000004433,52
:61:140102C000000000400,00N541NONREF
NL66RABO0160878799
:86:/ORDP//NAME/R. SMITH/ADDR/Green market 74 3311BE Sheepcity Nederl
and NL/REMI/Test money paid by other partner:
/ISDT/2014-01-02
:62F:C140102EUR000000004833,52

1
account_bank_statement_import_mt940_base/tests/__init__.py

@ -0,0 +1 @@
from . import test_import_bank_statement

240
account_bank_statement_import_mt940_base/tests/test_import_bank_statement.py

@ -0,0 +1,240 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Onestein (<http://www.onestein.eu>)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
import base64
from mock import patch
from odoo.tests.common import TransactionCase
from odoo.modules.module import get_module_resource
from ..mt940 import MT940, get_subfields, handle_common_subfields
class TestImport(TransactionCase):
"""Run test to import mt940 import."""
transactions = [
{
'account_number': 'NL46ABNA0499998748',
'amount': -754.25,
'ref': '435005714488-ABNO33052620',
'name': 'test line',
},
]
def setUp(self):
super(TestImport, self).setUp()
bank1 = self.env['res.partner.bank'].create({
'acc_number': 'NL77INGB0574908765',
'partner_id': self.env.ref('base.main_partner').id,
'company_id': self.env.ref('base.main_company').id,
'bank_id': self.env.ref('base.res_bank_1').id,
})
self.env['account.journal'].create({
'name': 'Bank Journal - (test1 mt940)',
'code': 'TBNK1MT940',
'type': 'bank',
'bank_account_id': bank1.id,
'currency_id': self.env.ref('base.EUR').id,
})
bank2 = self.env['res.partner.bank'].create({
'acc_number': 'NL34RABO0142623393',
'partner_id': self.env.ref('base.main_partner').id,
'company_id': self.env.ref('base.main_company').id,
'bank_id': self.env.ref('base.res_bank_1').id,
})
self.env['account.journal'].create({
'name': 'Bank Journal - (test2 mt940)',
'code': 'TBNK2MT940',
'type': 'bank',
'bank_account_id': bank2.id,
'currency_id': self.env.ref('base.EUR').id,
})
bank3 = self.env['res.partner.bank'].create({
'acc_number': 'NL05SNSB0908244436',
'partner_id': self.env.ref('base.main_partner').id,
'company_id': self.env.ref('base.main_company').id,
'bank_id': self.env.ref('base.res_bank_1').id,
})
self.env['account.journal'].create({
'name': 'Bank Journal - (test3 mt940)',
'code': 'TBNK3MT940',
'type': 'bank',
'bank_account_id': bank3.id,
'currency_id': self.env.ref('base.EUR').id,
})
self.data =\
"/BENM//NAME/Cost/REMI/Period 01-10-2013 t/m 31-12-2013/ISDT/20"
self.codewords = ['BENM', 'ADDR', 'NAME', 'CNTP', 'ISDT', 'REMI']
def test_wrong_file_import(self):
"""Test wrong file import."""
testfile = get_module_resource(
'account_bank_statement_import_mt940_base',
'test_files',
'test-wrong-file.940',
)
parser = MT940()
datafile = open(testfile, 'rb').read()
with self.assertRaises(ValueError):
parser.parse(datafile, header_lines=1)
def test_statement_import(self):
"""Test correct creation of single statement ING."""
def _prepare_statement_lines(statements):
transact = self.transactions[0]
for st_vals in statements[2]:
for line_vals in st_vals['transactions']:
line_vals['amount'] = transact['amount']
line_vals['name'] = transact['name']
line_vals['account_number'] = transact['account_number']
line_vals['ref'] = transact['ref']
testfile = get_module_resource(
'account_bank_statement_import_mt940_base',
'test_files',
'test-ing.940',
)
parser = MT940()
datafile = open(testfile, 'rb').read()
statements = parser.parse(datafile, header_lines=1)
_prepare_statement_lines(statements)
path_addon = 'odoo.addons.account_bank_statement_import.'
path_file = 'account_bank_statement_import.'
path_class = 'AccountBankStatementImport.'
method = path_addon + path_file + path_class + '_parse_file'
with patch(method) as my_mock:
my_mock.return_value = statements
action = self.env['account.bank.statement.import'].create({
'data_file': base64.b64encode(datafile),
}).import_file()
transact = self.transactions[0]
for statement in self.env['account.bank.statement'].browse(
action['context']['statement_ids']):
for line in statement.line_ids:
self.assertTrue(
line.bank_account_id.acc_number ==
transact['account_number'])
self.assertTrue(line.amount == transact['amount'])
self.assertTrue(line.date == '2014-02-20')
self.assertTrue(line.name == transact['name'])
self.assertTrue(line.ref == transact['ref'])
def test_get_subfields(self):
"""Unit Test function get_subfields()."""
res = get_subfields(self.data, self.codewords)
espected_res = {
'BENM': [''],
'NAME': ['Cost'],
'REMI': ['Period 01-10-2013 t', 'm 31-12-2013'],
'ISDT': ['20'],
}
self.assertTrue(res == espected_res)
def test_handle_common_subfields(self):
"""Unit Test function handle_common_subfields()."""
subfields = get_subfields(self.data, self.codewords)
transaction = self.transactions[0]
handle_common_subfields(transaction, subfields)
def test_statement_import2(self):
"""Test correct creation of single statement RABO."""
def _prepare_statement_lines(statements):
transact = self.transactions[0]
for st_vals in statements[2]:
for line_vals in st_vals['transactions']:
line_vals['amount'] = transact['amount']
line_vals['name'] = transact['name']
line_vals['account_number'] = transact['account_number']
line_vals['ref'] = transact['ref']
testfile = get_module_resource(
'account_bank_statement_import_mt940_base',
'test_files',
'test-rabo.swi',
)
parser = MT940()
parser.header_regex = '^:940:' # Start of header
parser.header_lines = 1 # Number of lines to skip
datafile = open(testfile, 'rb').read()
statements = parser.parse(datafile, header_lines=1)
_prepare_statement_lines(statements)
path_addon = 'odoo.addons.account_bank_statement_import.'
path_file = 'account_bank_statement_import.'
path_class = 'AccountBankStatementImport.'
method = path_addon + path_file + path_class + '_parse_file'
with patch(method) as my_mock:
my_mock.return_value = statements
action = self.env['account.bank.statement.import'].create({
'data_file': base64.b64encode(datafile),
}).import_file()
# The file contains 4 statements, but only 2 with transactions
self.assertTrue(len(action['context']['statement_ids']) == 2)
transact = self.transactions[0]
for statement in self.env['account.bank.statement'].browse(
action['context']['statement_ids']):
for line in statement.line_ids:
self.assertTrue(
line.bank_account_id.acc_number ==
transact['account_number'])
self.assertTrue(line.amount == transact['amount'])
self.assertTrue(line.date)
self.assertTrue(line.name == transact['name'])
self.assertTrue(line.ref == transact['ref'])
def test_statement_import3(self):
"""Test correct creation of multiple statements SNS."""
def _prepare_statement_lines(statements):
transact = self.transactions[0]
for st_vals in statements[2]:
for line_vals in st_vals['transactions']:
line_vals['amount'] = transact['amount']
line_vals['name'] = transact['name']
line_vals['account_number'] = transact['account_number']
line_vals['ref'] = transact['ref']
testfile = get_module_resource(
'account_bank_statement_import_mt940_base',
'test_files',
'test-sns.940',
)
parser = MT940()
datafile = open(testfile, 'rb').read()
statements = parser.parse(datafile, header_lines=1)
_prepare_statement_lines(statements)
path_addon = 'odoo.addons.account_bank_statement_import.'
path_file = 'account_bank_statement_import.'
path_class = 'AccountBankStatementImport.'
method = path_addon + path_file + path_class + '_parse_file'
with patch(method) as my_mock:
my_mock.return_value = statements
action = self.env['account.bank.statement.import'].create({
'data_file': base64.b64encode(datafile),
}).import_file()
self.assertTrue(len(action['context']['statement_ids']) == 3)
transact = self.transactions[-1]
for statement in self.env['account.bank.statement'].browse(
action['context']['statement_ids'][-1]):
for line in statement.line_ids:
self.assertTrue(
line.bank_account_id.acc_number ==
transact['account_number'])
self.assertTrue(line.amount == transact['amount'])
self.assertTrue(line.date == statement.date)
self.assertTrue(line.name == transact['name'])
self.assertTrue(line.ref == transact['ref'])
Loading…
Cancel
Save