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 0000000..c5842c2 Binary files /dev/null and b/account_bank_statement_import_qif/static/description/icon.png differ 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)