mirror of
https://github.com/OCA/bank-statement-import.git
synced 2025-01-20 12:37:43 +02:00
Merge pull request #15 from NL66278/8.0_import_parsers_new_api_therp
8.0 import parsers new api therp
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
{
|
||||
'name': 'Account Bank Statement Import',
|
||||
'category': 'Accounting & Finance',
|
||||
'category': 'Banking addons',
|
||||
'version': '1.0',
|
||||
'author': 'OpenERP SA,'
|
||||
'Odoo Community Association (OCA)',
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Framework for importing bank statement files."""
|
||||
import logging
|
||||
import base64
|
||||
|
||||
from openerp import api, models, fields
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import Warning
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountBankStatementLine(models.Model):
|
||||
"""Extend model account.bank.statement.line."""
|
||||
_inherit = "account.bank.statement.line"
|
||||
|
||||
# Ensure transactions can be imported only once (if the import format
|
||||
@@ -24,6 +26,7 @@ class AccountBankStatementLine(models.Model):
|
||||
|
||||
|
||||
class AccountBankStatementImport(models.TransientModel):
|
||||
"""Extend model account.bank.statement."""
|
||||
_name = 'account.bank.statement.import'
|
||||
_description = 'Import Bank Statement'
|
||||
|
||||
@@ -50,7 +53,7 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
@api.multi
|
||||
def import_file(self):
|
||||
""" Process the file chosen in the wizard, create bank statement(s) and
|
||||
go to reconciliation. """
|
||||
go to reconciliation."""
|
||||
self.ensure_one()
|
||||
data_file = base64.b64decode(self.data_file)
|
||||
statement_ids, notifications = self.with_context(
|
||||
@@ -70,15 +73,43 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
|
||||
@api.model
|
||||
def _import_file(self, data_file):
|
||||
""" Create bank statement(s) from file
|
||||
"""
|
||||
""" Create bank statement(s) from file."""
|
||||
# The appropriate implementation module returns the required data
|
||||
currency_code, account_number, stmts_vals = self._parse_file(data_file)
|
||||
# Check raw data
|
||||
self._check_parsed_data(stmts_vals)
|
||||
statement_ids = []
|
||||
notifications = []
|
||||
parse_result = self._parse_file(data_file)
|
||||
# Check for old version result, with separate currency and account
|
||||
if isinstance(parse_result, tuple) and len(parse_result) == 3:
|
||||
(currency_code, account_number, statements) = parse_result
|
||||
for stmt_vals in statements:
|
||||
stmt_vals['currency_code'] = currency_code
|
||||
stmt_vals['account_number'] = account_number
|
||||
else:
|
||||
statements = parse_result
|
||||
# Check raw data:
|
||||
self._check_parsed_data(statements)
|
||||
# Import all statements:
|
||||
for stmt_vals in statements:
|
||||
(statement_id, new_notifications) = (
|
||||
self._import_statement(stmt_vals))
|
||||
if statement_id:
|
||||
statement_ids.append(statement_id)
|
||||
notifications.append(new_notifications)
|
||||
if len(statement_ids) == 0:
|
||||
raise Warning(_('You have already imported that file.'))
|
||||
return statement_ids, notifications
|
||||
|
||||
@api.model
|
||||
def _import_statement(self, stmt_vals):
|
||||
"""Import a single bank-statement.
|
||||
|
||||
Return ids of created statements and notifications.
|
||||
"""
|
||||
currency_code = stmt_vals.pop('currency_code')
|
||||
account_number = stmt_vals.pop('account_number')
|
||||
# Try to find the bank account and currency in odoo
|
||||
currency_id, bank_account_id = self._find_additional_data(
|
||||
currency_code, account_number)
|
||||
currency_id = self._find_currency_id(currency_code)
|
||||
bank_account_id = self._find_bank_account_id(account_number)
|
||||
# Create the bank account if not already existing
|
||||
if not bank_account_id and account_number:
|
||||
journal_id = self.env.context.get('journal_id')
|
||||
@@ -90,87 +121,90 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
account_number, company_id=company_id,
|
||||
currency_id=currency_id).id
|
||||
# Find or create the bank journal
|
||||
journal_id = self._get_journal(
|
||||
currency_id, bank_account_id, account_number)
|
||||
journal_id = self._get_journal(currency_id, bank_account_id)
|
||||
# By now journal and account_number must be known
|
||||
if not journal_id:
|
||||
raise Warning(_('Can not determine journal for import.'))
|
||||
# Prepare statement data to be used for bank statements creation
|
||||
stmts_vals = self._complete_stmts_vals(
|
||||
stmts_vals, journal_id, account_number)
|
||||
# Create the bank statements
|
||||
return self._create_bank_statements(stmts_vals)
|
||||
stmt_vals = self._complete_statement(
|
||||
stmt_vals, journal_id, account_number)
|
||||
# Create the bank stmt_vals
|
||||
return self._create_bank_statement(stmt_vals)
|
||||
|
||||
@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.
|
||||
rtype: triplet (if a value can't be retrieved, use None)
|
||||
- currency code: string (e.g: 'EUR')
|
||||
The ISO 4217 currency code, case insensitive
|
||||
- account number: string (e.g: 'BE1234567890')
|
||||
The number of the bank account which the statement belongs
|
||||
to
|
||||
- bank statements data: list of dict containing (optional
|
||||
items marked by o) :
|
||||
- 'name': string (e.g: '000000123')
|
||||
- 'date': date (e.g: 2013-06-26)
|
||||
-o 'balance_start': float (e.g: 8368.56)
|
||||
-o 'balance_end_real': float (e.g: 8888.88)
|
||||
- 'transactions': list of dict containing :
|
||||
- 'name': string
|
||||
(e.g: 'KBC-INVESTERINGSKREDIET 787-5562831-01')
|
||||
- 'date': date
|
||||
- 'amount': float
|
||||
- 'unique_import_id': string
|
||||
-o 'account_number': string
|
||||
Will be used to find/create the res.partner.bank
|
||||
in odoo
|
||||
-o 'note': string
|
||||
-o 'partner_name': string
|
||||
-o 'ref': string
|
||||
- bank statements data: list of dict containing (optional
|
||||
items marked by o) :
|
||||
-o currency code: string (e.g: 'EUR')
|
||||
The ISO 4217 currency code, case insensitive
|
||||
-o account number: string (e.g: 'BE1234567890')
|
||||
The number of the bank account which the statement
|
||||
belongs to
|
||||
- 'name': string (e.g: '000000123')
|
||||
- 'date': date (e.g: 2013-06-26)
|
||||
-o 'balance_start': float (e.g: 8368.56)
|
||||
-o 'balance_end_real': float (e.g: 8888.88)
|
||||
- 'transactions': list of dict containing :
|
||||
- 'name': string
|
||||
(e.g: 'KBC-INVESTERINGSKREDIET 787-5562831-01')
|
||||
- 'date': date
|
||||
- 'amount': float
|
||||
- 'unique_import_id': string
|
||||
-o 'account_number': string
|
||||
Will be used to find/create the res.partner.bank
|
||||
in odoo
|
||||
-o 'note': string
|
||||
-o 'partner_name': string
|
||||
-o 'ref': string
|
||||
"""
|
||||
raise Warning(_('Could not make sense of the given file.\nDid you '
|
||||
'install the module to support this type of file ?'))
|
||||
raise Warning(_(
|
||||
'Could not make sense of the given file.\n'
|
||||
'Did you install the module to support this type of file?'
|
||||
))
|
||||
|
||||
@api.model
|
||||
def _check_parsed_data(self, stmts_vals):
|
||||
def _check_parsed_data(self, statements):
|
||||
""" Basic and structural verifications """
|
||||
if len(stmts_vals) == 0:
|
||||
if len(statements) == 0:
|
||||
raise Warning(_('This file doesn\'t contain any statement.'))
|
||||
|
||||
no_st_line = True
|
||||
for vals in stmts_vals:
|
||||
if vals['transactions'] and len(vals['transactions']) > 0:
|
||||
no_st_line = False
|
||||
break
|
||||
if no_st_line:
|
||||
raise Warning(_('This file doesn\'t contain any transaction.'))
|
||||
for stmt_vals in statements:
|
||||
if 'transactions' in stmt_vals and stmt_vals['transactions']:
|
||||
return
|
||||
# If we get here, no transaction was found:
|
||||
raise Warning(_('This file doesn\'t contain any transaction.'))
|
||||
|
||||
@api.model
|
||||
def _find_additional_data(self, currency_code, account_number):
|
||||
""" Get the res.currency ID and the res.partner.bank ID """
|
||||
# if no currency_code is provided, we'll use the company currency
|
||||
currency_id = False
|
||||
def _find_currency_id(self, currency_code):
|
||||
""" Get res.currency ID."""
|
||||
if currency_code:
|
||||
currency_ids = self.env['res.currency'].search(
|
||||
[('name', '=ilike', currency_code)])
|
||||
company_currency_id = self.env.user.company_id.currency_id
|
||||
if currency_ids:
|
||||
if currency_ids[0] != company_currency_id:
|
||||
currency_id = currency_ids[0].id
|
||||
return currency_ids[0].id
|
||||
else:
|
||||
raise Warning(_(
|
||||
'Statement has invalid currency code %s') % currency_code)
|
||||
# if no currency_code is provided, we'll use the company currency
|
||||
return self.env.user.company_id.currency_id.id
|
||||
|
||||
@api.model
|
||||
def _find_bank_account_id(self, account_number):
|
||||
""" Get res.partner.bank ID """
|
||||
bank_account_id = None
|
||||
if account_number and len(account_number) > 4:
|
||||
bank_account_ids = self.env['res.partner.bank'].search(
|
||||
[('acc_number', '=', account_number)], limit=1)
|
||||
if bank_account_ids:
|
||||
bank_account_id = bank_account_ids[0].id
|
||||
|
||||
return currency_id, bank_account_id
|
||||
return bank_account_id
|
||||
|
||||
@api.model
|
||||
def _get_journal(self, currency_id, bank_account_id, account_number):
|
||||
def _get_journal(self, currency_id, bank_account_id):
|
||||
""" Find the journal """
|
||||
bank_model = self.env['res.partner.bank']
|
||||
# Find the journal from context, wizard or bank account
|
||||
@@ -189,23 +223,45 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
if bank_account.journal_id.id:
|
||||
journal_id = bank_account.journal_id.id
|
||||
# If importing into an existing journal, its currency must be the same
|
||||
# as the bank statement
|
||||
if journal_id:
|
||||
journal_currency_id = self.env['account.journal'].browse(
|
||||
journal_id).currency.id
|
||||
if currency_id and currency_id != journal_currency_id:
|
||||
raise Warning(_('The currency of the bank statement is not '
|
||||
'the same as the currency of the journal !'))
|
||||
# If we couldn't find/create a journal, everything is lost
|
||||
if not journal_id:
|
||||
raise Warning(_('Cannot find in which journal import this '
|
||||
'statement. Please manually select a journal.'))
|
||||
# as the bank statement. When journal has no currency, currency must
|
||||
# be equal to company currency.
|
||||
if journal_id and currency_id:
|
||||
journal_obj = self.env['account.journal'].browse(journal_id)
|
||||
if journal_obj.currency:
|
||||
journal_currency_id = journal_obj.currency.id
|
||||
if currency_id != journal_currency_id:
|
||||
# ALso log message with id's for technical analysis:
|
||||
_logger.warn(
|
||||
_('Statement currency id is %d,'
|
||||
' but journal currency id = %d.'),
|
||||
currency_id,
|
||||
journal_currency_id
|
||||
)
|
||||
raise Warning(_(
|
||||
'The currency of the bank statement is not '
|
||||
'the same as the currency of the journal !'
|
||||
))
|
||||
else:
|
||||
company_currency_id = self.env.user.company_id.currency_id.id
|
||||
if currency_id != company_currency_id:
|
||||
# ALso log message with id's for technical analysis:
|
||||
_logger.warn(
|
||||
_('Statement currency id is %d,'
|
||||
' but company currency id = %d.'),
|
||||
currency_id,
|
||||
company_currency_id
|
||||
)
|
||||
raise Warning(_(
|
||||
'The currency of the bank statement is not '
|
||||
'the same as the company currency !'
|
||||
))
|
||||
return journal_id
|
||||
|
||||
@api.model
|
||||
@api.returns('res.partner.bank')
|
||||
def _create_bank_account(self, account_number, company_id=False,
|
||||
currency_id=False):
|
||||
def _create_bank_account(
|
||||
self, account_number, company_id=False, currency_id=False):
|
||||
"""Automagically create bank account, when not yet existing."""
|
||||
try:
|
||||
bank_type = self.env.ref('base.bank_normal')
|
||||
bank_code = bank_type.code
|
||||
@@ -233,95 +289,85 @@ class AccountBankStatementImport(models.TransientModel):
|
||||
default_currency=currency_id).create(vals_acc)
|
||||
|
||||
@api.model
|
||||
def _complete_stmts_vals(self, stmts_vals, journal_id, account_number):
|
||||
for st_vals in stmts_vals:
|
||||
st_vals['journal_id'] = journal_id
|
||||
|
||||
for line_vals in st_vals['transactions']:
|
||||
unique_import_id = line_vals.get('unique_import_id', False)
|
||||
if unique_import_id:
|
||||
line_vals['unique_import_id'] = (
|
||||
account_number and account_number + '-' or '') + \
|
||||
unique_import_id
|
||||
|
||||
if not line_vals.get('bank_account_id'):
|
||||
# Find the partner and his bank account or create the bank
|
||||
# account. The partner selected during the reconciliation
|
||||
# process will be linked to the bank when the statement is
|
||||
# closed.
|
||||
partner_id = False
|
||||
bank_account_id = False
|
||||
identifying_string = line_vals.get('account_number')
|
||||
if identifying_string:
|
||||
bank_model = self.env['res.partner.bank']
|
||||
banks = bank_model.search(
|
||||
[('acc_number', '=', identifying_string)], limit=1)
|
||||
if banks:
|
||||
bank_account_id = banks[0].id
|
||||
partner_id = banks[0].partner_id.id
|
||||
else:
|
||||
bank_account_id = self._create_bank_account(
|
||||
identifying_string).id
|
||||
line_vals['partner_id'] = partner_id
|
||||
line_vals['bank_account_id'] = bank_account_id
|
||||
|
||||
return stmts_vals
|
||||
def _complete_statement(self, stmt_vals, journal_id, account_number):
|
||||
"""Complete statement from information passed."""
|
||||
stmt_vals['journal_id'] = journal_id
|
||||
for line_vals in stmt_vals['transactions']:
|
||||
unique_import_id = line_vals.get('unique_import_id', False)
|
||||
if unique_import_id:
|
||||
line_vals['unique_import_id'] = (
|
||||
(account_number and account_number + '-' or '') +
|
||||
unique_import_id
|
||||
)
|
||||
if not line_vals.get('bank_account_id'):
|
||||
# Find the partner and his bank account or create the bank
|
||||
# account. The partner selected during the reconciliation
|
||||
# process will be linked to the bank when the statement is
|
||||
# closed.
|
||||
partner_id = False
|
||||
bank_account_id = False
|
||||
account_number = line_vals.get('account_number')
|
||||
if account_number:
|
||||
bank_model = self.env['res.partner.bank']
|
||||
banks = bank_model.search(
|
||||
[('acc_number', '=', account_number)], limit=1)
|
||||
if banks:
|
||||
bank_account_id = banks[0].id
|
||||
partner_id = banks[0].partner_id.id
|
||||
else:
|
||||
bank_obj = self._create_bank_account(account_number)
|
||||
bank_account_id = bank_obj and bank_obj.id or False
|
||||
line_vals['partner_id'] = partner_id
|
||||
line_vals['bank_account_id'] = bank_account_id
|
||||
return stmt_vals
|
||||
|
||||
@api.model
|
||||
def _create_bank_statements(self, stmts_vals):
|
||||
""" Create new bank statements from imported values, filtering out
|
||||
already imported transactions, and returns data used by the
|
||||
def _create_bank_statement(self, stmt_vals):
|
||||
""" Create bank statement from imported values, filtering out
|
||||
already imported transactions, and return data used by the
|
||||
reconciliation widget
|
||||
"""
|
||||
bs_model = self.env['account.bank.statement']
|
||||
bsl_model = self.env['account.bank.statement.line']
|
||||
|
||||
# Filter out already imported transactions and create statements
|
||||
statement_ids = []
|
||||
ignored_statement_lines_import_ids = []
|
||||
for st_vals in stmts_vals:
|
||||
filtered_st_lines = []
|
||||
for line_vals in st_vals['transactions']:
|
||||
if 'unique_import_id' not in line_vals \
|
||||
or not line_vals['unique_import_id'] \
|
||||
or not bool(bsl_model.sudo().search(
|
||||
[('unique_import_id', '=',
|
||||
line_vals['unique_import_id'])],
|
||||
limit=1)):
|
||||
filtered_st_lines.append(line_vals)
|
||||
else:
|
||||
ignored_statement_lines_import_ids.append(
|
||||
line_vals['unique_import_id'])
|
||||
if len(filtered_st_lines) > 0:
|
||||
# Remove values that won't be used to create records
|
||||
st_vals.pop('transactions', None)
|
||||
for line_vals in filtered_st_lines:
|
||||
line_vals.pop('account_number', None)
|
||||
# Create the satement
|
||||
st_vals['line_ids'] = [[0, False, line] for line in
|
||||
filtered_st_lines]
|
||||
statement_ids.append(bs_model.create(st_vals).id)
|
||||
if len(statement_ids) == 0:
|
||||
raise Warning(_('You have already imported that file.'))
|
||||
|
||||
# Filter out already imported transactions and create statement
|
||||
ignored_line_ids = []
|
||||
filtered_st_lines = []
|
||||
for line_vals in stmt_vals['transactions']:
|
||||
unique_id = (
|
||||
'unique_import_id' in line_vals and
|
||||
line_vals['unique_import_id']
|
||||
)
|
||||
if not unique_id or not bool(bsl_model.sudo().search(
|
||||
[('unique_import_id', '=', unique_id)], limit=1)):
|
||||
filtered_st_lines.append(line_vals)
|
||||
else:
|
||||
ignored_line_ids.append(unique_id)
|
||||
statement_id = False
|
||||
if len(filtered_st_lines) > 0:
|
||||
# Remove values that won't be used to create records
|
||||
stmt_vals.pop('transactions', None)
|
||||
for line_vals in filtered_st_lines:
|
||||
line_vals.pop('account_number', None)
|
||||
# Create the statement
|
||||
stmt_vals['line_ids'] = [
|
||||
[0, False, line] for line in filtered_st_lines]
|
||||
statement_id = bs_model.create(stmt_vals).id
|
||||
# Prepare import feedback
|
||||
notifications = []
|
||||
num_ignored = len(ignored_statement_lines_import_ids)
|
||||
num_ignored = len(ignored_line_ids)
|
||||
if num_ignored > 0:
|
||||
notifications += [{
|
||||
'type': 'warning',
|
||||
'message': _("%d transactions had already been imported and "
|
||||
"were ignored.") % num_ignored
|
||||
if num_ignored > 1
|
||||
else _("1 transaction had already been imported and "
|
||||
"was ignored."),
|
||||
'message':
|
||||
_("%d transactions had already been imported and "
|
||||
"were ignored.") % num_ignored
|
||||
if num_ignored > 1
|
||||
else _("1 transaction had already been imported and "
|
||||
"was ignored."),
|
||||
'details': {
|
||||
'name': _('Already imported items'),
|
||||
'model': 'account.bank.statement.line',
|
||||
'ids': bsl_model.search(
|
||||
[('unique_import_id', 'in',
|
||||
ignored_statement_lines_import_ids)]).ids
|
||||
}
|
||||
[('unique_import_id', 'in', ignored_line_ids)]).ids}
|
||||
}]
|
||||
|
||||
return statement_ids, notifications
|
||||
return statement_id, notifications
|
||||
|
||||
@@ -6,8 +6,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 8.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2015-06-08 12:02+0000\n"
|
||||
"PO-Revision-Date: 2015-06-08 12:02+0000\n"
|
||||
"POT-Creation-Date: 2015-06-11 12:15+0000\n"
|
||||
"PO-Revision-Date: 2015-06-11 12:15+0000\n"
|
||||
"Last-Translator: <>\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -16,13 +16,13 @@ msgstr ""
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:313
|
||||
#: 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 ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:316
|
||||
#: 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 ""
|
||||
@@ -53,7 +53,7 @@ msgid "Account Number must be unique"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:319
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:353
|
||||
#, python-format
|
||||
msgid "Already imported items"
|
||||
msgstr ""
|
||||
@@ -73,19 +73,19 @@ msgstr ""
|
||||
msgid "Bank Statement Line"
|
||||
msgstr ""
|
||||
|
||||
#. 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 ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: view:account.bank.statement.import:account_bank_statement_import.account_bank_statement_import_view
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:200
|
||||
#, python-format
|
||||
msgid "Cannot find in which journal import this statement. Please manually select a journal."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:129
|
||||
#: 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 ?"
|
||||
@@ -153,31 +153,55 @@ msgid "Sanitized Account Number"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:181
|
||||
#: 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 ""
|
||||
|
||||
#. 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 ""
|
||||
|
||||
#. 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 ""
|
||||
|
||||
#. 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 ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:195
|
||||
#: 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 ""
|
||||
|
||||
#. 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 ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:136
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:163
|
||||
#, python-format
|
||||
msgid "This file doesn't contain any statement."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:144
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:168
|
||||
#, python-format
|
||||
msgid "This file doesn't contain any transaction."
|
||||
msgstr ""
|
||||
|
||||
#. module: account_bank_statement_import
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:305
|
||||
#: code:addons/account_bank_statement_import/account_bank_statement_import.py:88
|
||||
#, python-format
|
||||
msgid "You have already imported that file."
|
||||
msgstr ""
|
||||
|
||||
236
account_bank_statement_import/i18n/nl.po
Normal file
236
account_bank_statement_import/i18n/nl.po
Normal file
@@ -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 ""
|
||||
236
account_bank_statement_import/parserlib.py
Normal file
236
account_bank_statement_import/parserlib.py
Normal file
@@ -0,0 +1,236 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""Classes and definitions used in parsing bank statements."""
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2015 Therp BV <http://therp.nl>.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
|
||||
class BankTransaction(dict):
|
||||
"""Single transaction that is part of a bank statement."""
|
||||
|
||||
@property
|
||||
def value_date(self):
|
||||
"""property getter"""
|
||||
return self['date']
|
||||
|
||||
@value_date.setter
|
||||
def value_date(self, value_date):
|
||||
"""property setter"""
|
||||
self['date'] = value_date
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""property getter"""
|
||||
return self['name']
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
"""property setter"""
|
||||
self['name'] = name
|
||||
|
||||
@property
|
||||
def transferred_amount(self):
|
||||
"""property getter"""
|
||||
return self['amount']
|
||||
|
||||
@transferred_amount.setter
|
||||
def transferred_amount(self, transferred_amount):
|
||||
"""property setter"""
|
||||
self['amount'] = transferred_amount
|
||||
|
||||
@property
|
||||
def eref(self):
|
||||
"""property getter"""
|
||||
return self['ref']
|
||||
|
||||
@eref.setter
|
||||
def eref(self, eref):
|
||||
"""property setter"""
|
||||
self['ref'] = eref
|
||||
if not self.message:
|
||||
self.name = eref
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
"""property getter"""
|
||||
return self._message
|
||||
|
||||
@message.setter
|
||||
def message(self, message):
|
||||
"""property setter"""
|
||||
self._message = message
|
||||
self.name = message
|
||||
|
||||
@property
|
||||
def remote_owner(self):
|
||||
"""property getter"""
|
||||
return self['partner_name']
|
||||
|
||||
@remote_owner.setter
|
||||
def remote_owner(self, remote_owner):
|
||||
"""property setter"""
|
||||
self['partner_name'] = remote_owner
|
||||
if not (self.message or self.eref):
|
||||
self.name = remote_owner
|
||||
|
||||
@property
|
||||
def remote_account(self):
|
||||
"""property getter"""
|
||||
return self['account_number']
|
||||
|
||||
@remote_account.setter
|
||||
def remote_account(self, remote_account):
|
||||
"""property setter"""
|
||||
self['account_number'] = remote_account
|
||||
|
||||
@property
|
||||
def note(self):
|
||||
return self['note']
|
||||
|
||||
@note.setter
|
||||
def note(self, note):
|
||||
self['note'] = note
|
||||
|
||||
def __init__(self):
|
||||
"""Define and initialize attributes.
|
||||
|
||||
Not all attributes are already used in the actual import.
|
||||
"""
|
||||
super(BankTransaction, self).__init__()
|
||||
self.transfer_type = False # Action type that initiated this message
|
||||
self.execution_date = False # The posted date of the action
|
||||
self.value_date = False # The value date of the action
|
||||
self.remote_account = False # The account of the other party
|
||||
self.remote_currency = False # The currency used by the other party
|
||||
self.exchange_rate = 0.0
|
||||
# The exchange rate used for conversion of local_currency and
|
||||
# remote_currency
|
||||
self.transferred_amount = 0.0 # actual amount transferred
|
||||
self.name = ''
|
||||
self._message = False # message from the remote party
|
||||
self.eref = False # end to end reference for transactions
|
||||
self.remote_owner = False # name of the other party
|
||||
self.remote_owner_address = [] # other parties address lines
|
||||
self.remote_owner_city = False # other parties city name
|
||||
self.remote_owner_postalcode = False # other parties zip code
|
||||
self.remote_owner_country_code = False # other parties country code
|
||||
self.remote_bank_bic = False # bic of other party's bank
|
||||
self.provision_costs = False # costs charged by bank for transaction
|
||||
self.provision_costs_currency = False
|
||||
self.provision_costs_description = False
|
||||
self.error_message = False # error message for interaction with user
|
||||
self.storno_retry = False
|
||||
# If True, make cancelled debit eligible for a next direct debit run
|
||||
self.data = '' # Raw data from which the transaction has been parsed
|
||||
|
||||
|
||||
class BankStatement(dict):
|
||||
"""A bank statement groups data about several bank transactions."""
|
||||
|
||||
@property
|
||||
def statement_id(self):
|
||||
"""property getter"""
|
||||
return self['name']
|
||||
|
||||
def _set_transaction_ids(self):
|
||||
"""Set transaction ids to statement_id with sequence-number."""
|
||||
subno = 0
|
||||
for transaction in self['transactions']:
|
||||
subno += 1
|
||||
transaction['unique_import_id'] = (
|
||||
self.statement_id + str(subno).zfill(4))
|
||||
|
||||
@statement_id.setter
|
||||
def statement_id(self, statement_id):
|
||||
"""property setter"""
|
||||
self['name'] = statement_id
|
||||
self._set_transaction_ids()
|
||||
|
||||
@property
|
||||
def local_account(self):
|
||||
"""property getter"""
|
||||
return self['account_number']
|
||||
|
||||
@local_account.setter
|
||||
def local_account(self, local_account):
|
||||
"""property setter"""
|
||||
self['account_number'] = local_account
|
||||
|
||||
@property
|
||||
def local_currency(self):
|
||||
"""property getter"""
|
||||
return self['currency_code']
|
||||
|
||||
@local_currency.setter
|
||||
def local_currency(self, local_currency):
|
||||
"""property setter"""
|
||||
self['currency_code'] = local_currency
|
||||
|
||||
@property
|
||||
def start_balance(self):
|
||||
"""property getter"""
|
||||
return self['balance_start']
|
||||
|
||||
@start_balance.setter
|
||||
def start_balance(self, start_balance):
|
||||
"""property setter"""
|
||||
self['balance_start'] = start_balance
|
||||
|
||||
@property
|
||||
def end_balance(self):
|
||||
"""property getter"""
|
||||
return self['balance_end']
|
||||
|
||||
@end_balance.setter
|
||||
def end_balance(self, end_balance):
|
||||
"""property setter"""
|
||||
self['balance_end'] = end_balance
|
||||
self['balance_end_real'] = end_balance
|
||||
|
||||
@property
|
||||
def date(self):
|
||||
"""property getter"""
|
||||
return self['date']
|
||||
|
||||
@date.setter
|
||||
def date(self, date):
|
||||
"""property setter"""
|
||||
self['date'] = date
|
||||
|
||||
def create_transaction(self):
|
||||
"""Create and append transaction.
|
||||
|
||||
This should only be called after statement_id has been set, because
|
||||
statement_id will become part of the unique transaction_id.
|
||||
"""
|
||||
transaction = BankTransaction()
|
||||
self['transactions'].append(transaction)
|
||||
# Fill default id, but might be overruled
|
||||
transaction['unique_import_id'] = (
|
||||
self.statement_id + str(len(self['transactions'])).zfill(4))
|
||||
return transaction
|
||||
|
||||
def __init__(self):
|
||||
super(BankStatement, self).__init__()
|
||||
self['transactions'] = []
|
||||
self.statement_id = ''
|
||||
self.local_account = ''
|
||||
self.local_currency = ''
|
||||
self.date = ''
|
||||
self.start_balance = 0.0
|
||||
self.end_balance = 0.0
|
||||
@@ -1,3 +1,5 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
"""Define tests to be run."""
|
||||
from . import test_res_partner_bank
|
||||
from . import test_import_bank_statement
|
||||
from .test_import_file import TestStatementFile
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
from openerp.tests.common import TransactionCase
|
||||
|
||||
|
||||
class TestAccountBankStatemetImport(TransactionCase):
|
||||
class TestAccountBankStatementImport(TransactionCase):
|
||||
"""Tests for import bank statement file import
|
||||
(account.bank.statement.import)
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestAccountBankStatemetImport, self).setUp()
|
||||
super(TestAccountBankStatementImport, self).setUp()
|
||||
self.statement_import_model = self.env[
|
||||
'account.bank.statement.import']
|
||||
self.account_journal_model = self.env['account.journal']
|
||||
@@ -65,10 +65,7 @@ class TestAccountBankStatemetImport(TransactionCase):
|
||||
"""
|
||||
journal = self.account_journal_model.browse(self.journal_id)
|
||||
expected_id = journal.company_id.partner_id.id
|
||||
|
||||
st_import = self.statement_import_model.sudo(self.other_user_id_a.id)
|
||||
bank = st_import._create_bank_account(
|
||||
'001251882303', company_id=self.company_id)
|
||||
|
||||
self.assertEqual(bank.partner_id.id,
|
||||
expected_id)
|
||||
self.assertEqual(bank.partner_id.id, expected_id)
|
||||
|
||||
131
account_bank_statement_import/tests/test_import_file.py
Normal file
131
account_bank_statement_import/tests/test_import_file.py
Normal file
@@ -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)
|
||||
49
account_bank_statement_import_camt/README.rst
Normal file
49
account_bank_statement_import_camt/README.rst
Normal file
@@ -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.
|
||||
1
account_bank_statement_import_camt/__init__.py
Normal file
1
account_bank_statement_import_camt/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import account_bank_statement_import
|
||||
34
account_bank_statement_import_camt/__openerp__.py
Normal file
34
account_bank_statement_import_camt/__openerp__.py
Normal file
@@ -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)
|
||||
239
account_bank_statement_import_camt/camt.py
Normal file
239
account_bank_statement_import_camt/camt.py
Normal file
@@ -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
|
||||
26
account_bank_statement_import_camt/demo/demo_data.xml
Normal file
26
account_bank_statement_import_camt/demo/demo_data.xml
Normal file
@@ -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>
|
||||
241
account_bank_statement_import_camt/test_files/test-camt053.xml
Normal file
241
account_bank_statement_import_camt/test_files/test-camt053.xml
Normal file
@@ -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>
|
||||
23
account_bank_statement_import_camt/tests/__init__.py
Normal file
23
account_bank_statement_import_camt/tests/__init__.py
Normal file
@@ -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
|
||||
)
|
||||
53
account_bank_statement_import_mt940_base/README.rst
Normal file
53
account_bank_statement_import_mt940_base/README.rst
Normal file
@@ -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.
|
||||
1
account_bank_statement_import_mt940_base/__init__.py
Normal file
1
account_bank_statement_import_mt940_base/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import mt940
|
||||
31
account_bank_statement_import_mt940_base/__openerp__.py
Normal file
31
account_bank_statement_import_mt940_base/__openerp__.py
Normal file
@@ -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
|
||||
}
|
||||
262
account_bank_statement_import_mt940_base/mt940.py
Normal file
262
account_bank_statement_import_mt940_base/mt940.py
Normal file
@@ -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
|
||||
51
account_bank_statement_import_mt940_nl_ing/README.rst
Normal file
51
account_bank_statement_import_mt940_nl_ing/README.rst
Normal file
@@ -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.
|
||||
1
account_bank_statement_import_mt940_nl_ing/__init__.py
Normal file
1
account_bank_statement_import_mt940_nl_ing/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import account_bank_statement_import
|
||||
34
account_bank_statement_import_mt940_nl_ing/__openerp__.py
Normal file
34
account_bank_statement_import_mt940_nl_ing/__openerp__.py
Normal file
@@ -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>
|
||||
66
account_bank_statement_import_mt940_nl_ing/mt940.py
Normal file
66
account_bank_statement_import_mt940_nl_ing/mt940.py
Normal file
@@ -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/
|
||||
-
|
||||
}
|
||||
25
account_bank_statement_import_mt940_nl_ing/tests/__init__.py
Normal file
25
account_bank_statement_import_mt940_nl_ing/tests/__init__.py
Normal file
@@ -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
|
||||
)
|
||||
47
account_bank_statement_import_mt940_nl_rabo/README.rst
Normal file
47
account_bank_statement_import_mt940_nl_rabo/README.rst
Normal file
@@ -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.
|
||||
20
account_bank_statement_import_mt940_nl_rabo/__init__.py
Normal file
20
account_bank_statement_import_mt940_nl_rabo/__init__.py
Normal file
@@ -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
|
||||
32
account_bank_statement_import_mt940_nl_rabo/__openerp__.py
Normal file
32
account_bank_statement_import_mt940_nl_rabo/__openerp__.py
Normal file
@@ -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)
|
||||
86
account_bank_statement_import_mt940_nl_rabo/mt940.py
Normal file
86
account_bank_statement_import_mt940_nl_rabo/mt940.py
Normal file
@@ -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
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
{
|
||||
'name': 'Import OFX Bank Statement',
|
||||
'category': 'Accounting & Finance',
|
||||
'category': 'Banking addons',
|
||||
'version': '1.0',
|
||||
'author': 'OpenERP SA,'
|
||||
'Odoo Community Association (OCA)',
|
||||
@@ -9,6 +9,9 @@
|
||||
'depends': [
|
||||
'account_bank_statement_import'
|
||||
],
|
||||
'demo': [
|
||||
'demo/demo_data.xml',
|
||||
],
|
||||
'external_dependencies': {
|
||||
'python': ['ofxparse'],
|
||||
},
|
||||
|
||||
27
account_bank_statement_import_ofx/demo/demo_data.xml
Normal file
27
account_bank_statement_import_ofx/demo/demo_data.xml
Normal file
@@ -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>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{
|
||||
'name': 'Import QIF Bank Statement',
|
||||
'category': 'Accounting & Finance',
|
||||
'category': 'Banking addons',
|
||||
'version': '1.0',
|
||||
'author': 'OpenERP SA,'
|
||||
'Odoo Community Association (OCA)',
|
||||
|
||||
Reference in New Issue
Block a user