Browse Source
[MIG] account_bank_statement_import_ofx: Migration to 10.0
[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 githubpull/142/head
Alexis de Lattre
8 years ago
committed by
Nicolas JEUDY
10 changed files with 138 additions and 188 deletions
-
37account_bank_statement_import_ofx/README.rst
-
2account_bank_statement_import_ofx/__init__.py
-
10account_bank_statement_import_ofx/__manifest__.py
-
23account_bank_statement_import_ofx/demo/demo_data.xml
-
75account_bank_statement_import_ofx/models/account_bank_statement_import.py
-
40account_bank_statement_import_ofx/models/ofx.py
-
39account_bank_statement_import_ofx/tests/test_import_bank_statement.py
-
2account_bank_statement_import_ofx/views/view_account_bank_statement_import.xml
-
0account_bank_statement_import_ofx/wizard/__init__.py
-
94account_bank_statement_import_ofx/wizard/account_bank_statement_import.py
@ -1,2 +1,2 @@ |
|||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
from . import models |
|
||||
|
from . import wizard |
@ -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) |
|
@ -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] |
Write
Preview
Loading…
Cancel
Save
Reference in new issue