New backport from odoo/master

Fix bug #5
This commit is contained in:
Alexis de Lattre
2015-01-23 22:50:24 +01:00
committed by Pedro M. Baeza
parent 0634acb78f
commit 483472fbc3
6 changed files with 91 additions and 51 deletions

View File

@@ -1,32 +1,28 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# noqa: This is a backport from Odoo. OCA has no control over style here. # noqa: This is a backport from Odoo. OCA has no control over style here.
# flake8: noqa # flake8: noqa
{ {
'name': 'Import QIF Bank Statement', 'name': 'Import QIF Bank Statement',
'category' : 'Accounting & Finance',
'version': '1.0', 'version': '1.0',
'author': 'OpenERP SA', 'author': 'OpenERP SA',
'description': ''' 'description': '''
Module to import QIF bank statements. Module to import QIF bank statements.
====================================== ======================================
This module allows you to import the machine readable QIF Files in Odoo: they are parsed and stored in human readable format in This module allows you to import the machine readable QIF Files in Odoo: they are parsed and stored in human readable format in
Accounting \ Bank and Cash \ Bank Statements. Accounting \ Bank and Cash \ Bank Statements.
Bank Statements may be generated containing a subset of the QIF information (only those transaction lines that are required for the Important Note
creation of the Financial Accounting records). ---------------------------------------------
Because of the QIF format limitation, we cannot ensure the same transactions aren't imported several times or handle multicurrency.
Backported from Odoo 9.0 Whenever possible, you should use a more appropriate file format like OFX.
When testing with the provided test file, make sure the demo data from the
base account_bank_statement_import module has been imported, or manually
create periods for the year 2013.
''', ''',
'images' : [], 'images': [],
'depends': ['account_bank_statement_import'], 'depends': ['account_bank_statement_import'],
'demo': [], 'demo': [],
'data': [], 'data': ['account_bank_statement_import_qif_view.xml'],
'auto_install': False, 'auto_install': False,
'installable': True, 'installable': True,
} }
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -3,29 +3,49 @@
# flake8: noqa # flake8: noqa
import dateutil.parser import dateutil.parser
import base64 import StringIO
from tempfile import TemporaryFile
from openerp.tools.translate import _ from openerp.tools.translate import _
from openerp.osv import osv from openerp.osv import osv, fields
from openerp.exceptions import Warning
from openerp.addons.account_bank_statement_import import account_bank_statement_import as ibs
ibs.add_file_type(('qif', 'QIF'))
class account_bank_statement_import(osv.TransientModel): class account_bank_statement_import(osv.TransientModel):
_inherit = "account.bank.statement.import" _inherit = "account.bank.statement.import"
def process_qif(self, cr, uid, data_file, journal_id=False, context=None): _columns = {
""" Import a file in the .QIF format""" 'journal_id': fields.many2one('account.journal', string='Journal', help='Accounting journal related to the bank statement you\'re importing. It has be be manually chosen for statement formats which doesn\'t allow automatic journal detection (QIF for example).'),
'hide_journal_field': fields.boolean('Hide the journal field in the view'),
}
def _get_hide_journal_field(self, cr, uid, context=None):
return context and 'journal_id' in context or False
_defaults = {
'hide_journal_field': _get_hide_journal_field,
}
def _get_journal(self, cr, uid, currency_id, bank_account_id, account_number, context=None):
""" As .QIF format does not allow us to detect the journal, we need to let the user choose it.
We set it in context before to call super so it's the same as calling the widget from a journal """
if context is None:
context = {}
if context.get('active_id'):
record = self.browse(cr, uid, context.get('active_id'), context=context)
if record.journal_id:
context['journal_id'] = record.journal_id.id
return super(account_bank_statement_import, self)._get_journal(cr, uid, currency_id, bank_account_id, account_number, context=context)
def _check_qif(self, cr, uid, data_file, context=None):
return data_file.strip().startswith('!Type:')
def _parse_file(self, cr, uid, data_file, context=None):
if not self._check_qif(cr, uid, data_file, context=context):
return super(account_bank_statement_import, self)._parse_file(cr, uid, data_file, context=context)
try: try:
fileobj = TemporaryFile('wb+')
fileobj.write(base64.b64decode(data_file))
fileobj.seek(0)
file_data = "" file_data = ""
for line in fileobj.readlines(): for line in StringIO.StringIO(data_file).readlines():
file_data += line file_data += line
fileobj.close()
if '\r' in file_data: if '\r' in file_data:
data_list = file_data.split('\r') data_list = file_data.split('\r')
else: else:
@@ -33,8 +53,8 @@ class account_bank_statement_import(osv.TransientModel):
header = data_list[0].strip() header = data_list[0].strip()
header = header.split(":")[1] header = header.split(":")[1]
except: except:
raise osv.except_osv(_('Import Error!'), _('Please check QIF file format is proper or not.')) raise Warning(_('Could not decipher the QIF file.'))
line_ids = [] transactions = []
vals_line = {} vals_line = {}
total = 0 total = 0
if header == "Bank": if header == "Bank":
@@ -45,33 +65,34 @@ class account_bank_statement_import(osv.TransientModel):
continue continue
if line[0] == 'D': # date of transaction if line[0] == 'D': # date of transaction
vals_line['date'] = dateutil.parser.parse(line[1:], fuzzy=True).date() vals_line['date'] = dateutil.parser.parse(line[1:], fuzzy=True).date()
if vals_line.get('date') and not vals_bank_statement.get('period_id'):
period_ids = self.pool.get('account.period').find(cr, uid, vals_line['date'], context=context)
vals_bank_statement.update({'period_id': period_ids and period_ids[0] or False})
elif line[0] == 'T': # Total amount elif line[0] == 'T': # Total amount
total += float(line[1:].replace(',', '')) total += float(line[1:].replace(',', ''))
vals_line['amount'] = float(line[1:].replace(',', '')) vals_line['amount'] = float(line[1:].replace(',', ''))
elif line[0] == 'N': # Check number elif line[0] == 'N': # Check number
vals_line['ref'] = line[1:] vals_line['ref'] = line[1:]
elif line[0] == 'P': # Payee elif line[0] == 'P': # Payee
bank_account_id, partner_id = self._detect_partner(cr, uid, line[1:], identifying_field='owner_name', context=context)
vals_line['partner_id'] = partner_id
vals_line['bank_account_id'] = bank_account_id
vals_line['name'] = 'name' in vals_line and line[1:] + ': ' + vals_line['name'] or line[1:] vals_line['name'] = 'name' in vals_line and line[1:] + ': ' + vals_line['name'] or line[1:]
# Since QIF 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)
ids = self.pool.get('res.partner.bank').search(cr, uid, [('owner_name', '=', line[1:])], context=context)
if ids:
vals_line['bank_account_id'] = bank_account_id = ids[0]
vals_line['partner_id'] = self.pool.get('res.partner.bank').browse(cr, uid, bank_account_id, context=context).partner_id.id
elif line[0] == 'M': # Memo elif line[0] == 'M': # Memo
vals_line['name'] = 'name' in vals_line and vals_line['name'] + ': ' + line[1:] or line[1:] vals_line['name'] = 'name' in vals_line and vals_line['name'] + ': ' + line[1:] or line[1:]
elif line[0] == '^': # end of item elif line[0] == '^': # end of item
line_ids.append((0, 0, vals_line)) transactions.append(vals_line)
vals_line = {} vals_line = {}
elif line[0] == '\n': elif line[0] == '\n':
line_ids = [] transactions = []
else: else:
pass pass
else: else:
raise osv.except_osv(_('Error!'), _('Cannot support this Format !Type:%s.') % (header,)) raise Warning(_('This file is either not a bank statement or is not correctly formed.'))
vals_bank_statement.update({'balance_end_real': total,
'line_ids': line_ids, vals_bank_statement.update({
'journal_id': journal_id}) 'balance_end_real': total,
return [vals_bank_statement] 'transactions': transactions
})
return None, None, [vals_bank_statement]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" ?>
<openerp>
<data>
<record id="account_bank_statement_import_view_inherited" model="ir.ui.view">
<field name="name">Import Bank Statements Inherited</field>
<field name="model">account.bank.statement.import</field>
<field name="priority" eval="20"/>
<field name="inherit_id" ref="account_bank_statement_import.account_bank_statement_import_view" />
<field name="arch" type="xml">
<xpath expr="//field[@name='data_file']" position="after">
<field name="hide_journal_field" invisible="1"/>
<label for="journal_id"/>
<field name="journal_id"
domain="[('type', '=', 'bank')]"
attrs="{'invisible': [('hide_journal_field', '=', True)]}"
context="{'default_type':'bank'}"/>
</xpath>
</field>
</record>
</data>
</openerp>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -2,7 +2,3 @@
# noqa: This is a backport from Odoo. OCA has no control over style here. # noqa: This is a backport from Odoo. OCA has no control over style here.
# flake8: noqa # flake8: noqa
from . import test_import_bank_statement from . import test_import_bank_statement
checks = [
test_import_bank_statement
]

View File

@@ -20,10 +20,13 @@ class TestQifFile(TransactionCase):
qif_file_path = get_module_resource('account_bank_statement_import_qif', 'test_qif_file', 'test_qif.qif') qif_file_path = get_module_resource('account_bank_statement_import_qif', 'test_qif_file', 'test_qif.qif')
qif_file = open(qif_file_path, 'rb').read().encode('base64') qif_file = open(qif_file_path, 'rb').read().encode('base64')
bank_statement_id = self.statement_import_model.create(cr, uid, dict( bank_statement_id = self.statement_import_model.create(cr, uid, dict(
file_type='qif', data_file=qif_file,
data_file=qif_file, ))
)) context = {
self.statement_import_model.parse_file(cr, uid, [bank_statement_id]) 'journal_id': self.registry('ir.model.data').get_object_reference(cr, uid, 'account', 'bank_journal')[1],
'allow_auto_create_journal': True,
}
self.statement_import_model.import_file(cr, uid, [bank_statement_id], context=context)
line_id = self.bank_statement_line_model.search(cr, uid, [('name', '=', 'YOUR LOCAL SUPERMARKET')])[0] line_id = self.bank_statement_line_model.search(cr, uid, [('name', '=', 'YOUR LOCAL SUPERMARKET')])[0]
statement_id = self.bank_statement_line_model.browse(cr, uid, line_id).statement_id.id statement_id = self.bank_statement_line_model.browse(cr, uid, line_id).statement_id.id
bank_st_record = self.bank_statement_model.browse(cr, uid, statement_id) bank_st_record = self.bank_statement_model.browse(cr, uid, statement_id)