From 2d9569cc18d47c0f9c29efaec78900ebefb0372d Mon Sep 17 00:00:00 2001 From: Arthur Maniet <arthurmaniet@me.com> Date: Sun, 31 May 2015 14:58:04 +0200 Subject: [PATCH] [FIX] account: bank statement import - use a wizard to ask about creating a new bank account + journal - fix 'Import Statement' action from the dashboard - make l10n_be_coda new-api-compliant --- addons/account/account_journal_dashboard.py | 4 +- .../account_bank_statement_import.py | 145 +++++++++--------- .../wizard/journal_creation.py | 4 +- .../wizard/journal_creation.xml | 2 +- .../account_bank_statement_import_qif.py | 15 +- ...account_bank_statement_import_qif_view.xml | 8 +- addons/l10n_be/__openerp__.py | 1 - addons/l10n_be_coda/__openerp__.py | 4 +- .../wizard/account_coda_import.py | 27 ++-- 9 files changed, 103 insertions(+), 107 deletions(-) diff --git a/addons/account/account_journal_dashboard.py b/addons/account/account_journal_dashboard.py index a1b5716ba85b..f7cdd726a3ef 100644 --- a/addons/account/account_journal_dashboard.py +++ b/addons/account/account_journal_dashboard.py @@ -343,5 +343,7 @@ class account_journal(models.Model): action_name = 'action_account_bank_statement_import' ir_model_obj = self.pool['ir.model.data'] model, action_id = ir_model_obj.get_object_reference(self._cr, self._uid, 'account_bank_statement_import', action_name) - action = self.pool[model].read(self._cr, self._uid, action_id, context=self._context) + action = self.pool[model].read(self._cr, self._uid, action_id, context=self.env.context) + # Note: this drops action['context'], which is a dict stored as a string, which is not easy to update + action.update({'context': (u"{'journal_id': " + str(self.id) + u"}")}) return action diff --git a/addons/account_bank_statement_import/account_bank_statement_import.py b/addons/account_bank_statement_import/account_bank_statement_import.py index 893c1fae0ba7..c9ca9c193885 100644 --- a/addons/account_bank_statement_import/account_bank_statement_import.py +++ b/addons/account_bank_statement_import/account_bank_statement_import.py @@ -30,25 +30,24 @@ class AccountBankStatementImport(models.TransientModel): def import_file(self): """ Process the file chosen in the wizard, create bank statement(s) and go to reconciliation. """ self.ensure_one() - rec = self.with_context(active_id=self.ids[0]) #set the active_id in the context, so that any extension module could #reuse the fields chosen in the wizard if needed (see .QIF for example) data_file = self.data_file - # The appropriate implementation module returns the required data - currency_code, account_number, stmts_vals = rec._parse_file(base64.b64decode(data_file)) + # Let the appropriate implementation module parse the file and return the required data + # The active_id is passed in context in case an implementation module requires information about the wizard state (see QIF) + currency_code, account_number, stmts_vals = self.with_context(active_id=self.ids[0])._parse_file(base64.b64decode(data_file)) # Check raw data - rec._check_parsed_data(stmts_vals) - # Try to find the bank account and currency in odoo - currency_id, bank_account_id = rec._find_additional_data(currency_code, account_number) - # Find or create the bank journal - journal_id = rec._get_journal(currency_id, bank_account_id, account_number) - # Create the bank account if not already existing - if not bank_account_id and account_number: - rec._create_bank_account(account_number, journal_id=journal_id) + self._check_parsed_data(stmts_vals) + # Try to find the currency and journal in odoo + currency, journal, bank_account = self._find_additional_data(currency_code, account_number) + # If no journal found, ask the user about creating one + if not journal: + # The active_id is passed in context so the wizard can call import_file again once the journal is created + return self.with_context(active_id=self.ids[0])._journal_creation_wizard(currency, account_number, bank_account) # Prepare statement data to be used for bank statements creation - stmts_vals = rec._complete_stmts_vals(stmts_vals, journal_id, account_number) + stmts_vals = self._complete_stmts_vals(stmts_vals, journal, account_number) # Create the bank statements - statement_ids, notifications = rec._create_bank_statements(stmts_vals) + statement_ids, notifications = self._create_bank_statements(stmts_vals) # Finally dispatch to reconciliation interface action = self.env.ref('account.action_bank_reconcile_bank_statements') return { @@ -61,6 +60,24 @@ class AccountBankStatementImport(models.TransientModel): 'type': 'ir.actions.client', } + def _journal_creation_wizard(self, currency, account_number, bank_account): + """ Calls a wizard that allows the user to accept/refuse journal creation """ + return { + 'name': _('Journal Creation'), + 'type': 'ir.actions.act_window', + 'res_model': 'account.bank.statement.import.journal.creation', + 'view_type': 'form', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'statement_import_transient_id': self.env.context['active_id'], + 'default_currency_id': currency and currency.id or False, + 'default_account_number': account_number, + 'bank_account_id': bank_account and bank_account.id or False, + 'default_name': _('Bank') + ' ' + account_number, + } + } + def _parse_file(self, data_file): """ Each module adding a file support must extends this method. It processes the file if it can, returns super otherwise, resulting in a chain of responsability. This method parses the given file and returns the data required by the bank statement import process, as specified below. @@ -102,83 +119,71 @@ class AccountBankStatementImport(models.TransientModel): def _find_additional_data(self, currency_code, account_number): """ Get the res.currency ID and the res.partner.bank ID """ - currency_id = False # So if no currency_code is provided, we'll use the company currency + company_currency = self.env.user.company_id.currency_id + journal_obj = self.env['account.journal'] + currency = None + if currency_code: currency = self.env['res.currency'].search([('name', '=ilike', currency_code)], limit=1) - company_currency = self.env.user.company_id.currency_id - if currency.id != company_currency.id: - currency_id = currency.id + if not currency: + raise osv.except_osv(_("No currency found matching '%s'.") % currency_code) + if currency == company_currency: + currency = False - bank_account_id = None + bank_account = None if account_number and len(account_number) > 4: - account_number = account_number.replace(' ', '').replace('-', '') - self.env.cr.execute("select id from res_partner_bank where replace(replace(acc_number,' ',''),'-','') = %s", (account_number,)) - bank_account_ids = [id[0] for id in self.env.cr.fetchall()] - bank_account_ids = self.env['res.partner.bank'].search([('id', 'in', bank_account_ids)], limit=1) - if bank_account_ids: - bank_account_id = bank_account_ids.id - - return currency_id, bank_account_id - - def _get_journal(self, currency_id, bank_account_id, account_number): - """ Find or create the journal """ - ResPartnerBank = self.env['res.partner.bank'] + bank_account = self.env['res.partner.bank'].search([('acc_number', '=', account_number)], limit=1) # Find the journal from context or bank account - journal_id = self._context.get('journal_id') - if bank_account_id: - bank_account = ResPartnerBank.browse(bank_account_id) - if journal_id: - if bank_account.journal_id.id and bank_account.journal_id.id != journal_id: + journal = journal_obj.browse(self._context.get('journal_id', [])) + if bank_account: + if journal: + if bank_account.journal_id and bank_account.journal_id != journal: raise UserError(_('The account of this statement is linked to another journal.')) - if not bank_account.journal_id.id: - bank_account.write({'journal_id': journal_id}) + if not bank_account.journal_id: + bank_account.write({'journal_id': journal.id}) else: - if bank_account.journal_id.id: - journal_id = bank_account.journal_id.id - - # If importing into an existing journal, its currency must be the same as the bank statement - if journal_id: - journal_currency_id = self.env['account.journal'].browse(journal_id).currency_id.id - if currency_id and currency_id != journal_currency_id: - raise UserError(_('The currency of the bank statement is not the same as the currency of the journal !')) - - # If there is no journal, create one (and its account) - if not journal_id and account_number: - company = self.env.user.company_id - journal_vals = self.env['account.journal']._prepare_bank_journal(company, {'account_type': 'bank', 'acc_name': account_number, 'currency_id': currency_id}) - journal_id = self.env['account.journal'].create(journal_vals).id - if bank_account_id: - bank_account.write({'journal_id': journal_id}) - - # If we couldn't find/create a journal, everything is lost - if not journal_id: + if bank_account.journal_id: + journal = bank_account.journal_id + + # If importing into an existing journal + if journal: + # The bank account cannot belong to another journal + if not bank_account and account_number: + journal_account = self.env['res.partner.bank'].search([('journal_id', '=', journal.id)], limit=1) + if journal_account: + raise UserError(_('You are importing a file from account %s while the account of journal %s is %s.') % (account_number, journal.name, journal_account.acc_number)) + + # Its currency must be the same as the bank statement + journal_currency = journal.currency_id + if currency == None: + currency = journal_currency + if currency and currency != journal_currency: + statement_cur_code = currency == False and company_currency.name or currency.name + journal_cur_code = not journal_currency and company_currency.name or journal_currency.name + raise UserError(_('The currency of the bank statement (%s) is not the same as the currency of the journal (%s) !') % (statement_cur_code, journal_cur_code)) + + # If we couldn't find / can't create a journal, everything is lost + if not journal and not account_number: raise UserError(_('Cannot find in which journal import this statement. Please manually select a journal.')) - return journal_id - def _create_bank_account(self, account_number, journal_id=False): + return currency, journal, bank_account + + def _create_bank_account(self, account_number): try: bank_type = self.env.ref('bank.bank_normal') bank_code = bank_type.code except ValueError: bank_code = 'bank' account_number = account_number.replace(' ', '').replace('-', '') - vals_acc = { + return self.env['res.partner.bank'].create({ 'acc_number': account_number, 'state': bank_code, - } - # Odoo users bank accounts (which we import statement from) have company_id and journal_id set - # while 'counterpart' bank accounts (from which statement transactions originate) don't. - if journal_id: - vals_acc['journal_id'] = journal_id - vals_acc['company_id'] = self.env.user.company_id.id - vals_acc['partner_id'] = self.env.user.company_id.partner_id.id - - return self.env['res.partner.bank'].create(vals_acc) + }) - def _complete_stmts_vals(self, stmts_vals, journal_id, account_number): + def _complete_stmts_vals(self, stmts_vals, journal, account_number): for st_vals in stmts_vals: - st_vals['journal_id'] = journal_id + st_vals['journal_id'] = journal.id for line_vals in st_vals['transactions']: unique_import_id = line_vals.get('unique_import_id') diff --git a/addons/account_bank_statement_import/wizard/journal_creation.py b/addons/account_bank_statement_import/wizard/journal_creation.py index d49fde7faf3f..3acd180ac50e 100644 --- a/addons/account_bank_statement_import/wizard/journal_creation.py +++ b/addons/account_bank_statement_import/wizard/journal_creation.py @@ -19,7 +19,7 @@ class account_bank_statement_import_journal_creation(models.TransientModel): currency_id = wiz.currency_id.id account_number = wiz.account_number - bank_account_id = self.env._context.get('bank_account_id') + bank_account_id = self.env.context.get('bank_account_id') if bank_account_id: vals = {'currency_id': currency_id, 'acc_name': account_number, 'account_type': 'bank'} vals_journal = journal_obj._prepare_bank_journal(company, vals) @@ -38,5 +38,5 @@ class account_bank_statement_import_journal_creation(models.TransientModel): self.env['res.partner.bank'].create(res_partner_bank_vals) # Finish the statement import - statement_import_transient = import_wiz_obj.browse(self.env._context['statement_import_transient_id']) + statement_import_transient = import_wiz_obj.browse(self.env.context['statement_import_transient_id']) return statement_import_transient.import_file() diff --git a/addons/account_bank_statement_import/wizard/journal_creation.xml b/addons/account_bank_statement_import/wizard/journal_creation.xml index 4f9c53af1a27..3894991eddbf 100644 --- a/addons/account_bank_statement_import/wizard/journal_creation.xml +++ b/addons/account_bank_statement_import/wizard/journal_creation.xml @@ -11,7 +11,7 @@ <p>Just click OK to create the account/journal and finish the import. If this was a mistake, hit cancel to abort the import.</p> <group> <field name="name"/> - <field name="currency_id" options="{'no_create': True}"/> + <field name="currency_id" options="{'no_create': True}" attrs="{'invisible': [('currency_id', '=', False)]}"/> <field name="account_number"/> </group> <footer> diff --git a/addons/account_bank_statement_import_qif/account_bank_statement_import_qif.py b/addons/account_bank_statement_import_qif/account_bank_statement_import_qif.py index 651ebff46061..5021c0e85b57 100644 --- a/addons/account_bank_statement_import_qif/account_bank_statement_import_qif.py +++ b/addons/account_bank_statement_import_qif/account_bank_statement_import_qif.py @@ -11,19 +11,18 @@ class AccountBankStatementImport(models.TransientModel): _inherit = "account.bank.statement.import" def _get_hide_journal_field(self): - return self._context and 'journal_id' in self._context or False + return self.env.context and 'journal_id' in self.env.context or False 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(string='Hide the journal field in the view', default=_get_hide_journal_field) - def _get_journal(self, currency_id, bank_account_id, account_number): + def _find_additional_data(self, *args): """ 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 self._context.get('active_id'): - record = self.browse(self._context.get('active_id')) - if record.journal_id: - return super(AccountBankStatementImport, self).with_context(journal_id=record.journal_id.id)._get_journal(currency_id, bank_account_id, account_number) - return super(AccountBankStatementImport, self)._get_journal(currency_id, bank_account_id, account_number) + We set it in context in the same way it's done when calling the import action from a journal. + """ + if self.journal_id: + self.env.context = dict(self.env.context, journal_id=self.journal_id.id) + return super(AccountBankStatementImport, self)._find_additional_data(*args) def _check_qif(self, data_file): return data_file.strip().startswith('!Type:') diff --git a/addons/account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml b/addons/account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml index 3b3a3421572e..fedd7e0ddbc7 100644 --- a/addons/account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml +++ b/addons/account_bank_statement_import_qif/account_bank_statement_import_qif_view.xml @@ -10,11 +10,9 @@ <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'}"/> + <group attrs="{'invisible': [('hide_journal_field', '=', True)]}"> + <field name="journal_id" domain="[('type', '=', 'bank')]" context="{'default_type':'bank'}"/> + </group> </xpath> </field> </record> diff --git a/addons/l10n_be/__openerp__.py b/addons/l10n_be/__openerp__.py index ec699d01bef0..b34350a15bdc 100644 --- a/addons/l10n_be/__openerp__.py +++ b/addons/l10n_be/__openerp__.py @@ -57,7 +57,6 @@ Wizards provided by this module: 'account', 'base_vat', 'base_iban', - 'l10n_be_coda', 'l10n_multilang', ], 'data': [ diff --git a/addons/l10n_be_coda/__openerp__.py b/addons/l10n_be_coda/__openerp__.py index 61ef8c758656..ed08f7e72eb1 100644 --- a/addons/l10n_be_coda/__openerp__.py +++ b/addons/l10n_be_coda/__openerp__.py @@ -71,14 +71,14 @@ description provided by the CODA configuration tables is based upon the CODA V2.2 specifications. If required, you can manually adjust the descriptions via the CODA configuration menu. ''', - 'depends': ['account_voucher', 'base_iban', 'l10n_be_invoice_bba', 'account_bank_statement_import'], + 'depends': ['account_accountant', 'l10n_be'], 'demo': [ 'l10n_be_coda_demo.xml', ], 'data': [ 'l10n_be_coda_view.xml', ], - 'auto_install': False, + 'auto_install': True, 'website': 'https://www.odoo.com/page/accounting', 'installable': True, 'license': 'AGPL-3', diff --git a/addons/l10n_be_coda/wizard/account_coda_import.py b/addons/l10n_be_coda/wizard/account_coda_import.py index 6a3902acde60..0b68639022ca 100644 --- a/addons/l10n_be_coda/wizard/account_coda_import.py +++ b/addons/l10n_be_coda/wizard/account_coda_import.py @@ -22,28 +22,23 @@ import time import re -from openerp.osv import osv -from openerp.tools.translate import _ -from openerp import tools +from openerp import models, tools, _ from openerp.exceptions import UserError -import logging +class AccountBankStatementImport(models.TransientModel): + _inherit = 'account.bank.statement.import' -_logger = logging.getLogger(__name__) - -class account_bank_statement_import(osv.TransientModel): - _inherit = "account.bank.statement.import" - - def _check_coda(self, cr, uid, data_file, context=None): + def _check_coda(self, data_file): # Matches the first 24 characters of a CODA file, as defined by the febelfin specifications return re.match('0{5}\d{9}05[ D] +', data_file) != None - def _parse_file(self, cr, uid, data_file, context=None): - if not self._check_coda(cr, uid, data_file, context=context): - return super(account_bank_statement_import, self)._parse_file(cr, uid, data_file, context=context) + def _parse_file(self, data_file): + if not self._check_coda(data_file): + return super(AccountBankStatementImport, self)._parse_file(data_file) + + def rmspaces(s): + return " ".join(s.split()) - if context is None: - context = {} recordlist = unicode(data_file, 'windows-1252', 'strict').split('\n') statements = [] globalisation_comm = {} @@ -251,5 +246,3 @@ class account_bank_statement_import(osv.TransientModel): currency_code = statement['currency'] acc_number = statements[0] and statements[0]['acc_number'] or False return currency_code, acc_number, ret_statements -def rmspaces(s): - return " ".join(s.split()) -- GitLab