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
-
250account_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 -*- |
# -*- encoding: utf-8 -*- |
||||
|
"""Define tests to be run.""" |
||||
from . import test_res_partner_bank |
from . import test_res_partner_bank |
||||
from . import test_import_bank_statement |
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