Browse Source

[RFR] Base import parsers on new api and master.

pull/15/head
Ronald Portier (Therp BV) 9 years ago
parent
commit
af10ace22d
  1. 2
      account_bank_statement_import/account_bank_statement_import.py
  2. 10
      bank_statement_parse/README.rst
  3. 31
      bank_statement_parse/__init__.py
  4. 40
      bank_statement_parse/__openerp__.py
  5. 91
      bank_statement_parse/i18n/bank_statement_parse.pot
  6. 92
      bank_statement_parse/i18n/nl.po
  7. 30
      bank_statement_parse/model/__init__.py
  8. 105
      bank_statement_parse/model/account_bank_statement_import.py
  9. 71
      bank_statement_parse/parserlib.py
  10. 3
      bank_statement_parse_camt/README.rst
  11. 1
      bank_statement_parse_camt/__init__.py
  12. 34
      bank_statement_parse_camt/__openerp__.py
  13. 51
      bank_statement_parse_camt/account_bank_statement_import.py
  14. 245
      bank_statement_parse_camt/camt.py
  15. 26
      bank_statement_parse_camt/demo/demo_data.xml
  16. 241
      bank_statement_parse_camt/test_files/test-camt053.xml
  17. 25
      bank_statement_parse_camt/tests/__init__.py
  18. 68
      bank_statement_parse_camt/tests/test_import_bank_statement.py

2
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.

10
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.

31
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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import model
from . import parserlib
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

40
bank_statement_parse/__openerp__.py

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (C) 2011-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/>.
#
##############################################################################
{
'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,
}

91
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 ""

92
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 <erwin@bas-solutions.nl>\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"

30
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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import account_bank_statement_import
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

105
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 <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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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:

71
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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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:

3
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.

1
bank_statement_parse_camt/__init__.py

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

34
bank_statement_parse_camt/__openerp__.py

@ -0,0 +1,34 @@
##############################################################################
#
# Copyright (C) 2013 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'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,
}

51
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 (<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 <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.")
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:

245
bank_statement_parse_camt/camt.py

@ -0,0 +1,245 @@
# -*- coding: utf-8 -*-
"""Class to parse camt files."""
##############################################################################
#
# Copyright (C) 2013-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 <http://www.gnu.org/licenses/>.
#
##############################################################################
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:

26
bank_statement_parse_camt/demo/demo_data.xml

@ -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">iban</field>
<field name="bank" ref="base.res_bank_1"/>
</record>
</data>
</openerp>

241
bank_statement_parse_camt/test_files/test-camt053.xml

@ -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>

25
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 <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

68
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 <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 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:
Loading…
Cancel
Save