From 483472fbc3cc85947fe14a22a8fcd62e2b5e5116 Mon Sep 17 00:00:00 2001 From: Alexis de Lattre Date: Fri, 23 Jan 2015 22:50:24 +0100 Subject: [PATCH] New backport from odoo/master Fix bug #5 --- .../__openerp__.py | 22 ++--- .../account_bank_statement_import_qif.py | 81 +++++++++++------- ...account_bank_statement_import_qif_view.xml | 24 ++++++ .../static/description/icon.png | Bin 0 -> 6146 bytes .../tests/__init__.py | 4 - .../tests/test_import_bank_statement.py | 11 ++- 6 files changed, 91 insertions(+), 51 deletions(-) create mode 100644 account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml create mode 100644 account_bank_statement_import_qif/static/description/icon.png diff --git a/account_bank_statement_import_qif/__openerp__.py b/account_bank_statement_import_qif/__openerp__.py index d06c90a..21b9684 100644 --- a/account_bank_statement_import_qif/__openerp__.py +++ b/account_bank_statement_import_qif/__openerp__.py @@ -1,32 +1,28 @@ # -*- coding: utf-8 -*- # noqa: This is a backport from Odoo. OCA has no control over style here. # flake8: noqa + { 'name': 'Import QIF Bank Statement', + 'category' : 'Accounting & Finance', 'version': '1.0', 'author': 'OpenERP SA', 'description': ''' 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. -Bank Statements may be generated containing a subset of the QIF information (only those transaction lines that are required for the -creation of the Financial Accounting records). - -Backported from Odoo 9.0 - -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. +Important Note +--------------------------------------------- +Because of the QIF format limitation, we cannot ensure the same transactions aren't imported several times or handle multicurrency. +Whenever possible, you should use a more appropriate file format like OFX. ''', - 'images' : [], + 'images': [], 'depends': ['account_bank_statement_import'], 'demo': [], - 'data': [], + 'data': ['account_bank_statement_import_qif_view.xml'], 'auto_install': False, 'installable': True, } - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_bank_statement_import_qif/account_bank_statement_import_qif.py b/account_bank_statement_import_qif/account_bank_statement_import_qif.py index a95656e..38f6a5c 100644 --- a/account_bank_statement_import_qif/account_bank_statement_import_qif.py +++ b/account_bank_statement_import_qif/account_bank_statement_import_qif.py @@ -3,29 +3,49 @@ # flake8: noqa import dateutil.parser -import base64 -from tempfile import TemporaryFile +import StringIO from openerp.tools.translate import _ -from openerp.osv import osv - -from openerp.addons.account_bank_statement_import import account_bank_statement_import as ibs - -ibs.add_file_type(('qif', 'QIF')) +from openerp.osv import osv, fields +from openerp.exceptions import Warning class account_bank_statement_import(osv.TransientModel): _inherit = "account.bank.statement.import" - def process_qif(self, cr, uid, data_file, journal_id=False, context=None): - """ Import a file in the .QIF format""" + _columns = { + '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: - fileobj = TemporaryFile('wb+') - fileobj.write(base64.b64decode(data_file)) - fileobj.seek(0) file_data = "" - for line in fileobj.readlines(): + for line in StringIO.StringIO(data_file).readlines(): file_data += line - fileobj.close() if '\r' in file_data: data_list = file_data.split('\r') else: @@ -33,8 +53,8 @@ class account_bank_statement_import(osv.TransientModel): header = data_list[0].strip() header = header.split(":")[1] except: - raise osv.except_osv(_('Import Error!'), _('Please check QIF file format is proper or not.')) - line_ids = [] + raise Warning(_('Could not decipher the QIF file.')) + transactions = [] vals_line = {} total = 0 if header == "Bank": @@ -45,33 +65,34 @@ class account_bank_statement_import(osv.TransientModel): continue if line[0] == 'D': # date of transaction 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 total += float(line[1:].replace(',', '')) vals_line['amount'] = float(line[1:].replace(',', '')) elif line[0] == 'N': # Check number vals_line['ref'] = line[1:] 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:] + # 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 vals_line['name'] = 'name' in vals_line and vals_line['name'] + ': ' + line[1:] or line[1:] elif line[0] == '^': # end of item - line_ids.append((0, 0, vals_line)) + transactions.append(vals_line) vals_line = {} elif line[0] == '\n': - line_ids = [] + transactions = [] else: pass else: - raise osv.except_osv(_('Error!'), _('Cannot support this Format !Type:%s.') % (header,)) - vals_bank_statement.update({'balance_end_real': total, - 'line_ids': line_ids, - 'journal_id': journal_id}) - return [vals_bank_statement] + raise Warning(_('This file is either not a bank statement or is not correctly formed.')) + + vals_bank_statement.update({ + 'balance_end_real': total, + 'transactions': transactions + }) + return None, None, [vals_bank_statement] -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml b/account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml new file mode 100644 index 0000000..3b3a342 --- /dev/null +++ b/account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml @@ -0,0 +1,24 @@ + + + + + + Import Bank Statements Inherited + account.bank.statement.import + + + + + + + + + + + + diff --git a/account_bank_statement_import_qif/static/description/icon.png b/account_bank_statement_import_qif/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c5842c2b0c2f6769ac66d20eff011c53ed0dc285 GIT binary patch literal 6146 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 OfQ6~G$vb1u#Qy=L>~_Kc literal 0 HcmV?d00001 diff --git a/account_bank_statement_import_qif/tests/__init__.py b/account_bank_statement_import_qif/tests/__init__.py index 389df58..902f7b0 100644 --- a/account_bank_statement_import_qif/tests/__init__.py +++ b/account_bank_statement_import_qif/tests/__init__.py @@ -2,7 +2,3 @@ # noqa: This is a backport from Odoo. OCA has no control over style here. # flake8: noqa from . import test_import_bank_statement -checks = [ - test_import_bank_statement -] - diff --git a/account_bank_statement_import_qif/tests/test_import_bank_statement.py b/account_bank_statement_import_qif/tests/test_import_bank_statement.py index 6838129..bef4c35 100644 --- a/account_bank_statement_import_qif/tests/test_import_bank_statement.py +++ b/account_bank_statement_import_qif/tests/test_import_bank_statement.py @@ -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 = open(qif_file_path, 'rb').read().encode('base64') bank_statement_id = self.statement_import_model.create(cr, uid, dict( - file_type='qif', - data_file=qif_file, - )) - self.statement_import_model.parse_file(cr, uid, [bank_statement_id]) + data_file=qif_file, + )) + context = { + '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] 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)