From b8759cc348330f7e660d799fd26824b2e6ebc1a2 Mon Sep 17 00:00:00 2001 From: qdp-odoo Date: Fri, 22 May 2015 17:27:23 +0200 Subject: [PATCH] [REF] account, base: refactoring/improvements/fixes of bank accounts and payments --- addons/account/account.py | 78 ++++- addons/account/account_bank.py | 77 +---- addons/account/account_payment.py | 29 +- addons/account/chart_template.py | 141 +-------- addons/account/company.py | 16 - addons/account/tests/test_payment.py | 5 +- addons/account/tests/test_reconciliation.py | 4 +- addons/account/views/account_payment_view.xml | 9 +- addons/account/views/account_view.xml | 29 +- .../account_bank_statement_import.py | 15 +- .../wizard/journal_creation.py | 58 ++-- .../tests/test_import_bank_statement.py | 6 +- addons/base_iban/__openerp__.py | 3 +- addons/base_iban/base_iban.py | 292 ++++++++---------- addons/base_iban/demo/iban_demo.xml | 30 ++ addons/sale/tests/test_sale_to_invoice.py | 7 +- openerp/addons/base/base_demo.xml | 1 - openerp/addons/base/res/res_bank.py | 17 +- openerp/addons/base/res/res_bank_view.xml | 2 +- openerp/addons/base/tests/test_expression.py | 46 +-- .../addons/base/tests/test_osv_expression.yml | 2 +- 21 files changed, 374 insertions(+), 493 deletions(-) create mode 100644 addons/base_iban/demo/iban_demo.xml diff --git a/addons/account/account.py b/addons/account/account.py index 6d6abd9226d..ee0da138daf 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -299,8 +299,84 @@ class AccountJournal(models.Model): seq['company_id'] = vals['company_id'] return self.env['ir.sequence'].create(seq) + @api.model + def _prepare_bank_account(self, name, company, currency_id): + ''' + This function prepares the value to use for the creation of the default debit and credit accounts of a + bank journal (created through the wizard of generating COA from templates for example). + + :param name: name of the bank account + :param company: company for which the wizard is running + :param currency_id: ID of the currency in wich is the bank account + :return: mapping of field names and values + :rtype: dict + ''' + + # Seek the next available number for the account code + code_digits = company.accounts_code_digits or 0 + bank_account_code_char = company.bank_account_code_char or '' + for num in xrange(1, 100): + new_code = str(bank_account_code_char.ljust(code_digits - 1, '0')) + str(num) + rec = self.env['account.account'].search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1) + if not rec: + break + else: + raise UserError(_('Cannot generate an unused account code.')) + + liquidity_type = self.env.ref('account.data_account_type_liquidity') + return { + 'name': name, + 'currency_id': currency_id or False, + 'code': new_code, + 'user_type': liquidity_type and liquidity_type.id or False, + 'company_id': company.id, + } + + @api.model + def _prepare_bank_journal(self, company, line): + ''' + This function prepares the value to use for the creation of a bank journal created through the wizard of + generating COA from templates. + + :param company: company for which the wizard is running + :param line: dictionary containing the values encoded by the user related to his bank account with keys + - acc_name (char): name of the bank account + - account_type (char): kind of liquidity journal to create. Either 'bank' or 'cash' + - currency_id (int): id of the currency related to this account if its different than the company currency (False otherwise) + :return: mapping of field names and values + :rtype: dict + ''' + # we need to loop to find next number for journal code + for num in xrange(1, 100): + # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100 + journal_code = line.get('account_type', 'bank') == 'cash' and 'CSH' or 'BNK' + journal_code += str(num) + journal = self.env['account.journal'].search([('code', '=', journal_code), ('company_id', '=', company.id)], limit=1) + if not journal: + break + else: + raise UserError(_('Cannot generate an unused journal code.')) + + return { + 'name': line['acc_name'], + 'code': journal_code, + 'type': line.get('account_type', 'bank'), + 'company_id': company.id, + 'analytic_journal_id': False, + 'currency_id': line.get('currency_id', False), + 'show_on_dashboard': True, + } + @api.model def create(self, vals): + if vals.get('type') in ('bank', 'cash'): + default_account = vals.get('default_debit_account_id') or vals.get('default_credit_account_id') + if not default_account: + company = self.env['res.company'].browse(vals['company_id']) + account_vals = self._prepare_bank_account(vals.get('name'), company, vals.get('currency_id')) + default_account = self.env['account.account'].create(account_vals) + vals['default_debit_account_id'] = default_account.id + vals['default_credit_account_id'] = default_account.id # We just need to create the relevant sequences according to the chosen options if not vals.get('sequence_id'): vals.update({'sequence_id': self.sudo()._create_sequence(vals).id}) @@ -555,4 +631,4 @@ class AccountOperationTemplate(models.Model): @api.onchange('name') def onchange_name(self): - self.label = self.name \ No newline at end of file + self.label = self.name diff --git a/addons/account/account_bank.py b/addons/account/account_bank.py index e93847f4465..96b69d81bab 100644 --- a/addons/account/account_bank.py +++ b/addons/account/account_bank.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from openerp import api, fields, models, _ +from openerp import api, fields, models class Bank(models.Model): @@ -8,8 +8,6 @@ class Bank(models.Model): journal_id = fields.Many2one('account.journal', string='Account Journal', help="This journal will be created automatically for this bank account when you save the record") - currency_id = fields.Many2one('res.currency', related='journal_id.currency_id', string='Currency', - readonly=True, help="Currency of the related account journal.") @api.model def create(self, data): @@ -26,70 +24,21 @@ class Bank(models.Model): @api.model def _prepare_name(self, bank): "Return the name to use when creating a bank journal" - return (bank.bank_name or '') + ' ' + (bank.acc_number or '') - - @api.model - def _prepare_name_get(self, bank_dicts): - """Add ability to have %(currency_name)s in the format_layout of res.partner.bank.type""" - currency_ids = list(set(data['currency_id'][0] for data in bank_dicts if data.get('currency_id'))) - currencies = self.env['res.currency'].browse(currency_ids) - currency_name = dict((currency.id, currency.name) for currency in currencies) - - for data in bank_dicts: - data['currency_name'] = data.get('currency_id') and currency_name[data['currency_id'][0]] or '' - return super(Bank, self)._prepare_name_get(bank_dicts) + name = bank.bank_name + ' ' if bank.bank_name else '' + name += bank.acc_number + return name @api.multi def post_write(self): - AccountObj = self.env['account.account'] JournalObj = self.env['account.journal'] - for bank in self: # Create a journal for the bank account if it belongs to the company. - if bank.partner_id in self.env.user.company_ids.mapped('partner_id') and not bank.journal_id: - # TODO: this code is duplicated; refactor using _prepare_bank_account() in chart_template.py - # Find the code and parent of the bank account to create - dig = 6 - current_num = 1 - account = AccountObj.search([('internal_type', '=', 'liquidity'), ('company_id', '=', bank.company_id.id)], limit=1) - # No liquidity account exists, no template available - if not account: continue - - ref_acc_bank = account - while True: - new_code = str(ref_acc_bank.code.ljust(dig-len(str(current_num)), '0')) + str(current_num) - account = AccountObj.search([('code', '=', new_code), ('company_id', '=', bank.company_id.id)], limit=1) - if not account: - break - current_num += 1 - name = self._prepare_name(bank) - acc = { - 'name': name, - 'code': new_code, - 'user_type': ref_acc_bank.user_type.id, - 'reconcile': False, - 'company_id': bank.company_id.id, - } - acc_bank = AccountObj.create(acc) - - new_code = 1 - while True: - code = _('BNK')+str(new_code) - account = JournalObj.search([('code', '=', code)], limit=1) - if not account: - break - new_code += 1 - - #create the bank journal - vals_journal = { - 'name': name, - 'code': code, - 'type': 'bank', - 'company_id': bank.company_id.id, - 'analytic_journal_id': False, - 'default_credit_account_id': acc_bank.id, - 'default_debit_account_id': acc_bank.id, - } - - bank.journal_id = JournalObj.create(vals_journal) - return True + if bank.company_id and not bank.journal_id: + journal_vals = JournalObj._prepare_bank_journal(bank.company_id, {'acc_name': self._prepare_name(bank), 'currency_id': bank.currency_id.id, 'account_type': 'bank'}) + journal = JournalObj.create(journal_vals) + missing_vals = {'journal_id': journal.id} + if not bank.partner_id: + missing_vals['partner_id'] = bank.company_id.partner_id.id + if not bank.owner_name: + missing_vals['owner_name'] = bank.company_id.partner_id.name + bank.write(missing_vals) diff --git a/addons/account/account_payment.py b/addons/account/account_payment.py index 7201e3a9bce..10b9faa182d 100644 --- a/addons/account/account_payment.py +++ b/addons/account/account_payment.py @@ -32,6 +32,8 @@ class account_abstract_payment(models.AbstractModel): payment_type = fields.Selection([('outbound', 'Send Money'), ('inbound', 'Receive Money')], default='outbound', required=True) payment_method = fields.Many2one('account.payment.method', string='Payment Method', required=True) + payment_method_code = fields.Char(related='payment_method.code', + help="Technical field used to adapt the interface to the payment method selected.") partner_type = fields.Selection([('customer', 'Customer'), ('supplier', 'Supplier')], default='supplier') partner_id = fields.Many2one('res.partner', string='Partner') @@ -52,18 +54,6 @@ class account_abstract_payment(models.AbstractModel): if not self.amount > 0.0: raise ValidationError('The payment amount must be strictly positive.') - @api.one - @api.constrains('communication') - def _check_communication(self): - """ This method is to be overwritten by payment type modules. The method body would look like : - if self.payment_method == self.env.ref('my_module.payment_method'): - try: - self.communication.decode('ascii') - except UnicodeDecodeError: - raise ValidationError(_t("The communication cannot contain any special character")) - """ - pass - @api.one @api.depends('payment_type', 'journal_id') def _compute_hide_payment_method(self): @@ -272,6 +262,21 @@ class account_payment(models.Model): def _get_invoices(self): return self.invoice_ids + @api.model + def create(self, vals): + self._check_communication(vals['payment_method'], vals.get('communication', '')) + return super(account_payment, self).create(vals) + + def _check_communication(self, payment_method_id, communication): + """ This method is to be overwritten by payment type modules. The method body would look like : + if payment_method_id == self.env.ref('my_module.payment_method').id: + try: + communication.decode('ascii') + except UnicodeError: + raise ValidationError(_("The communication cannot contain any special character")) + """ + pass + @api.multi def button_journal_entries(self): return { diff --git a/addons/account/chart_template.py b/addons/account/chart_template.py index 90f5fee6e14..42495463e52 100644 --- a/addons/account/chart_template.py +++ b/addons/account/chart_template.py @@ -50,55 +50,6 @@ class AccountAccountTemplate(models.Model): return res -class AccountAddTmplWizard(models.TransientModel): - """Add one more account from the template. - - With the 'nocreate' option, some accounts may not be created. Use this to add them later.""" - _name = 'account.addtmpl.wizard' - - @api.model - def _get_def_cparent(self): - context = self._context or {} - tmpl_obj = self.env['account.account.template'] - - tids = tmpl_obj.read([context['tmpl_ids']], ['parent_id']) - if not tids or not tids[0]['parent_id']: - return False - ptids = tmpl_obj.read([tids[0]['parent_id'][0]], ['code']) - account = False - if not ptids or not ptids[0]['code']: - raise UserError(_('There is no parent code for the template account.')) - account = self.env['account.account'].search([('code', '=', ptids[0]['code'])], limit=1) - return account - - cparent_id = fields.Many2one('account.account', string='Parent target', default=lambda self: self._get_def_cparent(), - help="Creates an account with the selected template under this existing parent.", required=True, domain=[('deprecated', '=', False)]) - - @api.multi - def action_create(self): - context = self._context or {} - AccountObj = self.env['account.account'] - data = self.read()[0] - company_id = AccountObj.read([data['cparent_id'][0]], ['company_id'])[0]['company_id'][0] - account_template = self.env['account.account.template'].browse(context['tmpl_ids']) - vals = { - 'name': account_template.name, - 'currency_id': account_template.currency_id and account_template.currency_id.id or False, - 'code': account_template.code, - 'user_type': account_template.user_type and account_template.user_type.id or False, - 'reconcile': account_template.reconcile, - 'note': account_template.note, - 'parent_id': data['cparent_id'][0], - 'company_id': company_id, - } - AccountObj.create(vals) - return {'type': 'state', 'state': 'end'} - - @api.multi - def action_cancel(self): - return {'type': 'state', 'state': 'end'} - - class AccountChartTemplate(models.Model): _name = "account.chart.template" _description = "Templates for Account Chart" @@ -776,74 +727,6 @@ class WizardMultiChartsAccounts(models.TransientModel): self._create_bank_journals_from_o2m(company, acc_template_ref) return {} - @api.model - def _prepare_bank_journal(self, company, line, default_account_id): - ''' - This function prepares the value to use for the creation of a bank journal created through the wizard of - generating COA from templates. - - :param line: dictionary containing the values encoded by the user related to his bank account - :param default_account_id: id of the default debit.credit account created before for this journal. - :param company_id: id of the company for which the wizard is running - :return: mapping of field names and values - :rtype: dict - ''' - # we need to loop to find next number for journal code - for num in xrange(1, 100): - # journal_code has a maximal size of 5, hence we can enforce the boundary num < 100 - journal_code = line['account_type'] == 'cash' and 'CSH' or 'BNK' - journal_code += str(num) - journal = self.env['account.journal'].search([('code', '=', journal_code), ('company_id', '=', company.id)], limit=1) - if not journal: - break - else: - raise UserError(_('Cannot generate an unused journal code.')) - - return { - 'name': line['acc_name'], - 'code': journal_code, - 'type': line['account_type'], - 'company_id': company.id, - 'analytic_journal_id': False, - 'currency_id': line['currency_id'] or False, - 'default_credit_account_id': default_account_id, - 'default_debit_account_id': default_account_id, - 'show_on_dashboard': True, - } - - @api.model - def _prepare_bank_account(self, company, line): - ''' - This function prepares the value to use for the creation of the default debit and credit accounts of a - bank journal created through the wizard of generating COA from templates. - - :param company: company for which the wizard is running - :param line: dictionary containing the values encoded by the user related to his bank account - :return: mapping of field names and values - :rtype: dict - ''' - - # Seek the next available number for the account code - code_digits = company.accounts_code_digits or 0 - bank_account_code_char = company.bank_account_code_char or '' - for num in xrange(1, 100): - new_code = str(bank_account_code_char.ljust(code_digits - 1, '0')) + str(num) - rec = self.env['account.account'].search([('code', '=', new_code), ('company_id', '=', company.id)], limit=1) - if not rec: - break - else: - raise UserError(_('Cannot generate an unused account code.')) - - liquidity_type = self.env.ref('account.data_account_type_liquidity') - - return { - 'name': line['acc_name'], - 'currency_id': line['currency_id'] or False, - 'code': new_code, - 'user_type': liquidity_type and liquidity_type.id or False, - 'company_id': company.id, - } - @api.multi def _create_bank_journals_from_o2m(self, company, acc_template_ref): ''' @@ -872,13 +755,21 @@ class WizardMultiChartsAccounts(models.TransientModel): company.write({'bank_account_code_char': ref_acc_bank}) for line in journal_data: - # Create the default debit/credit accounts for this bank journal - vals = self._prepare_bank_account(company, line) - default_account = self.env['account.account'].create(vals) - - #create the bank journal - vals_journal = self._prepare_bank_journal(company, line, default_account.id) - self.env['account.journal'].create(vals_journal) + if line['account_type'] == 'bank': + #create the bank account that will trigger the journal and account.account creation + res_partner_bank_vals = { + 'acc_number': line['acc_name'], + 'currency_id': line['currency_id'], + 'company_id': company.id, + 'owner_name': company.partner_id.name, + 'partner_id': company.partner_id.id, + 'footer': True + } + self.env['res.partner.bank'].create(res_partner_bank_vals) + else: + #create the cash journal that will trigger the account.account creation + vals_journal = self.env['account.journal']._prepare_bank_journal(company, line) + self.env['account.journal'].create(vals_journal) return True @@ -889,4 +780,4 @@ class AccountBankAccountsWizard(models.TransientModel): bank_account_id = fields.Many2one('wizard.multi.charts.accounts', string='Bank Account', required=True, ondelete='cascade') currency_id = fields.Many2one('res.currency', string='Secondary Currency', help="Forces all moves for this account to have this secondary currency.") - account_type = fields.Selection([('cash', 'Cash'), ('bank', 'Bank')]) \ No newline at end of file + account_type = fields.Selection([('cash', 'Cash'), ('bank', 'Bank')]) diff --git a/addons/account/company.py b/addons/account/company.py index c78ad5f34bb..2ea1c6a7c00 100644 --- a/addons/account/company.py +++ b/addons/account/company.py @@ -32,22 +32,6 @@ class ResCompany(models.Model): string="Loss Exchange Rate Account", domain="[('internal_type', '=', 'other'), ('deprecated', '=', False), ('company_id', '=', id)]") anglo_saxon_accounting = fields.Boolean(string="Use anglo-saxon accounting") - @api.multi - def _create_bank_account_and_journal(self, account_number, currency_id=None): - """ Create a journal and its account """ - MultiChartsAccounts = self.env['wizard.multi.charts.accounts'] - - if currency_id is None: - currency_id = self.currency_id.id - - vals_account = {'currency_id': currency_id, 'acc_name': account_number, 'account_type': 'bank'} - vals_account = MultiChartsAccounts._prepare_bank_account(self, vals_account) - account_id = self.env['account.account'].create(vals_account).id - - vals_journal = {'currency_id': currency_id, 'acc_name': _('Bank') + ' ' + account_number, 'account_type': 'bank'} - vals_journal = MultiChartsAccounts._prepare_bank_journal(self, vals_journal, account_id) - return self.env['account.journal'].create(vals_journal).id - @api.multi def compute_fiscalyear_dates(self, date): """ Computes the start and end dates of the fiscalyear where the given 'date' belongs to diff --git a/addons/account/tests/test_payment.py b/addons/account/tests/test_payment.py index 74c1e7dd95e..00ec04c25e1 100644 --- a/addons/account/tests/test_payment.py +++ b/addons/account/tests/test_payment.py @@ -26,11 +26,11 @@ class TestPayment(AccountingTestCase): self.account_payable = self.env['account.account'].search([('user_type', '=', self.env.ref('account.data_account_type_payable').id)])[0] self.account_revenue = self.env['account.account'].search([('user_type', '=', self.env.ref('account.data_account_type_revenue').id)])[0] - self.bank_euro = self.env['res.partner.bank'].create({'acc_number': '0123456789', 'bank_name': 'Test Bank'}) + self.bank_euro = self.env['res.partner.bank'].create({'acc_number': '0123456789', 'bank_name': 'Test Bank', 'company_id': self.env.user.company_id.id}) self.bank_journal_euro = self.bank_euro.journal_id self.account_eur = self.bank_journal_euro.default_debit_account_id - self.bank_usd = self.env['res.partner.bank'].create({'acc_number': '0123456789', 'bank_name': 'Test Bank USD'}) + self.bank_usd = self.env['res.partner.bank'].create({'acc_number': '0123456789', 'bank_name': 'Test Bank USD', 'company_id': self.env.user.company_id.id}) self.bank_journal_usd = self.bank_usd.journal_id self.account_usd = self.bank_journal_usd.default_debit_account_id self.account_usd.write({'currency_id': self.currency_usd_id}) @@ -40,7 +40,6 @@ class TestPayment(AccountingTestCase): self.diff_income_account = self.env['res.users'].browse(self.env.uid).company_id.income_currency_exchange_account_id self.diff_expense_account = self.env['res.users'].browse(self.env.uid).company_id.expense_currency_exchange_account_id - def create_invoice(self, amount=100, type='out_invoice', currency_id=None): """ Returns an open invoice """ invoice = self.invoice_model.create({ diff --git a/addons/account/tests/test_reconciliation.py b/addons/account/tests/test_reconciliation.py index 8442334c035..056232e701d 100644 --- a/addons/account/tests/test_reconciliation.py +++ b/addons/account/tests/test_reconciliation.py @@ -25,12 +25,12 @@ class TestReconciliation(AccountingTestCase): self.account_rsa = self.env['account.account'].search([('user_type', '=', self.env.ref('account.data_account_type_payable').id)])[0] self.product = self.env.ref("product.product_product_4") - self.bank_euro = self.env['res.partner.bank'].create({'acc_number': 'Reconciliation test', 'bank_name': 'Test Bank'}) + self.bank_euro = self.env['res.partner.bank'].create({'acc_number': 'Reconciliation test', 'bank_name': 'Test Bank', 'company_id': self.env.user.company_id.id}) self.bank_journal_euro = self.bank_euro.journal_id self.account_euro = self.bank_journal_euro.default_debit_account_id self.bank_usd = self.env['res.partner.bank'].create({'acc_number': 'Reconciliation test USD', - 'bank_name': 'Test Bank USD'}) + 'bank_name': 'Test Bank USD', 'company_id': self.env.user.company_id.id}) self.bank_journal_usd = self.bank_usd.journal_id self.account_usd = self.bank_journal_usd.default_debit_account_id self.account_usd.write({'currency_id': self.currency_usd_id}) diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml index cd14deef6b7..20b1d81ae1e 100644 --- a/addons/account/views/account_payment_view.xml +++ b/addons/account/views/account_payment_view.xml @@ -82,8 +82,9 @@ +