mirror of
https://github.com/OCA/bank-statement-import.git
synced 2025-01-20 12:37:43 +02:00
[MIG] account_bank_statement_import_ofx: Migration to 10.0
* Remove the code that matches partners, which is wrong and cannot work * Better 'name' on bank statement * Move from models directory to wizard * Remove demo data to tests * Use latest version of ofxparse from github
This commit is contained in:
@@ -5,16 +5,10 @@
|
|||||||
Import OFX Bank Statement
|
Import OFX Bank Statement
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
This module allows you to import the machine readable OFX Files in Odoo: they are parsed and stored in human readable format in
|
This module adds support for the import of bank statements in `OFX format <https://en.wikipedia.org/wiki/Open_Financial_Exchange>`_.
|
||||||
Accounting \ Bank and Cash \ Bank Statements.
|
|
||||||
|
|
||||||
Bank Statements may be generated containing a subset of the OFX information (only those transaction lines that are required for the
|
Bank Statements may be generated containing a subset of the OFX information (only those transaction lines that are required for the
|
||||||
creation of the Financial Accounting records).
|
creation of the Financial Accounting records).
|
||||||
|
|
||||||
The module has been initiated by a backport of the new framework developed
|
|
||||||
by Odoo for V9 at its early stage. It's no more kept in sync with the V9 since
|
|
||||||
it has reach a stage where maintaining a pure backport of 9.0 in 8.0 is not
|
|
||||||
feasible anymore
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
@@ -23,20 +17,12 @@ The module requires one additional python lib:
|
|||||||
|
|
||||||
* `ofxparse <http://pypi.python.org/pypi/ofxparse>`_
|
* `ofxparse <http://pypi.python.org/pypi/ofxparse>`_
|
||||||
|
|
||||||
Technical Note
|
Usage
|
||||||
==============
|
=====
|
||||||
|
|
||||||
this module is based on ofxparse python lib and overload some of its functions.
|
|
||||||
|
|
||||||
* For the time being the default ofxparse lib available with
|
|
||||||
'pip install ofxparse' do not manage correctly european amount that are
|
|
||||||
written with ',' and not with '.'. (For exemple, The Credit Cooperatif
|
|
||||||
French Bank provides OFX 1.0 with amounts written with coma)
|
|
||||||
|
|
||||||
April, 27 2016: this problem has been fixed here:
|
|
||||||
https://github.com/jseutter/ofxparse/commit/283f89c3246ed3fedccc3ef5c96078b7d5b94579
|
|
||||||
but it is not available in the pip lib for the time being.
|
|
||||||
|
|
||||||
|
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
|
||||||
|
:alt: Try me on Runbot
|
||||||
|
:target: https://runbot.odoo-community.org/runbot/174/10.0
|
||||||
|
|
||||||
Known issues / Roadmap
|
Known issues / Roadmap
|
||||||
======================
|
======================
|
||||||
@@ -46,11 +32,10 @@ Known issues / Roadmap
|
|||||||
Bug Tracker
|
Bug Tracker
|
||||||
===========
|
===========
|
||||||
|
|
||||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/bank-statement-import/issues>`_.
|
Bugs are tracked on `GitHub Issues
|
||||||
In case of trouble, please check there if your issue has already been reported.
|
<https://github.com/OCA/bank-statement-import/issues>`_. In case of trouble, please
|
||||||
If you spotted it first, help us smashing it by providing a detailed and welcomed feedback
|
check there if your issue has already been reported. If you spotted it first,
|
||||||
`here <https://github.com/OCA/bank-statement-import/issues/new?body=module:%20account_bank_statement_import_ofx%0Aversion:%208.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
help us smashing it by providing a detailed and welcomed feedback.
|
||||||
|
|
||||||
|
|
||||||
Credits
|
Credits
|
||||||
=======
|
=======
|
||||||
@@ -77,4 +62,4 @@ OCA, or the Odoo Community Association, is a nonprofit organization whose
|
|||||||
mission is to support the collaborative development of Odoo features and
|
mission is to support the collaborative development of Odoo features and
|
||||||
promote its widespread use.
|
promote its widespread use.
|
||||||
|
|
||||||
To contribute to this module, please visit http://odoo-community.org.
|
To contribute to this module, please visit https://odoo-community.org.
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from . import models
|
from . import wizard
|
||||||
|
|||||||
@@ -2,26 +2,22 @@
|
|||||||
{
|
{
|
||||||
'name': 'Import OFX Bank Statement',
|
'name': 'Import OFX Bank Statement',
|
||||||
'category': 'Banking addons',
|
'category': 'Banking addons',
|
||||||
'version': '9.0.0.0.0',
|
'version': '10.0.1.0.0',
|
||||||
'license': 'AGPL-3',
|
'license': 'AGPL-3',
|
||||||
'author': 'OpenERP SA,'
|
'author': 'Odoo SA,'
|
||||||
|
'Akretion,'
|
||||||
'La Louve,'
|
'La Louve,'
|
||||||
'GRAP,'
|
'GRAP,'
|
||||||
'Odoo Community Association (OCA)',
|
'Odoo Community Association (OCA)',
|
||||||
'website': 'https://odoo-community.org/',
|
'website': 'https://odoo-community.org/',
|
||||||
'depends': [
|
'depends': [
|
||||||
'l10n_generic_coa',
|
|
||||||
'account_bank_statement_import',
|
'account_bank_statement_import',
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'views/view_account_bank_statement_import.xml',
|
'views/view_account_bank_statement_import.xml',
|
||||||
],
|
],
|
||||||
'demo': [
|
|
||||||
'demo/demo_data.xml',
|
|
||||||
],
|
|
||||||
'external_dependencies': {
|
'external_dependencies': {
|
||||||
'python': ['ofxparse'],
|
'python': ['ofxparse'],
|
||||||
},
|
},
|
||||||
'auto_install': False,
|
|
||||||
'installable': True,
|
'installable': True,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<odoo>
|
|
||||||
|
|
||||||
<record id="ofx_sequence" model="ir.sequence">
|
|
||||||
<field name="name">Bank Journal - (test ofx)</field>
|
|
||||||
<field name="prefix">OFX/%(range_year)s/</field>
|
|
||||||
<field name="implementation">no_gap</field>
|
|
||||||
<field name="padding">4</field>
|
|
||||||
<field name="use_date_range">1</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
|
|
||||||
<record id="ofx_bank_journal" model="account.journal">
|
|
||||||
<field name="name">Bank Journal - (test ofx)</field>
|
|
||||||
<field name="bank_acc_number">123456</field>
|
|
||||||
<field name="code">TBNKOFX</field>
|
|
||||||
<field name="type">bank</field>
|
|
||||||
<field name="sequence_id" ref="ofx_sequence"/>
|
|
||||||
<field name="user_id" ref="base.user_root"/>
|
|
||||||
<field name="currency_id" ref="base.USD"/>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
</odoo>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import StringIO
|
|
||||||
|
|
||||||
from openerp import api, models
|
|
||||||
from openerp.tools.translate import _
|
|
||||||
from openerp.exceptions import Warning as UserError
|
|
||||||
|
|
||||||
from .ofx import OfxParser, OfxParser_ok
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AccountBankStatementImport(models.TransientModel):
|
|
||||||
_inherit = 'account.bank.statement.import'
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def _check_ofx(self, data_file):
|
|
||||||
if not OfxParser_ok:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
ofx = OfxParser.parse(StringIO.StringIO(data_file))
|
|
||||||
except Exception as e:
|
|
||||||
_logger.debug(e)
|
|
||||||
return False
|
|
||||||
return ofx
|
|
||||||
|
|
||||||
@api.model
|
|
||||||
def _parse_file(self, data_file):
|
|
||||||
ofx = self._check_ofx(data_file)
|
|
||||||
if not ofx:
|
|
||||||
return super(AccountBankStatementImport, self)._parse_file(
|
|
||||||
data_file)
|
|
||||||
|
|
||||||
transactions = []
|
|
||||||
total_amt = 0.00
|
|
||||||
try:
|
|
||||||
for transaction in ofx.account.statement.transactions:
|
|
||||||
# Since ofxparse doesn't provide account numbers, we'll have
|
|
||||||
# to find res.partner and res.partner.bank here
|
|
||||||
# (normal behavious is to provide 'account_number', which the
|
|
||||||
# generic module uses to find partner/bank)
|
|
||||||
bank_account_id = partner_id = False
|
|
||||||
banks = self.env['res.partner.bank'].search(
|
|
||||||
[('bank_name', '=', transaction.payee)], limit=1)
|
|
||||||
if banks:
|
|
||||||
bank_account = banks[0]
|
|
||||||
bank_account_id = bank_account.id
|
|
||||||
partner_id = bank_account.partner_id.id
|
|
||||||
vals_line = {
|
|
||||||
'date': transaction.date,
|
|
||||||
'name': transaction.payee + (
|
|
||||||
transaction.memo and ': ' + transaction.memo or ''),
|
|
||||||
'ref': transaction.id,
|
|
||||||
'amount': transaction.amount,
|
|
||||||
'unique_import_id': transaction.id,
|
|
||||||
'bank_account_id': bank_account_id,
|
|
||||||
'partner_id': partner_id,
|
|
||||||
}
|
|
||||||
total_amt += float(transaction.amount)
|
|
||||||
transactions.append(vals_line)
|
|
||||||
except Exception, e:
|
|
||||||
raise UserError(_("The following problem occurred during import. "
|
|
||||||
"The file might not be valid.\n\n %s" % e.message))
|
|
||||||
|
|
||||||
vals_bank_statement = {
|
|
||||||
'name': ofx.account.routing_number,
|
|
||||||
'transactions': transactions,
|
|
||||||
'balance_start': ofx.account.statement.balance,
|
|
||||||
'balance_end_real':
|
|
||||||
float(ofx.account.statement.balance) + total_amt,
|
|
||||||
}
|
|
||||||
return ofx.account.statement.currency, ofx.account.number, [
|
|
||||||
vals_bank_statement]
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ofxparse import OfxParser as OfxParserOriginal
|
|
||||||
OfxParser_ok = True
|
|
||||||
except ImportError:
|
|
||||||
_logger.warn("ofxparse not found, OFX parsing disabled.")
|
|
||||||
OfxParserOriginal = object
|
|
||||||
OfxParser_ok = False
|
|
||||||
|
|
||||||
|
|
||||||
class OfxParser(OfxParserOriginal):
|
|
||||||
""" Custom changes in the OFX Parser.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _tagToDecimal(self, tag):
|
|
||||||
tag.string = tag.string.replace(',', '.')
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parseStatement(cls_, stmt_ofx):
|
|
||||||
"""Amount with ',' replaced by '.' in the following tags :
|
|
||||||
//LEDGERBAL/BALAMT
|
|
||||||
"""
|
|
||||||
ledgerbal_tag = stmt_ofx.find('ledgerbal')
|
|
||||||
if hasattr(ledgerbal_tag, "contents"):
|
|
||||||
cls_._tagToDecimal(ledgerbal_tag.find('balamt'))
|
|
||||||
return super(OfxParser, cls_).parseStatement(stmt_ofx)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parseTransaction(cls_, txn_ofx):
|
|
||||||
"""Amount with ',' replaced by '.' in the following tags :
|
|
||||||
//TRNAMT
|
|
||||||
"""
|
|
||||||
cls_._tagToDecimal(txn_ofx.find('trnamt'))
|
|
||||||
return super(OfxParser, cls_).parseTransaction(txn_ofx)
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from openerp.tests.common import TransactionCase
|
from odoo.tests.common import TransactionCase
|
||||||
from openerp.modules.module import get_module_resource
|
from odoo.modules.module import get_module_resource
|
||||||
|
|
||||||
|
|
||||||
class TestOfxFile(TransactionCase):
|
class TestOfxFile(TransactionCase):
|
||||||
@@ -10,26 +10,39 @@ class TestOfxFile(TransactionCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestOfxFile, self).setUp()
|
super(TestOfxFile, self).setUp()
|
||||||
self.statement_import_model = self.env['account.bank.statement.import']
|
self.absi_model = self.env['account.bank.statement.import']
|
||||||
self.bank_statement_model = self.env['account.bank.statement']
|
self.abs_model = self.env['account.bank.statement']
|
||||||
|
self.absl_model = self.env['account.bank.statement.line']
|
||||||
|
cur = self.env.ref('base.USD')
|
||||||
|
self.env.ref('base.main_company').currency_id = cur.id
|
||||||
|
bank = self.env['res.partner.bank'].create({
|
||||||
|
'acc_number': '123456',
|
||||||
|
'partner_id': self.env.ref('base.main_partner').id,
|
||||||
|
'company_id': self.env.ref('base.main_company').id,
|
||||||
|
'bank_id': self.env.ref('base.res_bank_1').id,
|
||||||
|
})
|
||||||
|
self.env['account.journal'].create({
|
||||||
|
'name': 'Bank Journal TEST OFX',
|
||||||
|
'code': 'BNK12',
|
||||||
|
'type': 'bank',
|
||||||
|
'bank_account_id': bank.id,
|
||||||
|
})
|
||||||
|
|
||||||
def test_ofx_file_import(self):
|
def test_ofx_file_import(self):
|
||||||
ofx_file_path = get_module_resource(
|
ofx_file_path = get_module_resource(
|
||||||
'account_bank_statement_import_ofx',
|
'account_bank_statement_import_ofx',
|
||||||
'tests/test_ofx_file/', 'test_ofx.ofx')
|
'tests/test_ofx_file/', 'test_ofx.ofx')
|
||||||
ofx_file = open(ofx_file_path, 'rb').read().encode('base64')
|
ofx_file = open(ofx_file_path, 'rb').read().encode('base64')
|
||||||
bank_statement = self.statement_import_model.create(
|
bank_statement = self.absi_model.create(
|
||||||
dict(data_file=ofx_file))
|
dict(data_file=ofx_file))
|
||||||
bank_statement.import_file()
|
bank_statement.import_file()
|
||||||
bank_st_record = self.bank_statement_model.search(
|
bank_st_record = self.abs_model.search(
|
||||||
[('name', '=', '000000123')])[0]
|
[('name', 'like', '123456')])[0]
|
||||||
self.assertEquals(bank_st_record.balance_start, 2156.56)
|
self.assertEquals(bank_st_record.balance_start, 2156.56)
|
||||||
self.assertEquals(bank_st_record.balance_end_real, 1796.56)
|
self.assertEquals(bank_st_record.balance_end_real, 1796.56)
|
||||||
|
|
||||||
line = bank_st_record.line_ids[0]
|
line = self.absl_model.search([
|
||||||
self.assertEquals(line.name, 'Agrolait')
|
('name', '=', 'Agrolait'),
|
||||||
|
('statement_id', '=', bank_st_record.id)])[0]
|
||||||
self.assertEquals(line.ref, '219378')
|
self.assertEquals(line.ref, '219378')
|
||||||
self.assertEquals(line.partner_id.id, self.ref('base.res_partner_2'))
|
self.assertEquals(line.date, '2013-08-24')
|
||||||
self.assertEquals(
|
|
||||||
line.bank_account_id.id,
|
|
||||||
self.ref('account_bank_statement_import.ofx_partner_bank_1'))
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_account_bank_statement_import_form" model="ir.ui.view">
|
<record id="view_account_bank_statement_import_form" model="ir.ui.view">
|
||||||
<field name="model">account.bank.statement.import</field>
|
<field name="model">account.bank.statement.import</field>
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import StringIO
|
||||||
|
|
||||||
|
from odoo import api, models, fields, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools import float_is_zero
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from ofxparse import OfxParser
|
||||||
|
except ImportError:
|
||||||
|
_logger.debug("ofxparse not found.")
|
||||||
|
OfxParser = None
|
||||||
|
|
||||||
|
|
||||||
|
class AccountBankStatementImport(models.TransientModel):
|
||||||
|
_inherit = 'account.bank.statement.import'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _check_ofx(self, data_file):
|
||||||
|
if not OfxParser:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
ofx = OfxParser.parse(StringIO.StringIO(data_file))
|
||||||
|
except Exception as e:
|
||||||
|
_logger.debug(e)
|
||||||
|
return False
|
||||||
|
return ofx
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _prepare_ofx_transaction_line(self, transaction):
|
||||||
|
# since odoo 9, the account module defines a constraint
|
||||||
|
# on account.bank.statement.line: 'amount' must be != 0
|
||||||
|
# But some banks have some transactions with amount=0
|
||||||
|
# for bank charges that are offered, which blocks the import
|
||||||
|
precision = self.env['decimal.precision'].precision_get('Account')
|
||||||
|
if float_is_zero(
|
||||||
|
float(transaction.amount), precision_digits=precision):
|
||||||
|
return False
|
||||||
|
# Since ofxparse doesn't provide account numbers,
|
||||||
|
# we cannot provide the key 'bank_account_id',
|
||||||
|
# nor the key 'account_number'
|
||||||
|
# If you read odoo10/addons/account_bank_statement_import/
|
||||||
|
# account_bank_statement_import.py, it's the only 2 keys
|
||||||
|
# we can provide to match a partner.
|
||||||
|
vals = {
|
||||||
|
'date': transaction.date,
|
||||||
|
'name': transaction.payee + (
|
||||||
|
transaction.memo and ': ' + transaction.memo or ''),
|
||||||
|
'ref': transaction.id,
|
||||||
|
'amount': float(transaction.amount),
|
||||||
|
'unique_import_id': transaction.id,
|
||||||
|
}
|
||||||
|
return vals
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _parse_file(self, data_file):
|
||||||
|
ofx = self._check_ofx(data_file)
|
||||||
|
if not ofx:
|
||||||
|
return super(AccountBankStatementImport, self)._parse_file(
|
||||||
|
data_file)
|
||||||
|
|
||||||
|
transactions = []
|
||||||
|
total_amt = 0.00
|
||||||
|
start_date = end_date = False
|
||||||
|
try:
|
||||||
|
for transaction in ofx.account.statement.transactions:
|
||||||
|
vals = self._prepare_ofx_transaction_line(transaction)
|
||||||
|
if vals:
|
||||||
|
transactions.append(vals)
|
||||||
|
total_amt += vals['amount']
|
||||||
|
tdate = fields.Date.to_string(vals['date'])
|
||||||
|
if not start_date or tdate < start_date:
|
||||||
|
start_date = tdate
|
||||||
|
if not end_date or tdate > end_date:
|
||||||
|
end_date = tdate
|
||||||
|
except Exception, e:
|
||||||
|
raise UserError(_(
|
||||||
|
"The following problem occurred during import. "
|
||||||
|
"The file might not be valid.\n\n %s") % e.message)
|
||||||
|
|
||||||
|
vals_bank_statement = {
|
||||||
|
'name': _('Account %s %s > %s') % (
|
||||||
|
ofx.account.number, start_date, end_date),
|
||||||
|
'transactions': transactions,
|
||||||
|
'balance_start': ofx.account.statement.balance,
|
||||||
|
'balance_end_real':
|
||||||
|
float(ofx.account.statement.balance) + total_amt,
|
||||||
|
}
|
||||||
|
return ofx.account.statement.currency, ofx.account.number, [
|
||||||
|
vals_bank_statement]
|
||||||
Reference in New Issue
Block a user