From b8759cc348330f7e660d799fd26824b2e6ebc1a2 Mon Sep 17 00:00:00 2001 From: qdp-odoo <qdp@odoo.com> 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 | 306 ++++++++---------- 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, 381 insertions(+), 500 deletions(-) create mode 100644 addons/base_iban/demo/iban_demo.xml diff --git a/addons/account/account.py b/addons/account/account.py index 6d6abd9226dc..ee0da138dafb 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 e93847f4465a..96b69d81babd 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 7201e3a9bcee..10b9faa182d9 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 90f5fee6e14b..42495463e528 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 c78ad5f34bbc..2ea1c6a7c004 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 74c1e7dd95ee..00ec04c25e10 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 8442334c0355..056232e701db 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 cd14deef6b74..20b1d81ae1ee 100644 --- a/addons/account/views/account_payment_view.xml +++ b/addons/account/views/account_payment_view.xml @@ -82,8 +82,9 @@ <field name="destination_journal_id" widget="selection" attrs="{'required': [('payment_type', '=', 'transfer')], 'invisible': [('payment_type', '!=', 'transfer')], 'readonly': [('state', '!=', 'draft')]}"/> <field name="hide_payment_method" invisible="1"/> <field name="payment_method" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)], 'readonly': [('state', '!=', 'draft')]}" String="Payment Mode"/> + <field name="payment_method_code" invisible="1"/> <label for="amount"/> - <div> + <div name="amount_div"> <field name="amount" class="oe_inline" attrs="{'readonly': [('state', '!=', 'draft')]}"/> <field name="currency_id" groups="base.group_multi_currency" class="oe_inline" attrs="{'readonly': [('state', '!=', 'draft')]}"/> </div> @@ -161,8 +162,9 @@ <field name="journal_id" widget="selection"/> <field name="hide_payment_method" invisible="1"/> <field name="payment_method" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)]}"/> + <field name="payment_method_code" invisible="1"/> <label for="amount"/> - <div> + <div name="amount_div"> <field name="amount" class="oe_inline"/> <field name="currency_id" groups="base.group_multi_currency" class="oe_inline"/> </div> @@ -209,11 +211,14 @@ <field name="arch" type="xml"> <form string="Register Payment" version="7"> <field name="payment_type" invisible="1"/> + <field name="partner_type" invisible="1"/> + <field name="partner_id" invisible="1"/> <group> <group> <field name="journal_id" widget="selection"/> <field name="hide_payment_method" invisible="1"/> <field name="payment_method" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)]}"/> + <field name="payment_method_code" invisible="1"/> <label for="amount" /> <div> <field name="amount" class="oe_inline"/> diff --git a/addons/account/views/account_view.xml b/addons/account/views/account_view.xml index fe6897299578..2413d12d2528 100644 --- a/addons/account/views/account_view.xml +++ b/addons/account/views/account_view.xml @@ -254,14 +254,14 @@ <field name="type"/> </group> <group> - <field name="default_debit_account_id" attrs="{'required':[('type','in', ('cash', 'bank'))]}" domain="[('deprecated', '=', False)]"/> - <field name="default_credit_account_id" attrs="{'required':[('type','in',('cash', 'bank'))]}" domain="[('deprecated', '=', False)]"/> + <field name="default_debit_account_id" domain="[('deprecated', '=', False)]"/> + <field name="default_credit_account_id" domain="[('deprecated', '=', False)]"/> <field name="currency_id" groups="base.group_multi_currency"/> <field name="company_id" groups="base.group_multi_company"/> </group> </group> <notebook> - <page string="Advanced Settings"> + <page name="advanced_settings" string="Advanced Settings"> <group> <group> <field name="type_control_ids" widget="many2many_tags"/> @@ -1456,29 +1456,6 @@ <field name="search_view_id" ref="view_account_template_search"/> </record> - <record id="view_account_addtmpl_wizard_form" model="ir.ui.view"> - <field name="name">Create Account</field> - <field name="model">account.addtmpl.wizard</field> - <field name="arch" type="xml"> - <form string="Create Account"> - <header> - <button icon="gtk-ok" name="action_create" string="Add" type="object" class="oe_highlight" /> - </header> - <separator col="4" colspan="4" string="Create an Account Based on this Template"/> - <field name="cparent_id"/> - </form> - </field> - </record> - - <act_window domain="[]" id="action_account_addtmpl_wizard_form" - name="Create Account" - target="new" - res_model="account.addtmpl.wizard" - context="{'tmpl_ids': active_id}" - src_model="account.account.template" - view_type="form" view_mode="form"/> - - <!-- Chart of Accounts Templates --> <record id="view_account_chart_template_form" model="ir.ui.view"> <field name="name">account.chart.template.form</field> 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 c1dafd56ca53..893c1fae0ba7 100644 --- a/addons/account_bank_statement_import/account_bank_statement_import.py +++ b/addons/account_bank_statement_import/account_bank_statement_import.py @@ -44,7 +44,7 @@ class AccountBankStatementImport(models.TransientModel): 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, partner_id=self.env.uid) + rec._create_bank_account(account_number, journal_id=journal_id) # Prepare statement data to be used for bank statements creation stmts_vals = rec._complete_stmts_vals(stmts_vals, journal_id, account_number) # Create the bank statements @@ -145,7 +145,9 @@ class AccountBankStatementImport(models.TransientModel): # If there is no journal, create one (and its account) if not journal_id and account_number: - journal_id = self.env.user.company_id._create_bank_account_and_journal(account_number, currency_id) + 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}) @@ -154,7 +156,7 @@ class AccountBankStatementImport(models.TransientModel): 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, partner_id=False): + def _create_bank_account(self, account_number, journal_id=False): try: bank_type = self.env.ref('bank.bank_normal') bank_code = bank_type.code @@ -167,11 +169,10 @@ class AccountBankStatementImport(models.TransientModel): } # 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. - # Warning : if company_id is set, the method post_write of class bank will create a journal if journal_id: - vals_acc['partner_id'] = self.env.uid 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) @@ -184,7 +185,7 @@ class AccountBankStatementImport(models.TransientModel): if unique_import_id: line_vals['unique_import_id'] = (account_number and account_number + '-' or '') + unique_import_id - if not 'bank_account_id' in line_vals or not line_vals['bank_account_id']: + if not line_vals.get('bank_account_id'): # Find the partner and his bank account or create the bank account. The partner selected during the # reconciliation process will be linked to the bank when the statement is closed. partner_id = False @@ -196,6 +197,8 @@ class AccountBankStatementImport(models.TransientModel): bank_account_id = partner_bank.id partner_id = partner_bank.partner_id.id else: + #do not pass the journal_id in _create_bank_account() because we don't want to link + #that bank_account to the journal (it belongs to a partner, not to the company) bank_account_id = self._create_bank_account(identifying_string).id line_vals['partner_id'] = partner_id line_vals['bank_account_id'] = bank_account_id diff --git a/addons/account_bank_statement_import/wizard/journal_creation.py b/addons/account_bank_statement_import/wizard/journal_creation.py index 547afda23951..d49fde7faf3f 100644 --- a/addons/account_bank_statement_import/wizard/journal_creation.py +++ b/addons/account_bank_statement_import/wizard/journal_creation.py @@ -1,42 +1,42 @@ # -*- coding: utf-8 -*- -from openerp.osv import fields, osv -from openerp.tools.translate import _ +from openerp import api, fields, models -class account_bank_statement_import_journal_creation(osv.TransientModel): +class account_bank_statement_import_journal_creation(models.TransientModel): _name = 'account.bank.statement.import.journal.creation' _description = 'Import Bank Statement Journal Creation Wizard' - _columns = { - 'name': fields.char('Journal Name', required=True), - 'currency_id': fields.many2one('res.currency', 'Currency', readonly=True), - 'account_number': fields.char('Account Number', readonly=True), - } - def create_journal(self, cr, uid, ids, context=None): - bank_account_id = context['bank_account_id'] + name = fields.Char('Journal Name', required=True) + currency_id = fields.Many2one('res.currency', 'Currency', readonly=True) + account_number = fields.Char('Account Number', readonly=True) - wmca_pool = self.pool.get('wizard.multi.charts.accounts') - import_wiz_obj = self.pool['account.bank.statement.import'] - company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id - wiz = self.browse(cr, uid, ids, context=context)[0] + @api.multi + def create_journal(self): + import_wiz_obj = self.env['account.bank.statement.import'] + journal_obj = self.env['account.journal'] + company = self.env.user.company_id + wiz = self[0] currency_id = wiz.currency_id.id account_number = wiz.account_number - # Create the account.account - vals_account = {'currency_id': currency_id, 'acc_name': account_number, 'account_type': 'bank', 'currency_id': currency_id} - vals_account = wmca_pool._prepare_bank_account(cr, uid, company, vals_account, context=context) - account_id = self.pool.get('account.account').create(cr, uid, vals_account, context=context) - - # Create the account.journal - name = wiz.name - vals_journal = {'currency_id': currency_id, 'acc_name': name, 'account_type': 'bank'} - vals_journal = wmca_pool._prepare_bank_journal(cr, uid, company, vals_journal, account_id, context=context) - journal_id = self.pool.get('account.journal').create(cr, uid, vals_journal, context=context) + bank_account_id = self.env._context.get('bank_account_id') if bank_account_id: - self.pool.get('res.partner.bank').write(cr, uid, [bank_account_id], {'journal_id': journal_id}, context=context) - # Create the bank account if not already existing - elif not bank_account_id and account_number: - import_wiz_obj._create_bank_account(cr, uid, account_number, journal_id=journal_id, partner_id=uid, context=context) + vals = {'currency_id': currency_id, 'acc_name': account_number, 'account_type': 'bank'} + vals_journal = journal_obj._prepare_bank_journal(company, vals) + journal = journal_obj.create(vals_journal) + self.env['res.partner.bank'].browse(bank_account_id).write({'journal_id': journal.id}) + else: + #create the bank account that will trigger the journal and account.account creation + res_partner_bank_vals = { + 'acc_number': account_number, + 'currency_id': 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) + # Finish the statement import - statement_import_transient = import_wiz_obj.browse(cr, uid, context['statement_import_transient_id'], context=context) + 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_qif/tests/test_import_bank_statement.py b/addons/account_bank_statement_import_qif/tests/test_import_bank_statement.py index 7768e9a4a71e..6aa6b79b354e 100644 --- a/addons/account_bank_statement_import_qif/tests/test_import_bank_statement.py +++ b/addons/account_bank_statement_import_qif/tests/test_import_bank_statement.py @@ -19,7 +19,9 @@ class TestQifFile(TransactionCase): bank_statement_id = self.BankStatementImport.create(dict( data_file=qif_file, )) - journal = self.env.user.company_id._create_bank_account_and_journal('Bank Account (test import qif)') - bank_statement_id.with_context(journal_id=journal).import_file() + company = self.env.user.company_id + journal_vals = self.env['account.journal']._prepare_bank_journal(company, {'account_type': 'bank', 'acc_name': 'Bank Account (test import qif)'}) + journal = self.env['account.journal'].create(journal_vals) + bank_statement_id.with_context(journal_id=journal.id).import_file() line = self.BankStatementLine.search([('name', '=', 'YOUR LOCAL SUPERMARKET')], limit=1) assert float_compare(line.statement_id.balance_end_real, -1896.09, 2) == 0 diff --git a/addons/base_iban/__openerp__.py b/addons/base_iban/__openerp__.py index b7196edbded0..e7c3d4d718ba 100644 --- a/addons/base_iban/__openerp__.py +++ b/addons/base_iban/__openerp__.py @@ -26,12 +26,13 @@ This module installs the base for IBAN (International Bank Account Number) bank accounts and checks for it's validity. ====================================================================================================================== -The ability to extract the correctly represented local accounts from IBAN accounts +The ability to extract the correctly represented local accounts from IBAN accounts with a single statement. """, 'author': 'OpenERP SA', 'website': 'https://www.odoo.com', 'depends': ['base'], + 'demo': ['demo/iban_demo.xml'], 'data': ['base_iban_data.xml' , 'base_iban_view.xml'], 'installable': True, 'auto_install': False, diff --git a/addons/base_iban/base_iban.py b/addons/base_iban/base_iban.py index 3a48afe092a1..ccc6b123daa5 100644 --- a/addons/base_iban/base_iban.py +++ b/addons/base_iban/base_iban.py @@ -1,182 +1,144 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -############################################################################## -import string - -from openerp.osv import fields, osv -from openerp.tools.translate import _ - -# Reference Examples of IBAN -_ref_iban = { 'al':'ALkk BBBS SSSK CCCC CCCC CCCC CCCC', 'ad':'ADkk BBBB SSSS CCCC CCCC CCCC', -'at':'ATkk BBBB BCCC CCCC CCCC', 'be': 'BEkk BBBC CCCC CCKK', 'ba': 'BAkk BBBS SSCC CCCC CCKK', -'bg': 'BGkk BBBB SSSS DDCC CCCC CC', 'bh': 'BHkk BBBB SSSS SSSS SSSS SS', -'cr': 'CRkk BBBC CCCC CCCC CCCC C', -'hr': 'HRkk BBBB BBBC CCCC CCCC C', 'cy': 'CYkk BBBS SSSS CCCC CCCC CCCC CCCC', -'cz': 'CZkk BBBB SSSS SSCC CCCC CCCC', 'dk': 'DKkk BBBB CCCC CCCC CC', -'do': 'DOkk BBBB CCCC CCCC CCCC CCCC CCCC', - 'ee': 'EEkk BBSS CCCC CCCC CCCK', 'fo': 'FOkk CCCC CCCC CCCC CC', - 'fi': 'FIkk BBBB BBCC CCCC CK', 'fr': 'FRkk BBBB BGGG GGCC CCCC CCCC CKK', - 'ge': 'GEkk BBCC CCCC CCCC CCCC CC', 'de': 'DEkk BBBB BBBB CCCC CCCC CC', - 'gi': 'GIkk BBBB CCCC CCCC CCCC CCC', 'gr': 'GRkk BBBS SSSC CCCC CCCC CCCC CCC', - 'gl': 'GLkk BBBB CCCC CCCC CC', 'hu': 'HUkk BBBS SSSC CCCC CCCC CCCC CCCC', - 'is':'ISkk BBBB SSCC CCCC XXXX XXXX XX', 'ie': 'IEkk BBBB SSSS SSCC CCCC CC', - 'il': 'ILkk BBBS SSCC CCCC CCCC CCC', 'it': 'ITkk KBBB BBSS SSSC CCCC CCCC CCC', - 'kz': 'KZkk BBBC CCCC CCCC CCCC', 'kw': 'KWkk BBBB CCCC CCCC CCCC CCCC CCCC CC', - 'lv': 'LVkk BBBB CCCC CCCC CCCC C', -'lb': 'LBkk BBBB CCCC CCCC CCCC CCCC CCCC', 'li': 'LIkk BBBB BCCC CCCC CCCC C', -'lt': 'LTkk BBBB BCCC CCCC CCCC', 'lu': 'LUkk BBBC CCCC CCCC CCCC' , -'mk': 'MKkk BBBC CCCC CCCC CKK', 'mt': 'MTkk BBBB SSSS SCCC CCCC CCCC CCCC CCC', -'mr': 'MRkk BBBB BSSS SSCC CCCC CCCC CKK', -'mu': 'MUkk BBBB BBSS CCCC CCCC CCCC CCCC CC', 'mc': 'MCkk BBBB BGGG GGCC CCCC CCCC CKK', -'me': 'MEkk BBBC CCCC CCCC CCCC KK', -'nl': 'NLkk BBBB CCCC CCCC CC', 'no': 'NOkk BBBB CCCC CCK', -'pl':'PLkk BBBS SSSK CCCC CCCC CCCC CCCC', -'pt': 'PTkk BBBB SSSS CCCC CCCC CCCK K', 'ro': 'ROkk BBBB CCCC CCCC CCCC CCCC', -'sm': 'SMkk KBBB BBSS SSSC CCCC CCCC CCC', 'sa': 'SAkk BBCC CCCC CCCC CCCC CCCC', -'rs': 'RSkk BBBC CCCC CCCC CCCC KK', 'sk': 'SKkk BBBB SSSS SSCC CCCC CCCC', -'si': 'SIkk BBSS SCCC CCCC CKK', 'es': 'ESkk BBBB SSSS KKCC CCCC CCCC', -'se': 'SEkk BBBB CCCC CCCC CCCC CCCC', 'ch': 'CHkk BBBB BCCC CCCC CCCC C', -'tn': 'TNkk BBSS SCCC CCCC CCCC CCCC', 'tr': 'TRkk BBBB BRCC CCCC CCCC CCCC CC', -'ae': 'AEkk BBBC CCCC CCCC CCCC CCC', -'gb': 'GBkk BBBB SSSS SSCC CCCC CC', -} -def _format_iban(iban_str): - ''' - This function removes all characters from given 'iban_str' that isn't a alpha numeric and converts it to upper case. - ''' - res = "" - if iban_str: - for char in iban_str: - if char.isalnum(): - res += char.upper() - return res - -def _pretty_iban(iban_str): - "return iban_str in groups of four characters separated by a single space" - res = [] - while iban_str: - res.append(iban_str[:4]) - iban_str = iban_str[4:] - return ' '.join(res) - -class res_partner_bank(osv.osv): +import re + +from openerp import models, api, _ +from openerp.exceptions import UserError, ValidationError + + +def normalize_iban(iban): + return re.sub('[\W_]', '', iban) + +def pretty_iban(iban): + """ return iban in groups of four characters separated by a single space """ + return ' '.join([iban[i:i + 4] for i in range(0, len(iban), 4)]) + +def get_bban_from_iban(iban): + """ Returns the basic bank account number corresponding to an IBAN. + Note : the BBAN is not the same as the domestic bank account number ! + The relation between IBAN, BBAN and domestic can be found here : http://www.ecbs.org/iban.htm + """ + return normalize_iban(iban)[4:] + +def validate_iban(iban): + iban = normalize_iban(iban) + if not iban: + raise ValidationError(_("No IBAN !")) + + country_code = iban[:2].lower() + if country_code not in _map_iban_template: + raise ValidationError(_("The IBAN is invalid, it should begin with the country code")) + + iban_template = _map_iban_template[country_code] + if len(iban) != len(iban_template.replace(' ', '')): + raise ValidationError(_("The IBAN does not seem to be correct. You should have entered something like this %s\n"\ + "Where B = National bank code, S = Branch code, C = Account No, k = Check digit" % iban_template)) + + check_chars = iban[4:] + iban[:4] + digits = int(''.join(str(int(char, 36)) for char in check_chars)) # BASE 36: 0..9,A..Z -> 0..35 + if digits % 97 != 1: + raise ValidationError(_("This IBAN does not pass the validation check, please verify it.")) + + +class res_partner_bank(models.Model): _inherit = "res.partner.bank" + def get_bban(self): + if self.state != 'iban': + raise UserError(_("Cannot compute the BBAN because the account number is not an IBAN.")) + return get_bban_from_iban(self.acc_number) + def create(self, cr, uid, vals, context=None): - #overwrite to format the iban number correctly - if (vals.get('state',False)=='iban') and vals.get('acc_number', False): - vals['acc_number'] = _format_iban(vals['acc_number']) - vals['acc_number'] = _pretty_iban(vals['acc_number']) + if (vals.get('state') == 'iban') and vals.get('acc_number'): + vals['acc_number'] = pretty_iban(normalize_iban(vals['acc_number'])) return super(res_partner_bank, self).create(cr, uid, vals, context) def write(self, cr, uid, ids, vals, context=None): - #overwrite to format the iban number correctly - if (vals.get('state',False)=='iban') and vals.get('acc_number', False): - vals['acc_number'] = _format_iban(vals['acc_number']) - vals['acc_number'] = _pretty_iban(vals['acc_number']) + if (vals.get('state') == 'iban') and vals.get('acc_number'): + vals['acc_number'] = pretty_iban(normalize_iban(vals['acc_number'])) return super(res_partner_bank, self).write(cr, uid, ids, vals, context) - def is_iban_valid(self, cr, uid, iban, context=None): - """ Check if IBAN is valid or not - @param iban: IBAN as string - @return: True if IBAN is valid, False otherwise - """ - if not iban: - return False - iban = _format_iban(iban).lower() - if iban[:2] in _ref_iban and len(iban) != len(_format_iban(_ref_iban[iban[:2]])): - return False - #the four first digits have to be shifted to the end - iban = iban[4:] + iban[:4] - #letters have to be transformed into numbers (a = 10, b = 11, ...) - iban2 = "" - for char in iban: - if char.isalpha(): - iban2 += str(ord(char)-87) - else: - iban2 += char - #iban is correct if modulo 97 == 1 - return int(iban2) % 97 == 1 - - def check_iban(self, cr, uid, ids, context=None): - ''' - Check the IBAN number - ''' - for bank_acc in self.browse(cr, uid, ids, context=context): - if bank_acc.state != 'iban': - continue - if not self.is_iban_valid(cr, uid, bank_acc.acc_number, context=context): - return False - return True - - def _construct_constraint_msg(self, cr, uid, ids, context=None): - - def default_iban_check(iban_cn): - return iban_cn and iban_cn[0] in string.ascii_lowercase and iban_cn[1] in string.ascii_lowercase - - iban_country = self.browse(cr, uid, ids)[0].acc_number and self.browse(cr, uid, ids)[0].acc_number[:2].lower() - if default_iban_check(iban_country): - if iban_country in _ref_iban: - return _('The IBAN does not seem to be correct. You should have entered something like this %s'), \ - ('%s \nWhere B = National bank code, S = Branch code,'\ - ' C = Account No, K = Check digit' % _ref_iban[iban_country]) - return _('This IBAN does not pass the validation check, please verify it'), () - return _('The IBAN is invalid, it should begin with the country code'), () - - def _check_bank(self, cr, uid, ids, context=None): - for partner_bank in self.browse(cr, uid, ids, context=context): - if partner_bank.state == 'iban' and not partner_bank.bank.bic: - return False - return True - - def get_bban_from_iban(self, cr, uid, ids, context=None): - ''' - This function returns the bank account number computed from the iban account number, thanks to the mapping_list dictionary that contains the rules associated to its country. - ''' - res = {} - mapping_list = { - #TODO add rules for others countries - 'be': lambda x: x[4:], - 'fr': lambda x: x[14:], - 'ch': lambda x: x[9:], - 'gb': lambda x: x[14:], - } - for record in self.browse(cr, uid, ids, context=context): - if not record.acc_number: - res[record.id] = False - continue - res[record.id] = False - for code, function in mapping_list.items(): - if record.acc_number.lower().startswith(code): - res[record.id] = function(record.acc_number) - break - return res - - _columns = { - # Deprecated: we keep it for backward compatibility, to be removed in v7 - # We use acc_number instead of IBAN since v6.1, but we keep this field - # to not break community modules. - 'iban': fields.related('acc_number', string='IBAN', size=34, readonly=True, help="International Bank Account Number", type="char"), - } - _constraints = [ - (check_iban, _construct_constraint_msg, ["iban", "acc_number", "state"]), - (_check_bank, '\nPlease define BIC/Swift code on bank for bank type IBAN Account to make valid payments', ['bic']) - ] + @api.one + @api.constrains('state', 'acc_number') + def _check_iban(self): + if self.state == 'iban': + validate_iban(self.acc_number) + + @api.one + @api.constrains('state', 'bank_bic') + def _check_bank_bic(self): + if self.state == 'iban' and not self.bank_bic: + raise ValidationError(_("Please define BIC/Swift code on bank for bank type IBAN Account to make valid payments.")) + + +# Map ISO 3166-1 -> IBAN template, as described here : +# http://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country +_map_iban_template = { + 'ad': 'ADkk BBBB SSSS CCCC CCCC CCCC', # Andorra + 'ae': 'AEkk BBBC CCCC CCCC CCCC CCC', # United Arab Emirates + 'al': 'ALkk BBBS SSSK CCCC CCCC CCCC CCCC', # Albania + 'at': 'ATkk BBBB BCCC CCCC CCCC', # Austria + 'az': 'AZkk BBBB CCCC CCCC CCCC CCCC CCCC', # Azerbaijan + 'ba': 'BAkk BBBS SSCC CCCC CCKK', # Bosnia and Herzegovina + 'be': 'BEkk BBBC CCCC CCXX', # Belgium + 'bg': 'BGkk BBBB SSSS DDCC CCCC CC', # Bulgaria + 'bh': 'BHkk BBBB CCCC CCCC CCCC CC', # Bahrain + 'br': 'BRkk BBBB BBBB SSSS SCCC CCCC CCCT N', # Brazil + 'ch': 'CHkk BBBB BCCC CCCC CCCC C', # Switzerland + 'cr': 'CRkk BBBC CCCC CCCC CCCC C', # Costa Rica + 'cy': 'CYkk BBBS SSSS CCCC CCCC CCCC CCCC', # Cyprus + 'cz': 'CZkk BBBB SSSS SSCC CCCC CCCC', # Czech Republic + 'de': 'DEkk BBBB BBBB CCCC CCCC CC', # Germany + 'dk': 'DKkk BBBB CCCC CCCC CC', # Denmark + 'do': 'DOkk BBBB CCCC CCCC CCCC CCCC CCCC', # Dominican Republic + 'ee': 'EEkk BBSS CCCC CCCC CCCK', # Estonia + 'es': 'ESkk BBBB SSSS KKCC CCCC CCCC', # Spain + 'fi': 'FIkk BBBB BBCC CCCC CK', # Finland + 'fo': 'FOkk CCCC CCCC CCCC CC', # Faroe Islands + 'fr': 'FRkk BBBB BGGG GGCC CCCC CCCC CKK', # France + 'gb': 'GBkk BBBB SSSS SSCC CCCC CC', # United Kingdom + 'ge': 'GEkk BBCC CCCC CCCC CCCC CC', # Georgia + 'gi': 'GIkk BBBB CCCC CCCC CCCC CCC', # Gibraltar + 'gl': 'GLkk BBBB CCCC CCCC CC', # Greenland + 'gr': 'GRkk BBBS SSSC CCCC CCCC CCCC CCC', # Greece + 'gt': 'GTkk BBBB MMTT CCCC CCCC CCCC CCCC', # Guatemala + 'hr': 'HRkk BBBB BBBC CCCC CCCC C', # Croatia + 'hu': 'HUkk BBBS SSSC CCCC CCCC CCCC CCCC', # Hungary + 'ie': 'IEkk BBBB SSSS SSCC CCCC CC', # Ireland + 'il': 'ILkk BBBS SSCC CCCC CCCC CCC', # Israel + 'is': 'ISkk BBBB SSCC CCCC XXXX XXXX XX', # Iceland + 'it': 'ITkk KBBB BBSS SSSC CCCC CCCC CCC', # Italy + 'jo': 'JOkk BBBB NNNN CCCC CCCC CCCC CCCC CC', # Jordan + 'kw': 'KWkk BBBB CCCC CCCC CCCC CCCC CCCC CC', # Kuwait + 'kz': 'KZkk BBBC CCCC CCCC CCCC', # Kazakhstan + 'lb': 'LBkk BBBB CCCC CCCC CCCC CCCC CCCC', # Lebanon + 'li': 'LIkk BBBB BCCC CCCC CCCC C', # Liechtenstein + 'lt': 'LTkk BBBB BCCC CCCC CCCC', # Lithuania + 'lu': 'LUkk BBBC CCCC CCCC CCCC', # Luxembourg + 'lv': 'LVkk BBBB CCCC CCCC CCCC C', # Latvia + 'mc': 'MCkk BBBB BGGG GGCC CCCC CCCC CKK', # Monaco + 'md': 'MDkk BBCC CCCC CCCC CCCC CCCC', # Moldova + 'me': 'MEkk BBBC CCCC CCCC CCCC KK', # Montenegro + 'mk': 'MKkk BBBC CCCC CCCC CKK', # Macedonia + 'mr': 'MRkk BBBB BSSS SSCC CCCC CCCC CKK', # Mauritania + 'mt': 'MTkk BBBB SSSS SCCC CCCC CCCC CCCC CCC', # Malta + 'mu': 'MUkk BBBB BBSS CCCC CCCC CCCC CCCC CC', # Mauritius + 'nl': 'NLkk BBBB CCCC CCCC CC', # Netherlands + 'no': 'NOkk BBBB CCCC CCK', # Norway + 'pk': 'PKkk BBBB CCCC CCCC CCCC CCCC', # Pakistan + 'pl': 'PLkk BBBS SSSK CCCC CCCC CCCC CCCC', # Poland + 'ps': 'PSkk BBBB XXXX XXXX XCCC CCCC CCCC C', # Palestinian + 'pt': 'PTkk BBBB SSSS CCCC CCCC CCCK K', # Portugal + 'qa': 'QAkk BBBB CCCC CCCC CCCC CCCC CCCC C', # Qatar + 'ro': 'ROkk BBBB CCCC CCCC CCCC CCCC', # Romania + 'rs': 'RSkk BBBC CCCC CCCC CCCC KK', # Serbia + 'sa': 'SAkk BBCC CCCC CCCC CCCC CCCC', # Saudi Arabia + 'se': 'SEkk BBBB CCCC CCCC CCCC CCCC', # Sweden + 'si': 'SIkk BBSS SCCC CCCC CKK', # Slovenia + 'sk': 'SKkk BBBB SSSS SSCC CCCC CCCC', # Slovakia + 'sm': 'SMkk KBBB BBSS SSSC CCCC CCCC CCC', # San Marino + 'tn': 'TNkk BBSS SCCC CCCC CCCC CCCC', # Tunisia + 'tr': 'TRkk BBBB BRCC CCCC CCCC CCCC CC', # Turkey + 'vg': 'VGkk BBBB CCCC CCCC CCCC CCCC', # Virgin Islands + 'xk': 'XKkk BBBB CCCC CCCC CCCC', # Kosovo +} diff --git a/addons/base_iban/demo/iban_demo.xml b/addons/base_iban/demo/iban_demo.xml new file mode 100644 index 000000000000..602c2b4163c3 --- /dev/null +++ b/addons/base_iban/demo/iban_demo.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<openerp> + <data noupdate="1"> + + <record id="bank_iban_asustek" model="res.partner.bank"> + <field name="state">iban</field> + <field name="acc_number">BE39103123456719</field> + <field name="bank_name">Crelan</field> + <field name="bank_bic">NICABEBB</field> + <field name="partner_id" ref="base.res_partner_1"/> + </record> + + <record id="bank_iban_china_export" model="res.partner.bank"> + <field name="state">iban</field> + <field name="acc_number">SI56191000000123438</field> + <field name="bank_name">CBC</field> + <field name="bank_bic">CREGBEBB</field> + <field name="partner_id" ref="base.res_partner_3"/> + </record> + + <record id="bank_iban_main_partner" model="res.partner.bank"> + <field name="state">iban</field> + <field name="acc_number">BE61310126985517</field> + <field name="bank_name">ING</field> + <field name="bank_bic">BBRUBEBB</field> + <field name="partner_id" ref="base.res_partner_4"/> + </record> + + </data> +</openerp> diff --git a/addons/sale/tests/test_sale_to_invoice.py b/addons/sale/tests/test_sale_to_invoice.py index 17a4f17e949f..5861a79969be 100644 --- a/addons/sale/tests/test_sale_to_invoice.py +++ b/addons/sale/tests/test_sale_to_invoice.py @@ -31,7 +31,6 @@ class TestSale(TestMail): def test_sale_to_invoice(self): """ Testing for invoice create,validate and pay with invoicing and payment user.""" - import time # Usefull models IrModelData = self.env['ir.model.data'] partner_obj = self.env['res.partner'] @@ -41,9 +40,9 @@ class TestSale(TestMail): group_id = IrModelData.xmlid_to_res_id('account.group_account_invoice') or False product_id = IrModelData.xmlid_to_res_id('product.product_category_3') or False company_id = IrModelData.xmlid_to_res_id('base.main_company') or False - journal_id = self.env['res.company'].browse(company_id)._create_bank_account_and_journal('BNK') - account_id = journal_obj.browse(journal_id).default_credit_account_id.id - date = time.strftime("%Y/%m/%d") + company = self.env['res.company'].browse(company_id) + journal_vals = journal_obj._prepare_bank_journal(company, {'account_type': 'bank', 'acc_name': 'BNK'}) + journal = journal_obj.create(journal_vals) # Usefull accounts user_type_id = IrModelData.xmlid_to_res_id('account.data_account_type_revenue') diff --git a/openerp/addons/base/base_demo.xml b/openerp/addons/base/base_demo.xml index c5ab8baa9ef5..2ca2d031037d 100644 --- a/openerp/addons/base/base_demo.xml +++ b/openerp/addons/base/base_demo.xml @@ -29,7 +29,6 @@ </record> <record model="res.partner.bank" id="bank_company"> - <field name="name">Your Company Bank</field> <field name="acc_number">BE123456789</field> <field name="partner_id" ref="main_partner" /> </record> diff --git a/openerp/addons/base/res/res_bank.py b/openerp/addons/base/res/res_bank.py index b1bb73a31c55..24604bad8977 100644 --- a/openerp/addons/base/res/res_bank.py +++ b/openerp/addons/base/res/res_bank.py @@ -114,7 +114,6 @@ class res_partner_bank(osv.osv): return value _columns = { - 'name': fields.char('Bank Account'), # to be removed in v6.2 ? 'acc_number': fields.char('Account Number', required=True), 'bank': fields.many2one('res.bank', 'Bank'), 'bank_bic': fields.char('Bank Identifier Code'), @@ -133,7 +132,8 @@ class res_partner_bank(osv.osv): 'state': fields.selection(_bank_type_get, 'Bank Account Type', change_default=True), 'sequence': fields.integer('Sequence'), - 'footer': fields.boolean("Display on Reports", help="Display this bank account on the footer of printed documents like invoices and sales orders.") + 'footer': fields.boolean("Display on Reports", help="Display this bank account on the footer of printed documents like invoices and sales orders."), + 'currency_id': fields.many2one('res.currency', string='Currency', help="Currency of the bank account and its related journal."), } _defaults = { @@ -150,9 +150,6 @@ class res_partner_bank(osv.osv): 'state_id': lambda obj, cursor, user, context: obj._default_value( cursor, user, 'state_id', context=context), 'name': '/', - 'company_id': lambda obj, cursor, user, context: obj.pool.get('res.company')._company_default_get( - cursor, user, 'res.partner.bank', context=context), - 'partner_id': lambda obj, cr, uid, context: obj.pool.get('res.users').browse(cr, uid, uid, context).company_id.partner_id } def fields_get(self, cr, uid, allfields=None, context=None, write_access=True, attributes=None): @@ -190,7 +187,10 @@ class res_partner_bank(osv.osv): data = dict((k, v or '') for (k, v) in data.iteritems()) name = bank_code_format[data['state']] % data except Exception: - raise UserError(_("Formating Error") + ':' + _("Invalid Bank Account Type Name format.")) + raise UserError(_("Bank account name formating error") + ': ' + _("Check the format_layout field set on the Bank Account Type.")) + if data.get('currency_id'): + currency_name = self.pool.get('res.currency').browse(cr, uid, data['currency_id'], context=context).name + name += ' (' + currency_name + ')' res.append((data.get('id', False), name)) return res @@ -219,7 +219,6 @@ class res_partner_bank(osv.osv): result['bank_bic'] = bank.bic return {'value': result} - def onchange_partner_id(self, cr, uid, ids, partner_id, context=None): result = {} if partner_id is not False: @@ -228,7 +227,7 @@ class res_partner_bank(osv.osv): result['owner_name'] = part.name result['street'] = part.street or False result['city'] = part.city or False - result['zip'] = part.zip or False - result['country_id'] = part.country_id.id + result['zip'] = part.zip or False + result['country_id'] = part.country_id.id result['state_id'] = part.state_id.id return {'value': result} diff --git a/openerp/addons/base/res/res_bank_view.xml b/openerp/addons/base/res/res_bank_view.xml index a87dd20c160f..a45cd457f63a 100644 --- a/openerp/addons/base/res/res_bank_view.xml +++ b/openerp/addons/base/res/res_bank_view.xml @@ -177,7 +177,7 @@ </p> <p> If you use the accounting application of Odoo, journals and accounts will be created automatically based on these data. - </p> + </p> </field> </record> <menuitem action="action_res_partner_bank_account_form" diff --git a/openerp/addons/base/tests/test_expression.py b/openerp/addons/base/tests/test_expression.py index de7e04724795..b27b954edd80 100644 --- a/openerp/addons/base/tests/test_expression.py +++ b/openerp/addons/base/tests/test_expression.py @@ -152,9 +152,9 @@ class test_expression(common.TransactionCase): p_aa = partner_obj.create(cr, uid, {'name': 'test__AA', 'parent_id': p_a, 'state_id': state_ids[0]}) p_ab = partner_obj.create(cr, uid, {'name': 'test__AB', 'parent_id': p_a, 'state_id': state_ids[1]}) p_ba = partner_obj.create(cr, uid, {'name': 'test__BA', 'parent_id': p_b, 'state_id': state_ids[0]}) - b_aa = bank_obj.create(cr, uid, {'name': '__bank_test_a', 'state': bank_type[0], 'partner_id': p_aa, 'acc_number': '1234'}) - b_ab = bank_obj.create(cr, uid, {'name': '__bank_test_b', 'state': bank_type[0], 'partner_id': p_ab, 'acc_number': '5678'}) - b_ba = bank_obj.create(cr, uid, {'name': '__bank_test_b', 'state': bank_type[0], 'partner_id': p_ba, 'acc_number': '9876'}) + b_aa = bank_obj.create(cr, uid, {'acc_number': '__bank_test_a', 'state': bank_type[0], 'partner_id': p_aa}) + b_ab = bank_obj.create(cr, uid, {'acc_number': '__bank_test_b', 'state': bank_type[0], 'partner_id': p_ab}) + b_ba = bank_obj.create(cr, uid, {'acc_number': '__bank_test_b', 'state': bank_type[0], 'partner_id': p_ba}) # -------------------------------------------------- # Test1: basics about the attribute @@ -172,30 +172,30 @@ class test_expression(common.TransactionCase): # Do: one2many without _auto_join self._reinit_mock() - partner_ids = partner_obj.search(cr, uid, [('bank_ids.name', 'like', name_test)]) + partner_ids = partner_obj.search(cr, uid, [('bank_ids.acc_number', 'like', name_test)]) # Test result self.assertEqual(set(partner_ids), set([p_aa]), - "_auto_join off: ('bank_ids.name', 'like', '..'): incorrect result") + "_auto_join off: ('bank_ids.acc_number', 'like', '..'): incorrect result") # Test produced queries self.assertEqual(len(self.query_list), 3, - "_auto_join off: ('bank_ids.name', 'like', '..') should produce 3 queries (1 in res_partner_bank, 2 on res_partner)") + "_auto_join off: ('bank_ids.acc_number', 'like', '..') should produce 3 queries (1 in res_partner_bank, 2 on res_partner)") sql_query = self.query_list[0].get_sql() self.assertIn('res_partner_bank', sql_query[0], - "_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect main table") + "_auto_join off: ('bank_ids.acc_number', 'like', '..') first query incorrect main table") - expected = "%s::text like %s" % (unaccent('"res_partner_bank"."name"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_partner_bank"."acc_number"'), unaccent('%s')) self.assertIn(expected, sql_query[1], - "_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect where condition") + "_auto_join off: ('bank_ids.acc_number', 'like', '..') first query incorrect where condition") self.assertEqual(set(['%' + name_test + '%']), set(sql_query[2]), - "_auto_join off: ('bank_ids.name', 'like', '..') first query incorrect parameter") + "_auto_join off: ('bank_ids.acc_number', 'like', '..') first query incorrect parameter") sql_query = self.query_list[2].get_sql() self.assertIn('res_partner', sql_query[0], - "_auto_join off: ('bank_ids.name', 'like', '..') third query incorrect main table") + "_auto_join off: ('bank_ids.acc_number', 'like', '..') third query incorrect main table") self.assertIn('"res_partner"."id" in (%s)', sql_query[1], - "_auto_join off: ('bank_ids.name', 'like', '..') third query incorrect where condition") + "_auto_join off: ('bank_ids.acc_number', 'like', '..') third query incorrect where condition") self.assertEqual(set([p_aa]), set(sql_query[2]), - "_auto_join off: ('bank_ids.name', 'like', '..') third query incorrect parameter") + "_auto_join off: ('bank_ids.acc_number', 'like', '..') third query incorrect parameter") # Do: cascaded one2many without _auto_join self._reinit_mock() @@ -210,27 +210,27 @@ class test_expression(common.TransactionCase): # Do: one2many with _auto_join partner_bank_ids_col._auto_join = True self._reinit_mock() - partner_ids = partner_obj.search(cr, uid, [('bank_ids.name', 'like', 'test_a')]) + partner_ids = partner_obj.search(cr, uid, [('bank_ids.acc_number', 'like', 'test_a')]) # Test result self.assertEqual(set(partner_ids), set([p_aa]), - "_auto_join on: ('bank_ids.name', 'like', '..') incorrect result") + "_auto_join on: ('bank_ids.acc_number', 'like', '..') incorrect result") # Test produced queries self.assertEqual(len(self.query_list), 1, - "_auto_join on: ('bank_ids.name', 'like', '..') should produce 1 query") + "_auto_join on: ('bank_ids.acc_number', 'like', '..') should produce 1 query") sql_query = self.query_list[0].get_sql() self.assertIn('"res_partner"', sql_query[0], - "_auto_join on: ('bank_ids.name', 'like', '..') query incorrect main table") + "_auto_join on: ('bank_ids.acc_number', 'like', '..') query incorrect main table") self.assertIn('"res_partner_bank" as "res_partner__bank_ids"', sql_query[0], - "_auto_join on: ('bank_ids.name', 'like', '..') query incorrect join") + "_auto_join on: ('bank_ids.acc_number', 'like', '..') query incorrect join") - expected = "%s::text like %s" % (unaccent('"res_partner__bank_ids"."name"'), unaccent('%s')) + expected = "%s::text like %s" % (unaccent('"res_partner__bank_ids"."acc_number"'), unaccent('%s')) self.assertIn(expected, sql_query[1], - "_auto_join on: ('bank_ids.name', 'like', '..') query incorrect where condition") + "_auto_join on: ('bank_ids.acc_number', 'like', '..') query incorrect where condition") self.assertIn('"res_partner"."id"="res_partner__bank_ids"."partner_id"', sql_query[1], - "_auto_join on: ('bank_ids.name', 'like', '..') query incorrect join condition") + "_auto_join on: ('bank_ids.acc_number', 'like', '..') query incorrect join condition") self.assertEqual(set(['%' + name_test + '%']), set(sql_query[2]), - "_auto_join on: ('bank_ids.name', 'like', '..') query incorrect parameter") + "_auto_join on: ('bank_ids.acc_number', 'like', '..') query incorrect parameter") # Do: one2many with _auto_join, test final leaf is an id self._reinit_mock() @@ -391,7 +391,7 @@ class test_expression(common.TransactionCase): partner_child_ids_col._auto_join = True partner_bank_ids_col._auto_join = True partner_child_ids_col._domain = lambda self: ['!', ('name', '=', self._name)] - partner_bank_ids_col._domain = [('acc_number', 'like', '1')] + partner_bank_ids_col._domain = [('acc_number', 'like', 'a')] # Do: 2 cascaded one2many with _auto_join, test final leaf is an id self._reinit_mock() partner_ids = partner_obj.search(cr, uid, ['&', (1, '=', 1), ('child_ids.bank_ids.id', 'in', [b_aa, b_ba])]) diff --git a/openerp/addons/base/tests/test_osv_expression.yml b/openerp/addons/base/tests/test_osv_expression.yml index f35849974bb1..88f3143b1dda 100644 --- a/openerp/addons/base/tests/test_osv_expression.yml +++ b/openerp/addons/base/tests/test_osv_expression.yml @@ -370,7 +370,7 @@ # Search the company via its one2many (the one2many must point back at the company). company = self.browse(cr, uid, ref('ymltest_company3')) max_currency_id = max(self.pool['res.partner.bank'].search(cr, uid, [])) - bank_ids1 = self.pool['res.partner.bank'].search(cr, uid, [('name', 'not like', 'probably_unexisting_name')]) + bank_ids1 = self.pool['res.partner.bank'].search(cr, uid, [('acc_number', 'not like', 'probably_unexisting_name')]) bank_ids2 = self.pool['res.partner.bank'].search(cr, uid, [('id', 'not in', [max_currency_id + 1003])]) bank_ids3 = self.pool['res.partner.bank'].search(cr, uid, [('id', 'not in', [])]) assert bank_ids1 == bank_ids2 == bank_ids3, 'All 3 results should have be the same: all banks' -- GitLab