diff --git a/account_bank_statement_import/account_bank_statement_import.py b/account_bank_statement_import/account_bank_statement_import.py index 6ed3b32..4344f3b 100644 --- a/account_bank_statement_import/account_bank_statement_import.py +++ b/account_bank_statement_import/account_bank_statement_import.py @@ -101,7 +101,7 @@ class AccountBankStatementImport(models.TransientModel): @api.model def _parse_file(self, data_file): """ Each module adding a file support must extends this method. It - rocesses the file if it can, returns super otherwise, resulting in a + processes the file if it can, returns super otherwise, resulting in a chain of responsability. This method parses the given file and returns the data required by the bank statement import process, as specified below. diff --git a/bank_statement_parse/README.rst b/bank_statement_parse/README.rst new file mode 100644 index 0000000..4f2ea38 --- /dev/null +++ b/bank_statement_parse/README.rst @@ -0,0 +1,10 @@ + +This module should make it easy to migrate bank statement import +modules written for earlies versions of Odoo/OpenERP. + +At the same time the utility classes can be used as a reference for the +information that can be present in bank statements and/or bank transactions. + +RECOMMENDATION + +Install the web_sheet_full_width to have a good view on bank statement files. diff --git a/bank_statement_parse/__init__.py b/bank_statement_parse/__init__.py new file mode 100644 index 0000000..4ee600e --- /dev/null +++ b/bank_statement_parse/__init__.py @@ -0,0 +1,31 @@ +# -*- encoding: utf-8 -*- +"""Classes and models to parse bank statements and import them into Odoo.""" +############################################################################## +# +# Copyright (C) 2014 Therp BV - http://therp.nl. +# All Rights Reserved +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract EduSense BV +# +# 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 . +# +############################################################################## +from . import model +from . import parserlib + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bank_statement_parse/__openerp__.py b/bank_statement_parse/__openerp__.py new file mode 100644 index 0000000..8fd7487 --- /dev/null +++ b/bank_statement_parse/__openerp__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2011-2015 Therp BV . +# +# 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 . +# +############################################################################## +{ + 'name': 'Bank Statement Import Parse', + 'version': '0.5', + 'license': 'AGPL-3', + 'author': 'Banking addons community', + 'website': 'https://github.com/OCA/bank-statement-import', + 'category': 'Banking addons', + 'depends': [ + 'account_bank_statement_import', + ], + 'data': [ + ], + 'js': [ + ], + 'installable': True, + 'auto_install': False, +} diff --git a/bank_statement_parse/i18n/bank_statement_parse.pot b/bank_statement_parse/i18n/bank_statement_parse.pot new file mode 100644 index 0000000..18a4b38 --- /dev/null +++ b/bank_statement_parse/i18n/bank_statement_parse.pot @@ -0,0 +1,91 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bank_statement_parse +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-01-02 10:43+0000\n" +"PO-Revision-Date: 2015-01-02 10:43+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: bank_statement_parse +#: model:ir.actions.act_window,name:bank_statement_parse.action_res_partner_banks +#: model:ir.ui.menu,name:bank_statement_parse.menu_res_partner_banks +msgid "Bank Accounts" +msgstr "" + +#. module: bank_statement_parse +#: model:ir.ui.menu,name:bank_statement_parse.menu_finance_banking_settings +msgid "Banking" +msgstr "" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,company_id:0 +msgid "Company" +msgstr "" + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Error" +msgstr "" + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Finished" +msgstr "" + +#. module: bank_statement_parse +#: model:ir.model,name:bank_statement_parse.model_account_bank_statement_import +msgid "Import Bank Statement" +msgstr "" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,date:0 +msgid "Import Date" +msgstr "" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,log:0 +msgid "Import Log" +msgstr "" + +#. module: bank_statement_parse +#: model:ir.actions.act_window,name:bank_statement_parse.action_account_bank_statement_import_tree +#: model:ir.ui.menu,name:bank_statement_parse.menu_account_bank_statement_import_tree +msgid "Imported Bank Statements Files" +msgstr "" + +#. module: bank_statement_parse +#: code:addons/bank_statement_parse/parserlib/bank_transaction.py:235 +#, python-format +msgid "Invalid value for transfer_type" +msgstr "" + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Review" +msgstr "" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,state:0 +msgid "State" +msgstr "" + +#. module: bank_statement_parse +#: code:addons/bank_statement_parse/parserlib/bank_statement_parser.py:121 +#, python-format +msgid "This is a stub. Please implement your own." +msgstr "" + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Unfinished" +msgstr "" + diff --git a/bank_statement_parse/i18n/nl.po b/bank_statement_parse/i18n/nl.po new file mode 100644 index 0000000..6896a01 --- /dev/null +++ b/bank_statement_parse/i18n/nl.po @@ -0,0 +1,92 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * bank_statement_parse +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2015-01-02 10:43+0000\n" +"PO-Revision-Date: 2015-01-02 12:09+0100\n" +"Last-Translator: BAS Solutions \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: nl\n" +"X-Generator: Poedit 1.7.1\n" + +#. module: bank_statement_parse +#: model:ir.actions.act_window,name:bank_statement_parse.action_res_partner_banks +#: model:ir.ui.menu,name:bank_statement_parse.menu_res_partner_banks +msgid "Bank Accounts" +msgstr "Bankrekeningen" + +#. module: bank_statement_parse +#: model:ir.ui.menu,name:bank_statement_parse.menu_finance_banking_settings +msgid "Banking" +msgstr "Bankieren" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,company_id:0 +msgid "Company" +msgstr "Bedrijf" + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Error" +msgstr "Fout" + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Finished" +msgstr "Gereed" + +#. module: bank_statement_parse +#: model:ir.model,name:bank_statement_parse.model_account_bank_statement_import +msgid "Import Bank Statement" +msgstr "Importeer bankafschrift" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,date:0 +msgid "Import Date" +msgstr "Importeerdatum" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,log:0 +msgid "Import Log" +msgstr "Importeerlog" + +#. module: bank_statement_parse +#: model:ir.actions.act_window,name:bank_statement_parse.action_account_bank_statement_import_tree +#: model:ir.ui.menu,name:bank_statement_parse.menu_account_bank_statement_import_tree +msgid "Imported Bank Statements Files" +msgstr "Geïmporteerde bankafschriften" + +#. module: bank_statement_parse +#: code:addons/bank_statement_parse/parserlib/bank_transaction.py:235 +#, python-format +msgid "Invalid value for transfer_type" +msgstr "Ongeldige waarde voor transfer_type" + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Review" +msgstr "Controleren" + +#. module: bank_statement_parse +#: field:account.bank.statement.import,state:0 +msgid "State" +msgstr "Status" + +#. module: bank_statement_parse +#: code:addons/bank_statement_parse/parserlib/bank_statement_parser.py:121 +#, python-format +msgid "This is a stub. Please implement your own." +msgstr "Dit is een stub. Importeer uw eigen." + +#. module: bank_statement_parse +#: selection:account.bank.statement.import,state:0 +msgid "Unfinished" +msgstr "Niet gereed" diff --git a/bank_statement_parse/model/__init__.py b/bank_statement_parse/model/__init__.py new file mode 100644 index 0000000..fc92b27 --- /dev/null +++ b/bank_statement_parse/model/__init__.py @@ -0,0 +1,30 @@ +# -*- encoding: utf-8 -*- +"""Import python modules extending orm models.""" +############################################################################## +# +# Copyright (C) 2014 Therp BV - http://therp.nl. +# All Rights Reserved +# +# WARNING: This program as such is intended to be used by professional +# programmers who take the whole responsability of assessing all potential +# consequences resulting from its eventual inadequacies and bugs +# End users who are looking for a ready-to-use solution with commercial +# garantees and support are strongly adviced to contract EduSense BV +# +# 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 . +# +############################################################################## +from . import account_bank_statement_import + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bank_statement_parse/model/account_bank_statement_import.py b/bank_statement_parse/model/account_bank_statement_import.py new file mode 100644 index 0000000..a5c851e --- /dev/null +++ b/bank_statement_parse/model/account_bank_statement_import.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +"""Extend account.bank.statement.import.""" +############################################################################## +# +# Copyright (C) 2015 Therp BV . +# +# 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 . +# +############################################################################## +from openerp import models +from openerp.tools.translate import _ + + +class AccountBankStatementImport(models.TransientModel): + """Import Bank Statements File.""" + _inherit = 'account.bank.statement.import' + _description = __doc__ + + def convert_transaction( + self, cr, uid, transaction, context=None): + """Convert transaction object to values for create.""" + partner_vals = { + 'name': transaction.remote_owner, + } + bank_vals = { + 'acc_number': transaction.remote_account, + 'owner_name': transaction.remote_owner, + 'street': transaction.remote_owner_address, + 'city': transaction.remote_owner_city, + 'zip': transaction.remote_owner_postalcode, + 'country_code': transaction.remote_owner_country_code, + 'bank_bic': transaction.remote_bank_bic, + } + bank_account_id, partner_id = self.detect_partner_and_bank( + cr, uid, transaction_vals=None, partner_vals=partner_vals, + bank_vals=bank_vals, context=context + ) + vals_line = { + 'date': transaction.value_date, + 'name': ( + transaction.message or transaction.eref or + transaction.remote_owner or ''), # name is required + 'ref': transaction.eref, + 'amount': transaction.transferred_amount, + 'partner_name': transaction.remote_owner, + 'acc_number': transaction.remote_account, + 'partner_id': partner_id, + 'bank_account_id': bank_account_id, + 'unique_import_id': transaction.transaction_id, + } + return vals_line + + def convert_statements( + self, cr, uid, os_statements, context=None): + """Taking lots of code from the former import wizard, convert array + of BankStatement objects to values that can be used in create of + bank.statement model, including bank.statement.line tuple.""" + # os_ = old style + # ns_ = new style + ns_statements = [] + for statement in os_statements: + # Set statement_data + ns_statement = dict( + acc_number=statement.local_account, + name=statement.statement_id, + date=statement.date.strftime('%Y-%m-%d'), + balance_start=statement.start_balance, + balance_end_real=statement.end_balance, + balance_end=statement.end_balance, + state='draft', + user_id=uid, + ) + ns_transactions = [] + subno = 0 + for transaction in statement.transactions: + subno += 1 + if not transaction.transaction_id: + transaction.transaction_id = ( + statement.statement_id + str(subno).zfill(4)) + ns_transactions.append( + self.convert_transaction( + cr, uid, transaction, context=context)) + ns_statement['transactions'] = ns_transactions + ns_statements.append(ns_statement) + return ( + # For the moment all statements must have same currency and amount + ns_statements[0].local_currency, + ns_statements[0].local_account, + ns_statements, + ) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bank_statement_parse/parserlib.py b/bank_statement_parse/parserlib.py new file mode 100644 index 0000000..ebef108 --- /dev/null +++ b/bank_statement_parse/parserlib.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- +"""Classes and definitions used in parsing bank statements.""" +############################################################################## +# +# Copyright (C) 2015 Therp BV - http://therp.nl. +# 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 . +# +############################################################################## + + +class BankStatement(object): + """A bank statement groups data about several bank transactions.""" + + def __init__(self): + self.statement_id = '' + self.local_account = '' + self.local_currency = '' + self.start_balance = 0.0 + self.end_balance = 0.0 + self.date = '' + self.transactions = [] + + +class BankTransaction(object): + """Single transaction that is part of a bank statement.""" + + def __init__(self): + """Define and initialize attributes. + + Does not include attributes that belong to statement. + """ + self.transaction_id = False # Message id + self.transfer_type = False # Action type that initiated this message + self.eref = False # end to end reference for transactions + 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.message = False # message from the remote party + 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 + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bank_statement_parse_camt/README.rst b/bank_statement_parse_camt/README.rst new file mode 100644 index 0000000..28cf644 --- /dev/null +++ b/bank_statement_parse_camt/README.rst @@ -0,0 +1,3 @@ +Module to import SEPA CAMT.053 Format bank statement files. + +Based on the Banking addons framework. diff --git a/bank_statement_parse_camt/__init__.py b/bank_statement_parse_camt/__init__.py new file mode 100644 index 0000000..7dafcd1 --- /dev/null +++ b/bank_statement_parse_camt/__init__.py @@ -0,0 +1 @@ +from . import account_bank_statement_import diff --git a/bank_statement_parse_camt/__openerp__.py b/bank_statement_parse_camt/__openerp__.py new file mode 100644 index 0000000..5e522c0 --- /dev/null +++ b/bank_statement_parse_camt/__openerp__.py @@ -0,0 +1,34 @@ +############################################################################## +# +# Copyright (C) 2013 Therp BV () +# 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 . +# +############################################################################## +{ + 'name': 'CAMT Format Bank Statements Import', + 'version': '0.3', + 'license': 'AGPL-3', + 'author': 'Therp BV', + 'website': 'https://github.com/OCA/banking', + 'category': 'Banking addons', + 'depends': [ + 'bank_statement_parse' + ], + 'demo': [ + 'demo/demo_data.xml', + ], + 'installable': True, +} diff --git a/bank_statement_parse_camt/account_bank_statement_import.py b/bank_statement_parse_camt/account_bank_statement_import.py new file mode 100644 index 0000000..052edc9 --- /dev/null +++ b/bank_statement_parse_camt/account_bank_statement_import.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +"""Add process_camt method to account.bank.statement.import.""" +############################################################################## +# +# Copyright (C) 2013 Therp BV () +# 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 . +# +############################################################################## +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.") + os_statements = 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.") + return super(AccountBankStatementImport, self)._parse_file( + cr, uid, data_file, context=context) + # Succesfull parse, convert to format expected + return self.convert_statements( + cr, uid, os_statements, context=context) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bank_statement_parse_camt/camt.py b/bank_statement_parse_camt/camt.py new file mode 100644 index 0000000..7e0eb23 --- /dev/null +++ b/bank_statement_parse_camt/camt.py @@ -0,0 +1,245 @@ +# -*- coding: utf-8 -*- +"""Class to parse camt files.""" +############################################################################## +# +# Copyright (C) 2013-2015 Therp BV +# 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 . +# +############################################################################## +import re +from datetime import datetime +from lxml import etree +from openerp.addons.bank_statement_parse.parserlib import ( + BankStatement, + BankTransaction +) + + +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): + """Parse transaction (entry) node.""" + transaction = BankTransaction() + 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 = self.parse_transaction(ns, entry_node) + statement.transactions.append(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 + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/bank_statement_parse_camt/demo/demo_data.xml b/bank_statement_parse_camt/demo/demo_data.xml new file mode 100644 index 0000000..eb0a8bc --- /dev/null +++ b/bank_statement_parse_camt/demo/demo_data.xml @@ -0,0 +1,26 @@ + + + + + + Bank Journal - (test camt) + TBNKCAMT + bank + + + + + + + + Your Company + NL77ABNA0574908765 + + + + iban + + + + + diff --git a/bank_statement_parse_camt/test_files/test-camt053.xml b/bank_statement_parse_camt/test_files/test-camt053.xml new file mode 100644 index 0000000..1d74d81 --- /dev/null +++ b/bank_statement_parse_camt/test_files/test-camt053.xml @@ -0,0 +1,241 @@ + + + + TESTBANK/NL/1420561226673 + 2013-01-06T16:20:26.673Z + + + 1234Test/1 + 2 + 2013-01-06T16:20:26.673Z + + 2013-01-05T00:00:00.000Z + 2013-01-05T23:59:59.999Z + + + + NL77ABNA0574908765 + + Example company + + + ABNANL2A + + + + + + + OPBD + + + 15568.27 + CRDT +
+
2013-01-05
+ +
+ + + + CLBD + + + 15121.12 + CRDT +
+
2013-01-05
+ +
+ + 754.25 + DBIT + BOOK + +
2013-01-05
+
+ +
2013-01-05
+
+ + + PMNT + + RDDT + ESDD + + + + EI + + + + + + INNDNL2U20141231000142300002844 + 435005714488-ABNO33052620 + 1880000341866 + + + + 754.25 + + + + + INSURANCE COMPANY TESTX + + TEST STREET 20 + 1234 AB TESTCITY + NL + + + + + NL46ABNA0499998748 + + + + + + + ABNANL2A + + + + + Insurance policy 857239PERIOD 01.01.2013 - 31.12.2013 + + MKB Insurance 859239PERIOD 01.01.2013 - 31.12.2013 + + +
+ + 594.05 + DBIT + true + BOOK + +
2013-01-05
+
+ +
2013-01-05
+
+ + + PMNT + + IDDT + UPDD + + + + EIST + + + + + + TESTBANK/NL/20141229/01206408 + TESTBANK/NL/20141229/01206408 + NL22ZZZ524885430000-C0125.1 + + + + 564.05 + + + + + Test Customer + + NL + + + + + NL46ABNA0499998748 + + + + + + + ABNANL2A + + + + + Direct Debit S14 0410 + + + + AC06 + + + Direct debit S14 0410 AC07 Rek.nummer blokkade TESTBANK/NL/20141229/01206408 + + +
+ + 1405.31 + CRDT + BOOK + +
2013-01-05
+
+ +
2013-01-05
+
+ + + PMNT + + RCDT + ESCT + + + + ET + + + + + + INNDNL2U20130105000217200000708 + 115 + + + + 1405.31 + + + + + 3rd party Media + + SOMESTREET 570-A + 1276 ML HOUSCITY + NL + + + + + NL69ABNA0522123643 + + + + + + + ABNANL2A + + + + #RD PARTY MEDIA CUSNO 90782 4210773 + + +
+
+
+
diff --git a/bank_statement_parse_camt/tests/__init__.py b/bank_statement_parse_camt/tests/__init__.py new file mode 100644 index 0000000..9375f2e --- /dev/null +++ b/bank_statement_parse_camt/tests/__init__.py @@ -0,0 +1,25 @@ +# -*- encoding: utf-8 -*- +"""Test import of bank statement for camt.053.""" +############################################################################## +# +# Copyright (C) 2015 Therp BV . +# +# 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 . +# +############################################################################## +from . import test_import_bank_statement diff --git a/bank_statement_parse_camt/tests/test_import_bank_statement.py b/bank_statement_parse_camt/tests/test_import_bank_statement.py new file mode 100644 index 0000000..7da6218 --- /dev/null +++ b/bank_statement_parse_camt/tests/test_import_bank_statement.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +"""Run test to import camt.053 import.""" +############################################################################## +# +# Copyright (C) 2015 Therp BV . +# +# 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 . +# +############################################################################## +from openerp.tests.common import TransactionCase +from openerp.modules.module import get_module_resource + + +class TestStatementFile(TransactionCase): + """Run test to import camt.053 import.""" + + def test_statement_import(self): + """Test correct creation of single statement.""" + import_model = self.registry('account.bank.statement.import') + statement_model = self.registry('account.bank.statement') + cr, uid = self.cr, self.uid + statement_path = get_module_resource( + 'bank_statement_parse_camt', + 'test_files', + 'test-camt053.xml' + ) + statement_file = open( + statement_path, 'rb').read().encode('base64') + bank_statement_id = import_model.create( + cr, uid, + dict( + data_file=statement_file, + ) + ) + import_model.import_file(cr, uid, [bank_statement_id]) + ids = statement_model.search( + cr, uid, [('name', '=', '1234Test/1')]) + self.assertTrue(ids, 'Statement not found after parse.') + statement_id = ids[0] + statement_obj = statement_model.browse( + cr, uid, statement_id) + self.assertTrue( + abs(statement_obj.balance_start - 15568.27) < 0.00001, + 'Start balance %f not equal to 15568.27' % + statement_obj.balance_start + ) + self.assertTrue( + abs(statement_obj.balance_end_real - 15121.12) < 0.00001, + 'Real end balance %f not equal to 15121.12' % + statement_obj.balance_end_real + ) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: