Browse Source
Merge pull request #15 from NL66278/8.0_import_parsers_new_api_therp
Merge pull request #15 from NL66278/8.0_import_parsers_new_api_therp
8.0 import parsers new api therppull/35/head
Stéphane Bidoul (ACSONE)
9 years ago
42 changed files with 2679 additions and 176 deletions
-
2account_bank_statement_import/__openerp__.py
-
348account_bank_statement_import/account_bank_statement_import.py
-
56account_bank_statement_import/i18n/account_bank_statement_import.pot
-
236account_bank_statement_import/i18n/nl.po
-
236account_bank_statement_import/parserlib.py
-
2account_bank_statement_import/tests/__init__.py
-
9account_bank_statement_import/tests/test_import_bank_statement.py
-
131account_bank_statement_import/tests/test_import_file.py
-
49account_bank_statement_import_camt/README.rst
-
1account_bank_statement_import_camt/__init__.py
-
34account_bank_statement_import_camt/__openerp__.py
-
44account_bank_statement_import_camt/account_bank_statement_import.py
-
239account_bank_statement_import_camt/camt.py
-
26account_bank_statement_import_camt/demo/demo_data.xml
-
241account_bank_statement_import_camt/test_files/test-camt053.xml
-
23account_bank_statement_import_camt/tests/__init__.py
-
45account_bank_statement_import_camt/tests/test_import_bank_statement.py
-
53account_bank_statement_import_mt940_base/README.rst
-
1account_bank_statement_import_mt940_base/__init__.py
-
31account_bank_statement_import_mt940_base/__openerp__.py
-
262account_bank_statement_import_mt940_base/mt940.py
-
51account_bank_statement_import_mt940_nl_ing/README.rst
-
1account_bank_statement_import_mt940_nl_ing/__init__.py
-
34account_bank_statement_import_mt940_nl_ing/__openerp__.py
-
44account_bank_statement_import_mt940_nl_ing/account_bank_statement_import.py
-
26account_bank_statement_import_mt940_nl_ing/demo/demo_data.xml
-
66account_bank_statement_import_mt940_nl_ing/mt940.py
-
61account_bank_statement_import_mt940_nl_ing/test_files/test-ing-old.940
-
62account_bank_statement_import_mt940_nl_ing/test_files/test-ing.940
-
25account_bank_statement_import_mt940_nl_ing/tests/__init__.py
-
53account_bank_statement_import_mt940_nl_ing/tests/test_import_bank_statement.py
-
47account_bank_statement_import_mt940_nl_rabo/README.rst
-
20account_bank_statement_import_mt940_nl_rabo/__init__.py
-
32account_bank_statement_import_mt940_nl_rabo/__openerp__.py
-
46account_bank_statement_import_mt940_nl_rabo/account_bank_statement_import.py
-
86account_bank_statement_import_mt940_nl_rabo/mt940.py
-
29account_bank_statement_import_mt940_nl_rabo/test_files/test-rabo.swi
-
23account_bank_statement_import_mt940_nl_rabo/tests/__init__.py
-
46account_bank_statement_import_mt940_nl_rabo/tests/test_import_bank_statement.py
-
5account_bank_statement_import_ofx/__openerp__.py
-
27account_bank_statement_import_ofx/demo/demo_data.xml
-
2account_bank_statement_import_qif/__openerp__.py
@ -0,0 +1,236 @@ |
|||
# Translation of Odoo Server. |
|||
# This file contains the translation of the following modules: |
|||
# * account_bank_statement_import |
|||
# Therp BV <therp.nl>, 2015. |
|||
# |
|||
msgid "" |
|||
msgstr "" |
|||
"Project-Id-Version: Odoo Server 8.0\n" |
|||
"Report-Msgid-Bugs-To: \n" |
|||
"POT-Creation-Date: 2015-06-11 12:15+0000\n" |
|||
"PO-Revision-Date: 2015-06-11 14:37+0200\n" |
|||
"Last-Translator: Therp BV <therp.nl>\n" |
|||
"Language: nl\n" |
|||
"Language-Team: nl\n" |
|||
"MIME-Version: 1.0\n" |
|||
"Content-Type: text/plain; charset=UTF-8\n" |
|||
"Content-Transfer-Encoding: \n" |
|||
"Plural-Forms: \n" |
|||
"X-Generator: Gtranslator 2.91.6\n" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:347 |
|||
#, python-format |
|||
msgid "%d transactions had already been imported and were ignored." |
|||
msgstr "%d Transacties zijn overgeslagen omdat ze al reeds waren geïmporteerd." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:350 |
|||
#, python-format |
|||
msgid "1 transaction had already been imported and was ignored." |
|||
msgstr "1. Transactie was al geïmporteerd en is overgeslagen." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "1. Download your bank statements from your bank website." |
|||
msgstr "1. Download het bankafschrift bestand van de website van uw bank." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "" |
|||
"2. Make sure you have installed the right module to support the file format." |
|||
msgstr "" |
|||
"2. Zorg ervoor dat de modules die het formaat van uw bestand ondersteunen " |
|||
"zijn geïnstalleerd." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "3. Select the file and click 'Import'." |
|||
msgstr "3. Selecteer het bestand en klik op \"Importeren\"." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: sql_constraint:account.bank.statement.line:0 |
|||
msgid "A bank account transactions can be imported only once !" |
|||
msgstr "De transacties kunnen slechts eenmalig worden geïmporteerd." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: sql_constraint:res.partner.bank:0 |
|||
msgid "Account Number must be unique" |
|||
msgstr "Rekeningnummer moet uniek zijn" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:353 |
|||
#, python-format |
|||
msgid "Already imported items" |
|||
msgstr "Al eerder geïmporteerde regels" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: model:ir.model,name:account_bank_statement_import.model_res_partner_bank |
|||
msgid "Bank Accounts" |
|||
msgstr "Bankrekeningen" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:account.bank.statement.import,data_file:0 |
|||
msgid "Bank Statement File" |
|||
msgstr "Bankafschriften bestand" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: model:ir.model,name:account_bank_statement_import.model_account_bank_statement_line |
|||
msgid "Bank Statement Line" |
|||
msgstr "Bankafschrift regel" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:116 |
|||
#, python-format |
|||
msgid "Can not determine journal for import." |
|||
msgstr "Kan niet bepalen welk dagboek gebruikt moet worden." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "Cancel" |
|||
msgstr "Annuleren" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:154 |
|||
#, python-format |
|||
msgid "" |
|||
"Could not make sense of the given file.\n" |
|||
"Did you install the module to support this type of file ?" |
|||
msgstr "" |
|||
"Kon het bestand niet interpreteren.\n" |
|||
"Heeft u de juiste modules voor dit type bestand geïnstalleerd?" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:account.bank.statement.import,create_uid:0 |
|||
msgid "Created by" |
|||
msgstr "Aangemaakt door" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:account.bank.statement.import,create_date:0 |
|||
msgid "Created on" |
|||
msgstr "Aangemaakt op" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: help:account.bank.statement.import,data_file:0 |
|||
msgid "" |
|||
"Get you bank statements in electronic format from your bank and select them " |
|||
"here." |
|||
msgstr "" |
|||
"Verkrijg de bankafschriften van uw bank in elektronische vorm en selecteer " |
|||
"ze hier." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "How to import your bank statement :" |
|||
msgstr "Hoe uw bankafschrift te importeren:" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:account.bank.statement.import,id:0 |
|||
msgid "ID" |
|||
msgstr "ID" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: model:ir.actions.act_window,name:account_bank_statement_import.action_account_bank_statement_import |
|||
#: model:ir.ui.menu,name:account_bank_statement_import.menu_account_bank_statement_import |
|||
msgid "Import" |
|||
msgstr "Import" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: model:ir.model,name:account_bank_statement_import.model_account_bank_statement_import |
|||
msgid "Import Bank Statement" |
|||
msgstr "Geïmporteerd bankafschrift" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "Import Bank Statements" |
|||
msgstr "Importeer bankafschriften" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:account.bank.statement.line,unique_import_id:0 |
|||
msgid "Import ID" |
|||
msgstr "Import ID" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:account.bank.statement.import,write_uid:0 |
|||
msgid "Last Updated by" |
|||
msgstr "Laatst bijgewerkt door" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:account.bank.statement.import,write_date:0 |
|||
msgid "Last Updated on" |
|||
msgstr "Laatst bijgewerkt op" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: field:res.partner.bank,sanitized_acc_number:0 |
|||
msgid "Sanitized Account Number" |
|||
msgstr "Gestandaardiseerd rekeningnummer" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:237 |
|||
#, python-format |
|||
msgid "Statement currency id is %d, but company currency id = %d." |
|||
msgstr "Valuta id van afschrift = %d, maar valuta id van bedrijf = %d." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:224 |
|||
#, python-format |
|||
msgid "Statement currency id is %d, but journal currency id = %d." |
|||
msgstr "Valuta id van afschrift = %d, maar valuta id van dagboek = %d." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:179 |
|||
#, python-format |
|||
msgid "Statement has invalid currency code %s" |
|||
msgstr "Bankafschrift heeft ongeldige valutacode %s" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:207 |
|||
#, python-format |
|||
msgid "The account of this statement is linked to another journal." |
|||
msgstr "" |
|||
"Het rekeningnummer van dit afschrift is gekoppeld aan een ander dagboek." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:242 |
|||
#, python-format |
|||
msgid "" |
|||
"The currency of the bank statement is not the same as the company currency !" |
|||
msgstr "" |
|||
"De valuta van het afschrift is niet gelijk aan de valuta van het bedrijf!" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:229 |
|||
#, python-format |
|||
msgid "" |
|||
"The currency of the bank statement is not the same as the currency of the " |
|||
"journal !" |
|||
msgstr "" |
|||
"De valuta van het afschrift is niet hetzelfde als de valuta van het dagboek!" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:163 |
|||
#, python-format |
|||
msgid "This file doesn't contain any statement." |
|||
msgstr "Dit bestand bevat geen enkel afschrift." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:168 |
|||
#, python-format |
|||
msgid "This file doesn't contain any transaction." |
|||
msgstr "Dit bestand bevat geen enkele transactie." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:88 |
|||
#, python-format |
|||
msgid "You have already imported that file." |
|||
msgstr "U heeft dit bestand al geïmporteerd." |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "_Import" |
|||
msgstr "" |
|||
|
|||
#. module: account_bank_statement_import |
|||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view |
|||
msgid "or" |
|||
msgstr "" |
@ -0,0 +1,236 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
"""Classes and definitions used in parsing bank statements.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 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/>. |
|||
# |
|||
############################################################################## |
|||
|
|||
|
|||
class BankTransaction(dict): |
|||
"""Single transaction that is part of a bank statement.""" |
|||
|
|||
@property |
|||
def value_date(self): |
|||
"""property getter""" |
|||
return self['date'] |
|||
|
|||
@value_date.setter |
|||
def value_date(self, value_date): |
|||
"""property setter""" |
|||
self['date'] = value_date |
|||
|
|||
@property |
|||
def name(self): |
|||
"""property getter""" |
|||
return self['name'] |
|||
|
|||
@name.setter |
|||
def name(self, name): |
|||
"""property setter""" |
|||
self['name'] = name |
|||
|
|||
@property |
|||
def transferred_amount(self): |
|||
"""property getter""" |
|||
return self['amount'] |
|||
|
|||
@transferred_amount.setter |
|||
def transferred_amount(self, transferred_amount): |
|||
"""property setter""" |
|||
self['amount'] = transferred_amount |
|||
|
|||
@property |
|||
def eref(self): |
|||
"""property getter""" |
|||
return self['ref'] |
|||
|
|||
@eref.setter |
|||
def eref(self, eref): |
|||
"""property setter""" |
|||
self['ref'] = eref |
|||
if not self.message: |
|||
self.name = eref |
|||
|
|||
@property |
|||
def message(self): |
|||
"""property getter""" |
|||
return self._message |
|||
|
|||
@message.setter |
|||
def message(self, message): |
|||
"""property setter""" |
|||
self._message = message |
|||
self.name = message |
|||
|
|||
@property |
|||
def remote_owner(self): |
|||
"""property getter""" |
|||
return self['partner_name'] |
|||
|
|||
@remote_owner.setter |
|||
def remote_owner(self, remote_owner): |
|||
"""property setter""" |
|||
self['partner_name'] = remote_owner |
|||
if not (self.message or self.eref): |
|||
self.name = remote_owner |
|||
|
|||
@property |
|||
def remote_account(self): |
|||
"""property getter""" |
|||
return self['account_number'] |
|||
|
|||
@remote_account.setter |
|||
def remote_account(self, remote_account): |
|||
"""property setter""" |
|||
self['account_number'] = remote_account |
|||
|
|||
@property |
|||
def note(self): |
|||
return self['note'] |
|||
|
|||
@note.setter |
|||
def note(self, note): |
|||
self['note'] = note |
|||
|
|||
def __init__(self): |
|||
"""Define and initialize attributes. |
|||
|
|||
Not all attributes are already used in the actual import. |
|||
""" |
|||
super(BankTransaction, self).__init__() |
|||
self.transfer_type = False # Action type that initiated this message |
|||
self.execution_date = False # The posted date of the action |
|||
self.value_date = False # The value date of the action |
|||
self.remote_account = False # The account of the other party |
|||
self.remote_currency = False # The currency used by the other party |
|||
self.exchange_rate = 0.0 |
|||
# The exchange rate used for conversion of local_currency and |
|||
# remote_currency |
|||
self.transferred_amount = 0.0 # actual amount transferred |
|||
self.name = '' |
|||
self._message = False # message from the remote party |
|||
self.eref = False # end to end reference for transactions |
|||
self.remote_owner = False # name of the other party |
|||
self.remote_owner_address = [] # other parties address lines |
|||
self.remote_owner_city = False # other parties city name |
|||
self.remote_owner_postalcode = False # other parties zip code |
|||
self.remote_owner_country_code = False # other parties country code |
|||
self.remote_bank_bic = False # bic of other party's bank |
|||
self.provision_costs = False # costs charged by bank for transaction |
|||
self.provision_costs_currency = False |
|||
self.provision_costs_description = False |
|||
self.error_message = False # error message for interaction with user |
|||
self.storno_retry = False |
|||
# If True, make cancelled debit eligible for a next direct debit run |
|||
self.data = '' # Raw data from which the transaction has been parsed |
|||
|
|||
|
|||
class BankStatement(dict): |
|||
"""A bank statement groups data about several bank transactions.""" |
|||
|
|||
@property |
|||
def statement_id(self): |
|||
"""property getter""" |
|||
return self['name'] |
|||
|
|||
def _set_transaction_ids(self): |
|||
"""Set transaction ids to statement_id with sequence-number.""" |
|||
subno = 0 |
|||
for transaction in self['transactions']: |
|||
subno += 1 |
|||
transaction['unique_import_id'] = ( |
|||
self.statement_id + str(subno).zfill(4)) |
|||
|
|||
@statement_id.setter |
|||
def statement_id(self, statement_id): |
|||
"""property setter""" |
|||
self['name'] = statement_id |
|||
self._set_transaction_ids() |
|||
|
|||
@property |
|||
def local_account(self): |
|||
"""property getter""" |
|||
return self['account_number'] |
|||
|
|||
@local_account.setter |
|||
def local_account(self, local_account): |
|||
"""property setter""" |
|||
self['account_number'] = local_account |
|||
|
|||
@property |
|||
def local_currency(self): |
|||
"""property getter""" |
|||
return self['currency_code'] |
|||
|
|||
@local_currency.setter |
|||
def local_currency(self, local_currency): |
|||
"""property setter""" |
|||
self['currency_code'] = local_currency |
|||
|
|||
@property |
|||
def start_balance(self): |
|||
"""property getter""" |
|||
return self['balance_start'] |
|||
|
|||
@start_balance.setter |
|||
def start_balance(self, start_balance): |
|||
"""property setter""" |
|||
self['balance_start'] = start_balance |
|||
|
|||
@property |
|||
def end_balance(self): |
|||
"""property getter""" |
|||
return self['balance_end'] |
|||
|
|||
@end_balance.setter |
|||
def end_balance(self, end_balance): |
|||
"""property setter""" |
|||
self['balance_end'] = end_balance |
|||
self['balance_end_real'] = end_balance |
|||
|
|||
@property |
|||
def date(self): |
|||
"""property getter""" |
|||
return self['date'] |
|||
|
|||
@date.setter |
|||
def date(self, date): |
|||
"""property setter""" |
|||
self['date'] = date |
|||
|
|||
def create_transaction(self): |
|||
"""Create and append transaction. |
|||
|
|||
This should only be called after statement_id has been set, because |
|||
statement_id will become part of the unique transaction_id. |
|||
""" |
|||
transaction = BankTransaction() |
|||
self['transactions'].append(transaction) |
|||
# Fill default id, but might be overruled |
|||
transaction['unique_import_id'] = ( |
|||
self.statement_id + str(len(self['transactions'])).zfill(4)) |
|||
return transaction |
|||
|
|||
def __init__(self): |
|||
super(BankStatement, self).__init__() |
|||
self['transactions'] = [] |
|||
self.statement_id = '' |
|||
self.local_account = '' |
|||
self.local_currency = '' |
|||
self.date = '' |
|||
self.start_balance = 0.0 |
|||
self.end_balance = 0.0 |
@ -1,3 +1,5 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
"""Define tests to be run.""" |
|||
from . import test_res_partner_bank |
|||
from . import test_import_bank_statement |
|||
from .test_import_file import TestStatementFile |
@ -0,0 +1,131 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Provide common base for bank statement import tests.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 2015 Therp BV <http://therp.nl>. |
|||
# |
|||
# All other contributions are (C) by their respective contributors |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
import logging |
|||
|
|||
from openerp.tests.common import TransactionCase |
|||
from openerp.modules.module import get_module_resource |
|||
|
|||
|
|||
_logger = logging.getLogger(__name__) |
|||
|
|||
|
|||
class TestStatementFile(TransactionCase): |
|||
"""Check wether statements with transactions correctly imported. |
|||
|
|||
No actual tests are done in this class, implementations are in |
|||
subclasses in actual import modules. |
|||
""" |
|||
|
|||
def _test_transaction( |
|||
self, statement_obj, remote_account=False, |
|||
transferred_amount=False, value_date=False, ref=False): |
|||
"""Check wether transaction with attributes passed was created. |
|||
|
|||
Actually this method also tests wether automatic creation of |
|||
partner bank accounts is working. |
|||
""" |
|||
transaction_model = self.env['account.bank.statement.line'] |
|||
partner_bank_model = self.env['res.partner.bank'] |
|||
domain = [('statement_id', '=', statement_obj.id)] |
|||
if remote_account: |
|||
bids = partner_bank_model.search( |
|||
[('acc_number', '=', remote_account)]) |
|||
self.assertTrue( |
|||
bids, |
|||
'Bank-account %s not found after parse.' % remote_account |
|||
) |
|||
domain.append(('bank_account_id', '=', bids[0].id)) |
|||
if transferred_amount: |
|||
domain.append(('amount', '=', transferred_amount)) |
|||
if value_date: |
|||
domain.append(('date', '=', value_date)) |
|||
if ref: |
|||
domain.append(('ref', '=', ref)) |
|||
ids = transaction_model.search(domain) |
|||
if not ids: |
|||
# We will get assertion error, but to solve we need to see |
|||
# what transactions have been added: |
|||
self.cr.execute( |
|||
"select name, date, amount, ref, bank_account_id" |
|||
" from account_bank_statement_line" |
|||
" where statement_id=%d" % statement_obj.id) |
|||
_logger.error( |
|||
"Transaction not found in %s" % |
|||
str(self.cr.fetchall()) |
|||
) |
|||
self.assertTrue( |
|||
ids, |
|||
'Transaction %s not found after parse.' % str(domain) |
|||
) |
|||
|
|||
def _test_statement_import( |
|||
self, module_name, file_name, statement_name, local_account=False, |
|||
start_balance=False, end_balance=False, transactions=None): |
|||
"""Test correct creation of single statement.""" |
|||
import_model = self.env['account.bank.statement.import'] |
|||
partner_bank_model = self.env['res.partner.bank'] |
|||
statement_model = self.env['account.bank.statement'] |
|||
statement_path = get_module_resource( |
|||
module_name, |
|||
'test_files', |
|||
file_name |
|||
) |
|||
statement_file = open( |
|||
statement_path, 'rb').read().encode('base64') |
|||
bank_statement_id = import_model.create( |
|||
dict( |
|||
data_file=statement_file, |
|||
) |
|||
) |
|||
bank_statement_id.import_file() |
|||
# Check wether bank account has been created: |
|||
if local_account: |
|||
bids = partner_bank_model.search( |
|||
[('acc_number', '=', local_account)]) |
|||
self.assertTrue( |
|||
bids, |
|||
'Bank account %s not created from statement' % local_account |
|||
) |
|||
# statement name is account number + '-' + date of last 62F line: |
|||
ids = statement_model.search([('name', '=', statement_name)]) |
|||
self.assertTrue( |
|||
ids, |
|||
'Statement %s not found after parse.' % statement_name |
|||
) |
|||
statement_obj = ids[0] |
|||
if start_balance: |
|||
self.assertTrue( |
|||
abs(statement_obj.balance_start - start_balance) < 0.00001, |
|||
'Start balance %f not equal to expected %f' % |
|||
(statement_obj.balance_start, start_balance) |
|||
) |
|||
if end_balance: |
|||
self.assertTrue( |
|||
abs(statement_obj.balance_end_real - end_balance) < 0.00001, |
|||
'End balance %f not equal to expected %f' % |
|||
(statement_obj.balance_end_real, end_balance) |
|||
) |
|||
# Maybe we need to test transactions? |
|||
if transactions: |
|||
for transaction in transactions: |
|||
self._test_transaction(statement_obj, **transaction) |
@ -0,0 +1,49 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:alt: License: AGPL-3 |
|||
|
|||
Bank Statement Parse Camt |
|||
========================= |
|||
|
|||
Module to import SEPA CAMT.053 Format bank statement files. |
|||
|
|||
Based on the Banking addons framework. |
|||
|
|||
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 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**>`_. |
|||
|
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Stefan Rijnhart <srijnhart@therp.nl> |
|||
* Ronald Portier <rportier@therp.nl> |
|||
|
|||
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 http://odoo-community.org. |
|||
This module should make it easy to migrate bank statement import |
|||
modules written for earlies versions of Odoo/OpenERP. |
@ -0,0 +1 @@ |
|||
from . import account_bank_statement_import |
@ -0,0 +1,34 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# 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': 'CAMT Format Bank Statements Import', |
|||
'version': '0.3', |
|||
'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', |
|||
], |
|||
'demo': [ |
|||
'demo/demo_data.xml', |
|||
], |
|||
'installable': True, |
|||
} |
@ -0,0 +1,44 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Add process_camt method to account.bank.statement.import.""" |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
import logging |
|||
from openerp import models |
|||
from .camt import CamtParser as Parser |
|||
|
|||
|
|||
_logger = logging.getLogger(__name__) |
|||
|
|||
|
|||
class AccountBankStatementImport(models.TransientModel): |
|||
"""Add process_camt method to account.bank.statement.import.""" |
|||
_inherit = 'account.bank.statement.import' |
|||
|
|||
def _parse_file(self, cr, uid, data_file, context=None): |
|||
"""Parse a CAMT053 XML file.""" |
|||
parser = Parser() |
|||
try: |
|||
_logger.debug("Try parsing with camt.") |
|||
return parser.parse(data_file) |
|||
except ValueError: |
|||
# Not a camt file, returning super will call next candidate: |
|||
_logger.debug("Statement file was not a camt file.", |
|||
exc_info=True) |
|||
return super(AccountBankStatementImport, self)._parse_file( |
|||
cr, uid, data_file, context=context) |
@ -0,0 +1,239 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Class to parse camt files.""" |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
import re |
|||
from datetime import datetime |
|||
from lxml import etree |
|||
from openerp.addons.account_bank_statement_import.parserlib import ( |
|||
BankStatement) |
|||
|
|||
|
|||
class CamtParser(object): |
|||
"""Parser for camt bank statement import files.""" |
|||
|
|||
def parse_amount(self, ns, node): |
|||
"""Parse element that contains Amount and CreditDebitIndicator.""" |
|||
if node is None: |
|||
return 0.0 |
|||
sign = 1 |
|||
amount = 0.0 |
|||
sign_node = node.xpath('ns:CdtDbtInd', namespaces={'ns': ns}) |
|||
if sign_node and sign_node[0].text == 'DBIT': |
|||
sign = -1 |
|||
amount_node = node.xpath('ns:Amt', namespaces={'ns': ns}) |
|||
if amount_node: |
|||
amount = sign * float(amount_node[0].text) |
|||
return amount |
|||
|
|||
def add_value_from_node( |
|||
self, ns, node, xpath_str, obj, attr_name, join_str=None): |
|||
"""Add value to object from first or all nodes found with xpath. |
|||
|
|||
If xpath_str is a list (or iterable), it will be seen as a series |
|||
of search path's in order of preference. The first item that results |
|||
in a found node will be used to set a value.""" |
|||
if not isinstance(xpath_str, (list, tuple)): |
|||
xpath_str = [xpath_str] |
|||
for search_str in xpath_str: |
|||
found_node = node.xpath(search_str, namespaces={'ns': ns}) |
|||
if found_node: |
|||
if join_str is None: |
|||
attr_value = found_node[0].text |
|||
else: |
|||
attr_value = join_str.join([x.text for x in found_node]) |
|||
setattr(obj, attr_name, attr_value) |
|||
break |
|||
|
|||
def parse_transaction_details(self, ns, node, transaction): |
|||
"""Parse transaction details (message, party, account...).""" |
|||
# message |
|||
self.add_value_from_node( |
|||
ns, node, [ |
|||
'./ns:RmtInf/ns:Ustrd', |
|||
'./ns:AddtlTxInf', |
|||
'./ns:AddtlNtryInf', |
|||
], transaction, 'message') |
|||
# eref |
|||
self.add_value_from_node( |
|||
ns, node, [ |
|||
'./ns:RmtInf/ns:Strd/ns:CdtrRefInf/ns:Ref', |
|||
'./ns:Refs/ns:EndToEndId', |
|||
], |
|||
transaction, 'eref' |
|||
) |
|||
# remote party values |
|||
party_type = 'Dbtr' |
|||
party_type_node = node.xpath( |
|||
'../../ns:CdtDbtInd', namespaces={'ns': ns}) |
|||
if party_type_node and party_type_node[0].text != 'CRDT': |
|||
party_type = 'Cdtr' |
|||
party_node = node.xpath( |
|||
'./ns:RltdPties/ns:%s' % party_type, namespaces={'ns': ns}) |
|||
if party_node: |
|||
self.add_value_from_node( |
|||
ns, party_node[0], './ns:Nm', transaction, 'remote_owner') |
|||
self.add_value_from_node( |
|||
ns, party_node[0], './ns:PstlAdr/ns:Ctry', transaction, |
|||
'remote_owner_country' |
|||
) |
|||
address_node = party_node[0].xpath( |
|||
'./ns:PstlAdr/ns:AdrLine', namespaces={'ns': ns}) |
|||
if address_node: |
|||
transaction.remote_owner_address = [address_node[0].text] |
|||
# Get remote_account from iban or from domestic account: |
|||
account_node = node.xpath( |
|||
'./ns:RltdPties/ns:%sAcct/ns:Id' % party_type, |
|||
namespaces={'ns': ns} |
|||
) |
|||
if account_node: |
|||
iban_node = account_node[0].xpath( |
|||
'./ns:IBAN', namespaces={'ns': ns}) |
|||
if iban_node: |
|||
transaction.remote_account = iban_node[0].text |
|||
bic_node = node.xpath( |
|||
'./ns:RltdAgts/ns:%sAgt/ns:FinInstnId/ns:BIC' % party_type, |
|||
namespaces={'ns': ns} |
|||
) |
|||
if bic_node: |
|||
transaction.remote_bank_bic = bic_node[0].text |
|||
else: |
|||
self.add_value_from_node( |
|||
ns, account_node[0], './ns:Othr/ns:Id', transaction, |
|||
'remote_account' |
|||
) |
|||
|
|||
def parse_transaction(self, ns, node, transaction): |
|||
"""Parse transaction (entry) node.""" |
|||
self.add_value_from_node( |
|||
ns, node, './ns:BkTxCd/ns:Prtry/ns:Cd', transaction, |
|||
'transfer_type' |
|||
) |
|||
self.add_value_from_node( |
|||
ns, node, './ns:BookgDt/ns:Dt', transaction, 'execution_date') |
|||
self.add_value_from_node( |
|||
ns, node, './ns:ValDt/ns:Dt', transaction, 'value_date') |
|||
transaction.transferred_amount = self.parse_amount(ns, node) |
|||
details_node = node.xpath( |
|||
'./ns:NtryDtls/ns:TxDtls', namespaces={'ns': ns}) |
|||
if details_node: |
|||
self.parse_transaction_details(ns, details_node[0], transaction) |
|||
transaction.data = etree.tostring(node) |
|||
return transaction |
|||
|
|||
def get_balance_amounts(self, ns, node): |
|||
"""Return opening and closing balance. |
|||
|
|||
Depending on kind of balance and statement, the balance might be in a |
|||
different kind of node: |
|||
OPBD = OpeningBalance |
|||
PRCD = PreviousClosingBalance |
|||
ITBD = InterimBalance (first ITBD is start-, second is end-balance) |
|||
CLBD = ClosingBalance |
|||
""" |
|||
start_balance_node = None |
|||
end_balance_node = None |
|||
for node_name in ['OPBD', 'PRCD', 'CLBD', 'ITBD']: |
|||
code_expr = ( |
|||
'./ns:Bal/ns:Tp/ns:CdOrPrtry/ns:Cd[text()="%s"]/../../..' % |
|||
node_name |
|||
) |
|||
balance_node = node.xpath(code_expr, namespaces={'ns': ns}) |
|||
if balance_node: |
|||
if node_name in ['OPBD', 'PRCD']: |
|||
start_balance_node = balance_node[0] |
|||
elif node_name == 'CLBD': |
|||
end_balance_node = balance_node[0] |
|||
else: |
|||
if not start_balance_node: |
|||
start_balance_node = balance_node[0] |
|||
if not end_balance_node: |
|||
end_balance_node = balance_node[-1] |
|||
return ( |
|||
self.parse_amount(ns, start_balance_node), |
|||
self.parse_amount(ns, end_balance_node) |
|||
) |
|||
|
|||
def parse_statement(self, ns, node): |
|||
"""Parse a single Stmt node.""" |
|||
statement = BankStatement() |
|||
self.add_value_from_node( |
|||
ns, node, [ |
|||
'./ns:Acct/ns:Id/ns:IBAN', |
|||
'./ns:Acct/ns:Id/ns:Othr/ns:Id', |
|||
], statement, 'local_account' |
|||
) |
|||
self.add_value_from_node( |
|||
ns, node, './ns:Id', statement, 'statement_id') |
|||
self.add_value_from_node( |
|||
ns, node, './ns:Acct/ns:Ccy', statement, 'local_currency') |
|||
(statement.start_balance, statement.end_balance) = ( |
|||
self.get_balance_amounts(ns, node)) |
|||
transaction_nodes = node.xpath('./ns:Ntry', namespaces={'ns': ns}) |
|||
for entry_node in transaction_nodes: |
|||
transaction = statement.create_transaction() |
|||
self.parse_transaction(ns, entry_node, transaction) |
|||
if statement['transactions']: |
|||
statement.date = datetime.strptime( |
|||
statement['transactions'][0].execution_date, "%Y-%m-%d") |
|||
return statement |
|||
|
|||
def check_version(self, ns, root): |
|||
"""Validate validity of camt file.""" |
|||
# Check wether it is camt at all: |
|||
re_camt = re.compile( |
|||
r'(^urn:iso:std:iso:20022:tech:xsd:camt.' |
|||
r'|^ISO:camt.)' |
|||
) |
|||
if not re_camt.search(ns): |
|||
raise ValueError('no camt: ' + ns) |
|||
# Check wether version 052 or 053: |
|||
re_camt_version = re.compile( |
|||
r'(^urn:iso:std:iso:20022:tech:xsd:camt.053.' |
|||
r'|^urn:iso:std:iso:20022:tech:xsd:camt.052.' |
|||
r'|^ISO:camt.053.' |
|||
r'|^ISO:camt.052.)' |
|||
) |
|||
if not re_camt_version.search(ns): |
|||
raise ValueError('no camt 052 or 053: ' + ns) |
|||
# Check GrpHdr element: |
|||
root_0_0 = root[0][0].tag[len(ns) + 2:] # strip namespace |
|||
if root_0_0 != 'GrpHdr': |
|||
raise ValueError('expected GrpHdr, got: ' + root_0_0) |
|||
|
|||
def parse(self, data): |
|||
"""Parse a camt.052 or camt.053 file.""" |
|||
try: |
|||
root = etree.fromstring( |
|||
data, parser=etree.XMLParser(recover=True)) |
|||
except etree.XMLSyntaxError: |
|||
# ABNAmro is known to mix up encodings |
|||
root = etree.fromstring( |
|||
data.decode('iso-8859-15').encode('utf-8')) |
|||
if root is None: |
|||
raise ValueError( |
|||
'Not a valid xml file, or not an xml file at all.') |
|||
ns = root.tag[1:root.tag.index("}")] |
|||
self.check_version(ns, root) |
|||
statements = [] |
|||
for node in root[0][1:]: |
|||
statement = self.parse_statement(ns, node) |
|||
if len(statement['transactions']): |
|||
statements.append(statement) |
|||
return statements |
@ -0,0 +1,26 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data> |
|||
|
|||
<record id="camt_bank_journal" model="account.journal"> |
|||
<field name="name">Bank Journal - (test camt)</field> |
|||
<field name="code">TBNKCAMT</field> |
|||
<field name="type">bank</field> |
|||
<field name="sequence_id" ref="account.sequence_bank_journal"/> |
|||
<field name="default_debit_account_id" ref="account.bnk"/> |
|||
<field name="default_credit_account_id" ref="account.bnk"/> |
|||
<field name="user_id" ref="base.user_root"/> |
|||
</record> |
|||
|
|||
<record id="camt_company_bank" model="res.partner.bank"> |
|||
<field name="owner_name">Your Company</field> |
|||
<field name="acc_number">NL77ABNA0574908765</field> |
|||
<field name="partner_id" ref="base.partner_root"></field> |
|||
<field name="company_id" ref="base.main_company"></field> |
|||
<field name="journal_id" ref="camt_bank_journal"></field> |
|||
<field name="state">bank</field> |
|||
<field name="bank" ref="base.res_bank_1"/> |
|||
</record> |
|||
</data> |
|||
|
|||
</openerp> |
@ -0,0 +1,241 @@ |
|||
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02"> |
|||
<BkToCstmrStmt> |
|||
<GrpHdr> |
|||
<MsgId>TESTBANK/NL/1420561226673</MsgId> |
|||
<CreDtTm>2013-01-06T16:20:26.673Z</CreDtTm> |
|||
</GrpHdr> |
|||
<Stmt> |
|||
<Id>1234Test/1</Id> |
|||
<LglSeqNb>2</LglSeqNb> |
|||
<CreDtTm>2013-01-06T16:20:26.673Z</CreDtTm> |
|||
<FrToDt> |
|||
<FrDtTm>2013-01-05T00:00:00.000Z</FrDtTm> |
|||
<ToDtTm>2013-01-05T23:59:59.999Z</ToDtTm> |
|||
</FrToDt> |
|||
<Acct> |
|||
<Id> |
|||
<IBAN>NL77ABNA0574908765</IBAN> |
|||
</Id> |
|||
<Nm>Example company</Nm> |
|||
<Svcr> |
|||
<FinInstnId> |
|||
<BIC>ABNANL2A</BIC> |
|||
</FinInstnId> |
|||
</Svcr> |
|||
</Acct> |
|||
<Bal> |
|||
<Tp> |
|||
<CdOrPrtry> |
|||
<Cd>OPBD</Cd> |
|||
</CdOrPrtry> |
|||
</Tp> |
|||
<Amt Ccy="EUR">15568.27</Amt> |
|||
<CdtDbtInd>CRDT</CdtDbtInd> |
|||
<Dt> |
|||
<Dt>2013-01-05</Dt> |
|||
</Dt> |
|||
</Bal> |
|||
<Bal> |
|||
<Tp> |
|||
<CdOrPrtry> |
|||
<Cd>CLBD</Cd> |
|||
</CdOrPrtry> |
|||
</Tp> |
|||
<Amt Ccy="EUR">15121.12</Amt> |
|||
<CdtDbtInd>CRDT</CdtDbtInd> |
|||
<Dt> |
|||
<Dt>2013-01-05</Dt> |
|||
</Dt> |
|||
</Bal> |
|||
<Ntry> |
|||
<Amt Ccy="EUR">754.25</Amt> |
|||
<CdtDbtInd>DBIT</CdtDbtInd> |
|||
<Sts>BOOK</Sts> |
|||
<BookgDt> |
|||
<Dt>2013-01-05</Dt> |
|||
</BookgDt> |
|||
<ValDt> |
|||
<Dt>2013-01-05</Dt> |
|||
</ValDt> |
|||
<BkTxCd> |
|||
<Domn> |
|||
<Cd>PMNT</Cd> |
|||
<Fmly> |
|||
<Cd>RDDT</Cd> |
|||
<SubFmlyCd>ESDD</SubFmlyCd> |
|||
</Fmly> |
|||
</Domn> |
|||
<Prtry> |
|||
<Cd>EI</Cd> |
|||
</Prtry> |
|||
</BkTxCd> |
|||
<NtryDtls> |
|||
<TxDtls> |
|||
<Refs> |
|||
<InstrId>INNDNL2U20141231000142300002844</InstrId> |
|||
<EndToEndId>435005714488-ABNO33052620</EndToEndId> |
|||
<MndtId>1880000341866</MndtId> |
|||
</Refs> |
|||
<AmtDtls> |
|||
<TxAmt> |
|||
<Amt Ccy="EUR">754.25</Amt> |
|||
</TxAmt> |
|||
</AmtDtls> |
|||
<RltdPties> |
|||
<Cdtr> |
|||
<Nm>INSURANCE COMPANY TESTX</Nm> |
|||
<PstlAdr> |
|||
<StrtNm>TEST STREET 20</StrtNm> |
|||
<TwnNm>1234 AB TESTCITY</TwnNm> |
|||
<Ctry>NL</Ctry> |
|||
</PstlAdr> |
|||
</Cdtr> |
|||
<CdtrAcct> |
|||
<Id> |
|||
<IBAN>NL46ABNA0499998748</IBAN> |
|||
</Id> |
|||
</CdtrAcct> |
|||
</RltdPties> |
|||
<RltdAgts> |
|||
<CdtrAgt> |
|||
<FinInstnId> |
|||
<BIC>ABNANL2A</BIC> |
|||
</FinInstnId> |
|||
</CdtrAgt> |
|||
</RltdAgts> |
|||
<RmtInf> |
|||
<Ustrd>Insurance policy 857239PERIOD 01.01.2013 - 31.12.2013</Ustrd> |
|||
</RmtInf> |
|||
<AddtlTxInf>MKB Insurance 859239PERIOD 01.01.2013 - 31.12.2013</AddtlTxInf> |
|||
</TxDtls> |
|||
</NtryDtls> |
|||
</Ntry> |
|||
<Ntry> |
|||
<Amt Ccy="EUR">594.05</Amt> |
|||
<CdtDbtInd>DBIT</CdtDbtInd> |
|||
<RvslInd>true</RvslInd> |
|||
<Sts>BOOK</Sts> |
|||
<BookgDt> |
|||
<Dt>2013-01-05</Dt> |
|||
</BookgDt> |
|||
<ValDt> |
|||
<Dt>2013-01-05</Dt> |
|||
</ValDt> |
|||
<BkTxCd> |
|||
<Domn> |
|||
<Cd>PMNT</Cd> |
|||
<Fmly> |
|||
<Cd>IDDT</Cd> |
|||
<SubFmlyCd>UPDD</SubFmlyCd> |
|||
</Fmly> |
|||
</Domn> |
|||
<Prtry> |
|||
<Cd>EIST</Cd> |
|||
</Prtry> |
|||
</BkTxCd> |
|||
<NtryDtls> |
|||
<TxDtls> |
|||
<Refs> |
|||
<InstrId>TESTBANK/NL/20141229/01206408</InstrId> |
|||
<EndToEndId>TESTBANK/NL/20141229/01206408</EndToEndId> |
|||
<MndtId>NL22ZZZ524885430000-C0125.1</MndtId> |
|||
</Refs> |
|||
<AmtDtls> |
|||
<TxAmt> |
|||
<Amt Ccy="EUR">564.05</Amt> |
|||
</TxAmt> |
|||
</AmtDtls> |
|||
<RltdPties> |
|||
<Cdtr> |
|||
<Nm>Test Customer</Nm> |
|||
<PstlAdr> |
|||
<Ctry>NL</Ctry> |
|||
</PstlAdr> |
|||
</Cdtr> |
|||
<CdtrAcct> |
|||
<Id> |
|||
<IBAN>NL46ABNA0499998748</IBAN> |
|||
</Id> |
|||
</CdtrAcct> |
|||
</RltdPties> |
|||
<RltdAgts> |
|||
<CdtrAgt> |
|||
<FinInstnId> |
|||
<BIC>ABNANL2A</BIC> |
|||
</FinInstnId> |
|||
</CdtrAgt> |
|||
</RltdAgts> |
|||
<RmtInf> |
|||
<Ustrd>Direct Debit S14 0410</Ustrd> |
|||
</RmtInf> |
|||
<RtrInf> |
|||
<Rsn> |
|||
<Cd>AC06</Cd> |
|||
</Rsn> |
|||
</RtrInf> |
|||
<AddtlTxInf>Direct debit S14 0410 AC07 Rek.nummer blokkade TESTBANK/NL/20141229/01206408</AddtlTxInf> |
|||
</TxDtls> |
|||
</NtryDtls> |
|||
</Ntry> |
|||
<Ntry> |
|||
<Amt Ccy="EUR">1405.31</Amt> |
|||
<CdtDbtInd>CRDT</CdtDbtInd> |
|||
<Sts>BOOK</Sts> |
|||
<BookgDt> |
|||
<Dt>2013-01-05</Dt> |
|||
</BookgDt> |
|||
<ValDt> |
|||
<Dt>2013-01-05</Dt> |
|||
</ValDt> |
|||
<BkTxCd> |
|||
<Domn> |
|||
<Cd>PMNT</Cd> |
|||
<Fmly> |
|||
<Cd>RCDT</Cd> |
|||
<SubFmlyCd>ESCT</SubFmlyCd> |
|||
</Fmly> |
|||
</Domn> |
|||
<Prtry> |
|||
<Cd>ET</Cd> |
|||
</Prtry> |
|||
</BkTxCd> |
|||
<NtryDtls> |
|||
<TxDtls> |
|||
<Refs> |
|||
<InstrId>INNDNL2U20130105000217200000708</InstrId> |
|||
<EndToEndId>115</EndToEndId> |
|||
</Refs> |
|||
<AmtDtls> |
|||
<TxAmt> |
|||
<Amt Ccy="EUR">1405.31</Amt> |
|||
</TxAmt> |
|||
</AmtDtls> |
|||
<RltdPties> |
|||
<Dbtr> |
|||
<Nm>3rd party Media</Nm> |
|||
<PstlAdr> |
|||
<StrtNm>SOMESTREET 570-A</StrtNm> |
|||
<TwnNm>1276 ML HOUSCITY</TwnNm> |
|||
<Ctry>NL</Ctry> |
|||
</PstlAdr> |
|||
</Dbtr> |
|||
<DbtrAcct> |
|||
<Id> |
|||
<IBAN>NL69ABNA0522123643</IBAN> |
|||
</Id> |
|||
</DbtrAcct> |
|||
</RltdPties> |
|||
<RltdAgts> |
|||
<DbtrAgt> |
|||
<FinInstnId> |
|||
<BIC>ABNANL2A</BIC> |
|||
</FinInstnId> |
|||
</DbtrAgt> |
|||
</RltdAgts> |
|||
<AddtlTxInf>#RD PARTY MEDIA CUSNO 90782 4210773</AddtlTxInf> |
|||
</TxDtls> |
|||
</NtryDtls> |
|||
</Ntry> |
|||
</Stmt> |
|||
</BkToCstmrStmt> |
|||
</Document> |
@ -0,0 +1,23 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
"""Test import of bank statement for camt.053.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 2015 Therp BV <http://therp.nl>. |
|||
# |
|||
# All other contributions are (C) by their respective contributors |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
from . import test_import_bank_statement |
@ -0,0 +1,45 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Run test to import camt.053 import.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 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/>. |
|||
# |
|||
############################################################################## |
|||
from openerp.addons.account_bank_statement_import.tests import ( |
|||
TestStatementFile) |
|||
|
|||
|
|||
class TestImport(TestStatementFile): |
|||
"""Run test to import camt import.""" |
|||
|
|||
def test_statement_import(self): |
|||
"""Test correct creation of single statement.""" |
|||
transactions = [ |
|||
{ |
|||
'remote_account': 'NL46ABNA0499998748', |
|||
'transferred_amount': -754.25, |
|||
'value_date': '2013-01-05', |
|||
'ref': '435005714488-ABNO33052620', |
|||
}, |
|||
] |
|||
# statement name is account number + '-' + date of last 62F line: |
|||
self._test_statement_import( |
|||
'account_bank_statement_import_camt', 'test-camt053.xml', |
|||
'1234Test/1', |
|||
local_account='NL77ABNA0574908765', |
|||
start_balance=15568.27, end_balance=15121.12, |
|||
transactions=transactions |
|||
) |
@ -0,0 +1,53 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
: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 bank_statement_parse_nl_ing_mt940 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 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**>`_. |
|||
|
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Stefan Rijnhart <srijnhart@therp.nl> |
|||
* Ronald Portier <rportier@therp.nl> |
|||
|
|||
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 http://odoo-community.org. |
|||
This module should make it easy to migrate bank statement import |
|||
modules written for earlies versions of Odoo/OpenERP. |
@ -0,0 +1 @@ |
|||
from . import mt940 |
@ -0,0 +1,31 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# 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', |
|||
'version': '1.1', |
|||
'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 |
|||
} |
@ -0,0 +1,262 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Generic parser for MT940 files, base for customized versions per bank.""" |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
import re |
|||
import logging |
|||
from datetime import datetime |
|||
|
|||
from openerp.addons.account_bank_statement_import.parserlib import ( |
|||
BankStatement) |
|||
|
|||
|
|||
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.remote_account = subfield[0] |
|||
if len(subfield) >= 2 and subfield[1]: |
|||
transaction.remote_bank_bic = subfield[1] |
|||
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] |
|||
|
|||
|
|||
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]) |
|||
# REMI: Remitter information (text entered by other party on trans.): |
|||
if 'REMI' in subfields: |
|||
transaction.message = ( |
|||
'/'.join(x for x in subfields['REMI'] if x)) |
|||
# Get transaction reference subfield (might vary): |
|||
if transaction.eref in subfields: |
|||
transaction.eref = ''.join( |
|||
subfields[transaction.eref]) |
|||
|
|||
|
|||
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' |
|||
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 = [] |
|||
|
|||
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 parse(self, data): |
|||
"""Parse mt940 bank statement file contents.""" |
|||
self.is_mt940(data) |
|||
iterator = data.replace('\r\n', '\n').split('\n').__iter__() |
|||
line = None |
|||
record_line = '' |
|||
try: |
|||
while True: |
|||
if not self.current_statement: |
|||
self.handle_header(line, iterator) |
|||
line = iterator.next() |
|||
if not self.is_tag(line) and not self.is_footer(line): |
|||
record_line += 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.statements |
|||
|
|||
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): |
|||
"""skip header lines, create current statement""" |
|||
for dummy_i in range(self.header_lines): |
|||
iterator.next() |
|||
self.current_statement = BankStatement() |
|||
|
|||
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): |
|||
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.current_statement.local_account = 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. |
|||
stmt = self.current_statement |
|||
if not stmt.local_currency: |
|||
stmt.local_currency = data[7:10] |
|||
stmt.start_balance = str2amount(data[0], data[10:]) |
|||
|
|||
def handle_tag_61(self, data): |
|||
"""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 |
|||
|
|||
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. |
|||
""" |
|||
stmt = self.current_statement |
|||
stmt.end_balance = str2amount(data[0], data[10:]) |
|||
stmt.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. |
|||
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'), |
|||
) |
|||
|
|||
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 |
@ -0,0 +1,51 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:alt: License: AGPL-3 |
|||
|
|||
Import MT940 IBAN ING Bank Statements |
|||
===================================== |
|||
|
|||
This module allows you to import the MT940 IBAN files from the Dutch ING bank |
|||
in Odoo as bank statements. |
|||
The specifications are published at: |
|||
https://www.ing.nl/media/ING_ming_mt940s_24_juli_tcm162-46356.pdf |
|||
and were last updated august 2014. |
|||
|
|||
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 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**>`_. |
|||
|
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Stefan Rijnhart <srijnhart@therp.nl> |
|||
* Ronald Portier <rportier@therp.nl> |
|||
|
|||
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 http://odoo-community.org. |
|||
This module should make it easy to migrate bank statement import |
|||
modules written for earlies versions of Odoo/OpenERP. |
@ -0,0 +1 @@ |
|||
from . import account_bank_statement_import |
@ -0,0 +1,34 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# 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 IBAN ING Format Bank Statements Import', |
|||
'version': '0.3', |
|||
'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_mt940_base' |
|||
], |
|||
'demo': [ |
|||
'demo/demo_data.xml', |
|||
], |
|||
'installable': True |
|||
} |
@ -0,0 +1,44 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Parse a MT940 ING file.""" |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
import logging |
|||
from openerp import models |
|||
from .mt940 import MT940Parser as Parser |
|||
|
|||
|
|||
_logger = logging.getLogger(__name__) |
|||
|
|||
|
|||
class AccountBankStatementImport(models.TransientModel): |
|||
"""Add parsing of mt940 files to bank statement import.""" |
|||
_inherit = 'account.bank.statement.import' |
|||
|
|||
def _parse_file(self, cr, uid, data_file, context=None): |
|||
"""Parse a MT940 IBAN ING file.""" |
|||
parser = Parser() |
|||
try: |
|||
_logger.debug("Try parsing with MT940 IBAN ING.") |
|||
return parser.parse(data_file) |
|||
except ValueError: |
|||
# Returning super will call next candidate: |
|||
_logger.debug("Statement file was not a MT940 IBAN ING file.", |
|||
exc_info=True) |
|||
return super(AccountBankStatementImport, self)._parse_file( |
|||
cr, uid, data_file, context=context) |
@ -0,0 +1,26 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data> |
|||
|
|||
<record id="mt940_ing_bank_journal" model="account.journal"> |
|||
<field name="name">Bank Journal - (test mt940 ING)</field> |
|||
<field name="code">TBNKMT940ING</field> |
|||
<field name="type">bank</field> |
|||
<field name="sequence_id" ref="account.sequence_bank_journal"/> |
|||
<field name="default_debit_account_id" ref="account.bnk"/> |
|||
<field name="default_credit_account_id" ref="account.bnk"/> |
|||
<field name="user_id" ref="base.user_root"/> |
|||
</record> |
|||
|
|||
<record id="mt940_ing_company_bank" model="res.partner.bank"> |
|||
<field name="owner_name">Your Company</field> |
|||
<field name="acc_number">NL77INGB0574908765</field> |
|||
<field name="partner_id" ref="base.partner_root"></field> |
|||
<field name="company_id" ref="base.main_company"></field> |
|||
<field name="journal_id" ref="mt940_ing_bank_journal"></field> |
|||
<field name="state">bank</field> |
|||
<field name="bank" ref="base.res_bank_1"/> |
|||
</record> |
|||
</data> |
|||
|
|||
</openerp> |
@ -0,0 +1,66 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Implement BankStatementParser for MT940 IBAN ING files.""" |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
import re |
|||
from openerp.addons.account_bank_statement_import_mt940_base.mt940 import ( |
|||
MT940, str2amount, get_subfields, handle_common_subfields) |
|||
|
|||
|
|||
class MT940Parser(MT940): |
|||
"""Parser for ing MT940 bank statement import files.""" |
|||
|
|||
tag_61_regex = re.compile( |
|||
r'^(?P<date>\d{6})(?P<line_date>\d{0,4})' |
|||
r'(?P<sign>[CD])(?P<amount>\d+,\d{2})N(?P<type>.{3})' |
|||
r'(?P<reference>\w{1,50})' |
|||
) |
|||
|
|||
def __init__(self): |
|||
"""Initialize parser - override at least header_regex.""" |
|||
super(MT940Parser, self).__init__() |
|||
self.mt940_type = 'ING' |
|||
|
|||
def handle_tag_61(self, data): |
|||
"""get transaction values""" |
|||
super(MT940Parser, self).handle_tag_61(data) |
|||
re_61 = self.tag_61_regex.match(data) |
|||
if not re_61: |
|||
raise ValueError("Cannot parse %s" % data) |
|||
parsed_data = re_61.groupdict() |
|||
self.current_transaction.transferred_amount = ( |
|||
str2amount(parsed_data['sign'], parsed_data['amount'])) |
|||
self.current_transaction.eref = parsed_data['reference'] |
|||
|
|||
def handle_tag_86(self, data): |
|||
"""Parse 86 tag containing reference data.""" |
|||
if not self.current_transaction: |
|||
return |
|||
codewords = ['RTRN', 'BENM', 'ORDP', 'CSID', 'BUSP', 'MARF', 'EREF', |
|||
'PREF', 'REMI', 'ID', 'PURP', 'ULTB', 'ULTD', |
|||
'CREF', 'IREF', 'CNTP', 'ULTC', 'EXCH', 'CHGS'] |
|||
subfields = get_subfields(data, codewords) |
|||
transaction = self.current_transaction |
|||
# If we have no subfields, set message to whole of data passed: |
|||
if not subfields: |
|||
transaction.message = data |
|||
else: |
|||
handle_common_subfields(transaction, subfields) |
|||
# Prevent handling tag 86 later for non transaction details: |
|||
self.current_transaction = None |
@ -0,0 +1,61 @@ |
|||
0000 01INGBNL2AXXXX00001 |
|||
0000 01INGBNL2AXXXX00001 |
|||
940 00 |
|||
:20:P140120000000001 |
|||
:25:NL77INGB0574908765EUR |
|||
:28C:0000 |
|||
0 |
|||
:60F:C140119EUR662,23 |
|||
:61:1401200220C1,56NTRFEREF//00000000001 |
|||
005 |
|||
/TRCD/00100/ |
|||
:86:/EREF/EV12341REP1231456T1234//CNTP/NL32INGB0000012345/INGBNL2 |
|||
A/ING BANK NV INZAKE WEB///REMI/USTD//EV10001REP1000000T1000/ |
|||
:61:1401200220D1,57NTRFPREF//00000000001006 |
|||
/TRCD/00200/ |
|||
:86:/PREF/M000000003333333//REMI/USTD//TOTAAL 1 VZ/ |
|||
:61:1401200220C1,57NRTIEREF//00000000001007 |
|||
/TRCD/00190/ |
|||
:86:/RTRN/MS03//EREF/20120123456789//CNTP/NL32INGB0000012345/INGB |
|||
NL2A/J.Janssen///REMI/USTD//Factuurnr 123456 Klantnr 00123/ |
|||
:61:1401200220D1,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:1401200220C1,45NDDTPREF//00000000001008 |
|||
/TRCD/01000/ |
|||
:86:/PREF/M000000001111111/ |
|||
/CSID/ |
|||
NL32ZZZ999999991234 |
|||
/ |
|||
/REMI/USTD// |
|||
TOTAAL 1 POSTEN/ |
|||
:61:1401200220D12,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:1401200220C32,00NTRF9001123412341234//00000000001011 |
|||
/TRCD/00108/ |
|||
:86:/EREF/15814016000676480//CNTP/NL32INGB0000012345/INGBNL2A/J.J |
|||
anssen///REMI/STRD/CUR/9001123412341234/ |
|||
:61:1401200220D119,00NTRF1070123412341234//00000000001012 |
|||
/ |
|||
TRCD/00108/ |
|||
:86:/EREF/15614016000384600//CNTP/NL32INGB0000012345/INGBNL2A/ING |
|||
BANK NV///REMI/STRD/CUR/1070123412341234/ |
|||
:62F:C140120EUR564,35 |
|||
:64:C140120EUR564,35 |
|||
:65:C140121EUR564,35 |
|||
:65:C140124EUR564,35 |
|||
:86:/SUM/4/4/134,46/36,58/ |
|||
-XXX |
@ -0,0 +1,62 @@ |
|||
{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/ |
|||
- |
|||
} |
@ -0,0 +1,25 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
"""Test import of bank statement for MT940 ING.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 2015 Therp BV <http://therp.nl>. |
|||
# |
|||
# All other contributions are (C) by their respective contributors |
|||
# |
|||
# All Rights Reserved |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
from . import test_import_bank_statement |
@ -0,0 +1,53 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Run test to import MT940 IBAN ING import.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 2015 Therp BV <http://therp.nl>. |
|||
# |
|||
# All other contributions are (C) by their respective contributors |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
from openerp.addons.account_bank_statement_import.tests import ( |
|||
TestStatementFile) |
|||
|
|||
|
|||
class TestImport(TestStatementFile): |
|||
"""Run test to import MT940 ING import.""" |
|||
|
|||
def test_old_statement_import(self): |
|||
"""Test correct creation of single statement from old format.""" |
|||
self._test_statement_import( |
|||
'account_bank_statement_import_mt940_nl_ing', 'test-ing-old.940', |
|||
'NL77INGB0574908765-2014-01-20', |
|||
start_balance=662.23, end_balance=564.35 |
|||
) |
|||
|
|||
def test_statement_import(self): |
|||
"""Test correct creation of single statement.""" |
|||
transactions = [ |
|||
{ |
|||
'remote_account': 'NL32INGB0000012345', |
|||
'transferred_amount': 1.56, |
|||
'value_date': '2014-02-20', |
|||
'ref': 'EV12341REP1231456T1234', |
|||
}, |
|||
] |
|||
self._test_statement_import( |
|||
'account_bank_statement_import_mt940_nl_ing', 'test-ing.940', |
|||
'NL77INGB0574908765-2014-02-20', |
|||
start_balance=662.23, end_balance=564.35, |
|||
transactions=transactions |
|||
) |
@ -0,0 +1,47 @@ |
|||
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg |
|||
:alt: License: AGPL-3 |
|||
|
|||
Bank Statement NL Rabobank MT940 |
|||
================================ |
|||
|
|||
This addon imports the structured MT940 format as offered by |
|||
the dutch Rabobank. |
|||
|
|||
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 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**>`_. |
|||
|
|||
|
|||
Credits |
|||
======= |
|||
|
|||
Contributors |
|||
------------ |
|||
|
|||
* Ronald Portier <rportier@therp.nl> |
|||
|
|||
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 http://odoo-community.org. |
|||
This module should make it easy to migrate bank statement import |
|||
modules written for earlies versions of Odoo/OpenERP. |
@ -0,0 +1,20 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
from . import account_bank_statement_import |
@ -0,0 +1,32 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
{ |
|||
'name': 'MT940 import for dutch Rabobank', |
|||
'version': '1.1', |
|||
'author': 'Odoo Community Association (OCA), Therp BV', |
|||
'website': 'https://github.com/OCA/bank-statement-import', |
|||
'category': 'Banking addons', |
|||
'depends': [ |
|||
'account_bank_statement_import_mt940_base' |
|||
], |
|||
'auto_install': False, |
|||
'installable': True, |
|||
'application': False, |
|||
} |
@ -0,0 +1,46 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Parse a MT940 RABO file.""" |
|||
############################################################################## |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
import logging |
|||
|
|||
from openerp import models |
|||
from .mt940 import MT940Parser as Parser |
|||
|
|||
|
|||
_logger = logging.getLogger(__name__) |
|||
|
|||
|
|||
class AccountBankStatementImport(models.TransientModel): |
|||
"""Add parsing of RABO mt940 files to bank statement import.""" |
|||
_inherit = 'account.bank.statement.import' |
|||
|
|||
def _parse_file(self, cr, uid, data_file, context=None): |
|||
"""Parse a MT940 RABO file.""" |
|||
parser = Parser() |
|||
try: |
|||
_logger.debug("Try parsing with MT940 RABO.") |
|||
statements = parser.parse(data_file) |
|||
return statements |
|||
except ValueError: |
|||
# Returning super will call next candidate: |
|||
_logger.debug("Statement file was not a MT940 RABO file.", |
|||
exc_info=True) |
|||
return super(AccountBankStatementImport, self)._parse_file( |
|||
cr, uid, data_file, context=context) |
@ -0,0 +1,86 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Implement parser for MT940 files - Rabobank dialect.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 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/>. |
|||
# |
|||
############################################################################## |
|||
import re |
|||
from string import printable |
|||
from openerp.addons.account_bank_statement_import_mt940_base.mt940 import ( |
|||
MT940, str2amount, get_subfields, handle_common_subfields) |
|||
|
|||
|
|||
class MT940Parser(MT940): |
|||
"""Implement parser for MT940 files - Rabobank dialect.""" |
|||
|
|||
tag_61_regex = re.compile( |
|||
r'^(?P<date>\d{6})(?P<sign>[CD])(?P<amount>\d+,\d{2})N(?P<type>.{3})' |
|||
r'(?P<reference>MARF|EREF|PREF|NONREF)\s*' |
|||
r'\n?(?P<remote_account>\w{1,34})?' |
|||
) |
|||
|
|||
def __init__(self): |
|||
"""Initialize parser - override at least header_regex.""" |
|||
super(MT940Parser, self).__init__() |
|||
self.mt940_type = 'RABO' |
|||
self.header_lines = 1 # Number of lines to skip |
|||
# Do not user $ for end of string below: line contains much |
|||
# more data than just the first line. |
|||
self.header_regex = '^:940:' # Start of relevant data |
|||
|
|||
def parse(self, data): |
|||
"""Filter Unprintable characters from file data. |
|||
|
|||
The file contents of the Rabobank tend to contain unprintable |
|||
characters that prevent proper parsing. These will be removed. |
|||
""" |
|||
data = ''.join([x for x in data if x in printable]) |
|||
return super(MT940Parser, self).parse(data) |
|||
|
|||
def handle_tag_61(self, data): |
|||
"""Handle tag 61: transaction data.""" |
|||
super(MT940Parser, self).handle_tag_61(data) |
|||
parsed_data = self.tag_61_regex.match(data).groupdict() |
|||
self.current_transaction.transferred_amount = ( |
|||
str2amount(parsed_data['sign'], parsed_data['amount'])) |
|||
self.current_transaction.eref = parsed_data['reference'] |
|||
if parsed_data['remote_account']: |
|||
self.current_transaction.remote_account = ( |
|||
parsed_data['remote_account']) |
|||
|
|||
def handle_tag_86(self, data): |
|||
"""Handle tag 86: transaction details""" |
|||
if not self.current_transaction: |
|||
return |
|||
codewords = ['RTRN', 'BENM', 'ORDP', 'CSID', 'BUSP', 'MARF', 'EREF', |
|||
'PREF', 'REMI', 'ID', 'PURP', 'ULTB', 'ULTD', |
|||
'CREF', 'IREF', 'NAME', 'ADDR', 'ULTC', 'EXCH', 'CHGS'] |
|||
subfields = get_subfields(data, codewords) |
|||
transaction = self.current_transaction |
|||
# If we have no subfields, set message to whole of data passed: |
|||
if not subfields: |
|||
transaction.message = data |
|||
else: |
|||
handle_common_subfields(transaction, subfields) |
|||
# Use subfields for transaction details: |
|||
if 'NAME' in subfields: |
|||
transaction.remote_owner = ' '.join(subfields['NAME']) |
|||
if 'ADDR' in subfields: |
|||
# Do NOT join address fields, array is expected on other code! |
|||
transaction.remote_owner_address = subfields['ADDR'] |
|||
# Prevent handling tag 86 later for non transaction details: |
|||
self.current_transaction = None |
@ -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 |
@ -0,0 +1,23 @@ |
|||
# -*- encoding: utf-8 -*- |
|||
"""Test import of bank statement for MT940 ING.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 2015 Therp BV <http://therp.nl>. |
|||
# |
|||
# All other contributions are (C) by their respective contributors |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
from . import test_import_bank_statement |
@ -0,0 +1,46 @@ |
|||
# -*- coding: utf-8 -*- |
|||
"""Run test to import MT940 IBAN RABO import.""" |
|||
############################################################################## |
|||
# |
|||
# Copyright (C) 2015 Therp BV <http://therp.nl>. |
|||
# All other contributions are (C) by their respective contributors |
|||
# |
|||
# 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/>. |
|||
# |
|||
############################################################################## |
|||
from openerp.addons.account_bank_statement_import.tests import ( |
|||
TestStatementFile) |
|||
|
|||
|
|||
class TestImport(TestStatementFile): |
|||
"""Run test to import MT940 RABO import.""" |
|||
|
|||
def test_statement_import(self): |
|||
"""Test correct creation of single statement.""" |
|||
transactions = [ |
|||
{ |
|||
'remote_account': 'NL66RABO0160878799', |
|||
'transferred_amount': 400.00, |
|||
'value_date': '2014-01-02', |
|||
'ref': 'NONREF', |
|||
}, |
|||
] |
|||
# statement name is account number + '-' + date of last 62F line: |
|||
self._test_statement_import( |
|||
'account_bank_statement_import_mt940_nl_rabo', 'test-rabo.swi', |
|||
'NL34RABO0142623393-2014-01-07', |
|||
local_account='NL34RABO0142623393', |
|||
start_balance=4433.52, end_balance=4798.91, |
|||
transactions=transactions |
|||
) |
@ -0,0 +1,27 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<openerp> |
|||
<data> |
|||
|
|||
<record id="ofx_bank_journal" model="account.journal"> |
|||
<field name="name">Bank Journal - (test ofx)</field> |
|||
<field name="code">TBNKOFX</field> |
|||
<field name="type">bank</field> |
|||
<field name="sequence_id" ref="account.sequence_bank_journal"/> |
|||
<field name="default_debit_account_id" ref="account.usd_bnk"/> |
|||
<field name="default_credit_account_id" ref="account.usd_bnk"/> |
|||
<field name="user_id" ref="base.user_root"/> |
|||
<field name="currency" ref="base.USD"/> |
|||
</record> |
|||
|
|||
<record id="ofx_company_bank" model="res.partner.bank"> |
|||
<field name="owner_name">Your Company</field> |
|||
<field name="acc_number">123456</field> |
|||
<field name="partner_id" ref="base.partner_root"></field> |
|||
<field name="company_id" ref="base.main_company"></field> |
|||
<field name="journal_id" ref="ofx_bank_journal"></field> |
|||
<field name="state">bank</field> |
|||
<field name="bank" ref="base.res_bank_1"/> |
|||
</record> |
|||
</data> |
|||
|
|||
</openerp> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue