Browse Source

Merge pull request #137 from feketemihai/11.0-mig-account_bank_statement_import_mt940_base

[11.0] [MIG] account_bank_statement_import_mt940_base
pull/154/head
Pedro M. Baeza 6 years ago
committed by GitHub
parent
commit
31df61d03e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 55
      account_bank_statement_import_mt940_base/README.rst
  2. 1
      account_bank_statement_import_mt940_base/__init__.py
  3. 14
      account_bank_statement_import_mt940_base/__manifest__.py
  4. 310
      account_bank_statement_import_mt940_base/mt940.py
  5. 61
      account_bank_statement_import_mt940_base/test_files/test-ing.940
  6. 29
      account_bank_statement_import_mt940_base/test_files/test-rabo.swi
  7. 78
      account_bank_statement_import_mt940_base/test_files/test-sns.940
  8. 11
      account_bank_statement_import_mt940_base/test_files/test-wrong-file.940
  9. 1
      account_bank_statement_import_mt940_base/tests/__init__.py
  10. 239
      account_bank_statement_import_mt940_base/tests/test_import_bank_statement.py

55
account_bank_statement_import_mt940_base/README.rst

@ -0,0 +1,55 @@
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png
:target: https://www.gnu.org/licenses/agpl
:alt: License: AGPL-3
====================
Bank Statement MT940
====================
This module provides a generic parser for MT940 files. Given that MT940 is a
non-open non-standard of pure evil in the way that every bank cooks up its own
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
certain bank.
See account_bank_statement_import_mt940_nl_ing for an example on how to use it.
Known issues / Roadmap
======================
* None
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 smash it by providing detailed and welcomed feedback.
Credits
=======
Contributors
------------
* Stefan Rijnhart <srijnhart@therp.nl>
* Ronald Portier <rportier@therp.nl>
* Andrea Stirpe <a.stirpe@onestein.nl>
* Fekete Mihai <mihai.fekete@forbiom.eu>
Maintainer
----------
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
This module is maintained by the OCA.
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
To contribute to this module, please visit https://odoo-community.org.

1
account_bank_statement_import_mt940_base/__init__.py

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

14
account_bank_statement_import_mt940_base/__manifest__.py

@ -0,0 +1,14 @@
# Copyright (C) 2013-2015 Therp BV <http://therp.nl>
{
'name': 'MT940 Bank Statements Import',
'version': '11.0.1.0.0',
'license': 'AGPL-3',
'author': 'Odoo Community Association (OCA), Therp BV',
'website': 'https://github.com/OCA/bank-statement-import',
'category': 'Banking addons',
'depends': [
'account_bank_statement_import',
],
'installable': True
}

310
account_bank_statement_import_mt940_base/mt940.py

@ -0,0 +1,310 @@
# Copyright (C) 2014-2015 Therp BV <http://therp.nl>.
# 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 logging
from datetime import datetime
def str2amount(sign, amount_str):
"""Convert sign (C or D) and amount in string to signed amount (float)."""
factor = (1 if sign == 'C' else -1)
return factor * float(amount_str.replace(',', '.'))
def get_subfields(data, codewords):
"""Return dictionary with value array for each codeword in data.
For instance:
data =
/BENM//NAME/Kosten/REMI/Periode 01-10-2013 t/m 31-12-2013/ISDT/20
codewords = ['BENM', 'ADDR', 'NAME', 'CNTP', ISDT', 'REMI']
Then return subfields = {
'BENM': [],
'NAME': ['Kosten'],
'REMI': ['Periode 01-10-2013 t', 'm 31-12-2013'],
'ISDT': ['20'],
}
"""
subfields = {}
current_codeword = None
for word in data.split('/'):
if not word and not current_codeword:
continue
if word in codewords:
current_codeword = word
subfields[current_codeword] = []
continue
if current_codeword in subfields:
subfields[current_codeword].append(word)
return subfields
def get_counterpart(transaction, subfield):
"""Get counterpart from transaction.
Counterpart is often stored in subfield of tag 86. The subfield
can be BENM, ORDP, CNTP"""
if not subfield:
return # subfield is empty
if len(subfield) >= 1 and subfield[0]:
transaction.update({'account_number': subfield[0]})
if len(subfield) >= 2 and subfield[1]:
transaction.update({'account_bic': subfield[1]})
if len(subfield) >= 3 and subfield[2]:
transaction.update({'partner_name': subfield[2]})
def handle_common_subfields(transaction, subfields):
"""Deal with common functionality for tag 86 subfields."""
# Get counterpart from CNTP, BENM or ORDP subfields:
for counterpart_field in ['CNTP', 'BENM', 'ORDP']:
if counterpart_field in subfields:
get_counterpart(transaction, subfields[counterpart_field])
if not transaction.get('name'):
transaction['name'] = ''
# REMI: Remitter information (text entered by other party on trans.):
if 'REMI' in subfields:
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):
if transaction.get('ref') in subfields:
transaction['ref'] = ''.join(subfields[transaction['ref']])
class MT940(object):
"""Inherit this class in your account_banking.parsers.models.parser,
define functions to handle the tags you need to handle and adjust static
variables as needed.
At least, you should override handle_tag_61 and handle_tag_86.
Don't forget to call super.
handle_tag_* functions receive the remainder of the the line (that is,
without ':XX:') and are supposed to write into self.current_transaction
"""
def __init__(self):
"""Initialize parser - override at least header_regex.
This in fact uses the ING syntax, override in others."""
self.mt940_type = 'General'
self.header_lines = 3 # Number of lines to skip
self.header_regex = '^0000 01INGBNL2AXXXX|^{1' # Start of header
self.footer_regex = '^-}$|^-XXX$' # Stop processing on seeing this
self.tag_regex = '^:[0-9]{2}[A-Z]*:' # Start of new tag
self.current_statement = None
self.current_transaction = None
self.statements = []
self.currency_code = None
self.account_number = None
def is_mt940(self, line):
"""determine if a line is the header of a statement"""
if not bool(re.match(self.header_regex, line)):
raise ValueError(
'File starting with %s does not seem to be a'
' valid %s MT940 format bank statement.' %
(line[:12], self.mt940_type)
)
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."""
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
record_line = ''
try:
while True:
if not self.current_statement:
self.handle_header(line, iterator,
header_lines=header_lines)
line = next(iterator)
if not self.is_tag(line) and not self.is_footer(line):
record_line = self.add_record_line(line, record_line)
continue
if record_line:
self.handle_record(record_line)
if self.is_footer(line):
self.handle_footer(line, iterator)
record_line = ''
continue
record_line = line
except StopIteration:
pass
if self.current_statement:
if record_line:
self.handle_record(record_line)
record_line = ''
self.statements.append(self.current_statement)
self.current_statement = None
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):
"""determine if a line is the footer of a statement"""
return line and bool(re.match(self.footer_regex, line))
def is_tag(self, line):
"""determine if a line has a tag"""
return line and bool(re.match(self.tag_regex, line))
def handle_header(self, dummy_line, iterator, header_lines=None):
"""skip header lines, create current statement"""
if not header_lines:
header_lines = self.header_lines
for dummy_i in range(header_lines):
next(iterator)
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):
"""add current statement to list, reset state"""
self.statements.append(self.current_statement)
self.current_statement = None
def handle_record(self, line):
"""find a function to handle the record represented by line"""
tag_match = re.match(self.tag_regex, line)
tag = tag_match.group(0).strip(':')
if not hasattr(self, 'handle_tag_%s' % tag): # pragma: no cover
logging.error('Unknown tag %s', tag)
logging.error(line)
return
handler = getattr(self, 'handle_tag_%s' % tag)
handler(line[tag_match.end():])
def handle_tag_20(self, data):
"""Contains unique ? message ID"""
pass
def handle_tag_25(self, data):
"""Handle tag 25: local bank account information."""
data = data.replace('EUR', '').replace('.', '').strip()
self.account_number = data
def handle_tag_28C(self, data):
"""Sequence number within batch - normally only zeroes."""
pass
def handle_tag_60F(self, data):
"""get start balance and currency"""
# For the moment only first 60F record
# The alternative would be to split the file and start a new
# statement for each 20: tag encountered.
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):
"""get transaction values"""
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):
"""Get ending balance, statement date and id.
We use the date on the last 62F tag as statement date, as the date
on the 60F record (previous end balance) might contain a date in
a previous period.
We generate the statement.id from the local_account and the end-date,
this should normally be unique, provided there is a maximum of
one statement per day.
Depending on the bank, there might be multiple 62F tags in the import
file. The last one counts.
"""
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:
# But do replace statement_id's added before (therefore starting
# with local_account), because we need the date on the last 62F
# record.
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):
"""get current balance in currency"""
pass
def handle_tag_65(self, data):
"""get future balance in currency"""
pass
def handle_tag_86(self, data):
"""details for previous transaction, here most differences between
banks occur"""
pass

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

239
account_bank_statement_import_mt940_base/tests/test_import_bank_statement.py

@ -0,0 +1,239 @@
# 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