[IMP] account: make account creation easier

Task 2888204

- Name is automatically split into code and name
- account_type is computed based on the closest parent of the code prefix (and can be changed)
- _onchange_name is needed to split name into code and name when creating from a related model's form

closes odoo/odoo#94171

Signed-off-by: Quentin De Paoli <qdp@odoo.com>
This commit is contained in:
aliya
2022-07-14 18:03:47 +02:00
parent 27b472e5ed
commit 8a455d8519
3 changed files with 77 additions and 0 deletions
+40
View File
@@ -2,7 +2,10 @@
from odoo import api, fields, models, _, tools
from odoo.osv import expression
from odoo.exceptions import UserError, ValidationError
from bisect import bisect_left
import re
ACCOUNT_REGEX = re.compile(r'(?:(\S*\d+\S*)\s)?(.*)')
class AccountAccount(models.Model):
_name = "account.account"
@@ -58,6 +61,7 @@ class AccountAccount(models.Model):
],
string="Type", tracking=True,
required=True,
compute='_compute_account_type', store=True, readonly=False, precompute=True,
help="Account Type is used for information purpose, to generate country-specific legal reports, and set the rules to close a fiscal year and generate opening entries."
)
include_initial_balance = fields.Boolean(string="Bring Accounts Balance Forward",
@@ -366,6 +370,19 @@ class AccountAccount(models.Model):
record.opening_credit = res['credit']
record.opening_balance = res['balance']
@api.depends('code')
def _compute_account_type(self):
""" Compute the account type based on the account code.
Search for the closest parent account code and sets the account type according to the parent.
"""
accounts_to_process = self.filtered(lambda r: r.code and not r.account_type)
accounts_with_codes = {r['code']: r['account_type'] for r in self.search_read(domain=[(
'code', '!=', False), ('company_id', '=', self.company_id.id)], fields=['code', 'account_type'])}
codes_list = list(accounts_with_codes.keys())
for account in accounts_to_process:
closest_index = bisect_left(codes_list, account.code) - 1
account.account_type = accounts_with_codes[codes_list[closest_index]]
@api.depends('internal_group')
def _compute_is_off_balance(self):
for account in self:
@@ -513,6 +530,18 @@ class AccountAccount(models.Model):
elif self.internal_group == 'expense' and not self.tax_ids:
self.tax_ids = self.company_id.account_purchase_tax_id
def _split_code_name(self, code_name):
# We only want to split the name on the first word if there is a digit in it
code, name = ACCOUNT_REGEX.match(code_name or '').groups()
return code, name.strip()
@api.onchange('name')
def _onchange_name(self):
code, name = self._split_code_name(self.name)
if code:
self.name = name
self.code = code
def name_get(self):
result = []
for account in self:
@@ -592,6 +621,17 @@ class AccountAccount(models.Model):
"""
self.env.cr.execute(query, [tuple(self.ids)])
@api.model
def name_create(self, name):
""" Split the account name into account code and account name in import.
When importing a file with accounts, the account code and name may be both entered in the name column.
In this case, the name will be split into code and name.
"""
if 'import_file' in self.env.context:
code, name = self._split_code_name(name)
return self.create({'code': code, 'name': name}).name_get()[0]
raise ValueError(_("The values for the created account need to be verified."))
def write(self, vals):
# Do not allow changing the company_id when account_move_line already exist
if vals.get('company_id', False):
@@ -153,3 +153,35 @@ class TestAccountAccount(AccountTestInvoicingCommon):
group.code_prefix_end = 401000
self.assertRecordValues(account_1 + account_2, [{'group_id': group.id}, {'group_id': False}])
def test_name_create(self):
"""name_create should only be possible when importing
Code and Name should be split
"""
with self.assertRaises(ValueError):
self.env['account.account'].name_create('550003 Existing Account')
account_id = self.env['account.account'].with_context(import_file=True).name_create('550003 Existing Account')[0]
account = self.env['account.account'].browse(account_id)
self.assertEqual(account.code, "550003")
self.assertEqual(account.name, "Existing Account")
def test_compute_account_type(self):
existing_account = self.env['account.account'].search([], limit=1)
# account_type should be computed
new_account_code = self.env['account.account']._search_new_account_code(
company=existing_account.company_id,
digits=len(existing_account.code),
prefix=existing_account.code[:-1])
new_account = self.env['account.account'].create({
'code': new_account_code,
'name': 'A new account'
})
self.assertEqual(new_account.account_type, existing_account.account_type)
# account_type should not be altered
alternate_account = self.env['account.account'].search([('account_type', '!=', existing_account.account_type)], limit=1)
alternate_code = self.env['account.account']._search_new_account_code(
company=alternate_account.company_id,
digits=len(alternate_account.code),
prefix=alternate_account.code[:-1])
new_account.code = alternate_code
self.assertEqual(new_account.account_type, existing_account.account_type)
@@ -56,24 +56,29 @@
<!-- Bank -->
<field name="default_account_id" string="Bank Account"
attrs="{'required': [('id', '!=', False), ('type', '=', 'bank')], 'invisible': [('type', '!=', 'bank')]}"
options="{'no_quick_create': True}"
groups="account.group_account_readonly"/>
<!-- Cash -->
<field name="default_account_id" string="Cash Account"
attrs="{'required': [('id', '!=', False), ('type', '=', 'cash')], 'invisible': [('type', '!=', 'cash')]}"
options="{'no_quick_create': True}"
groups="account.group_account_readonly"/>
<field name="suspense_account_id"
attrs="{'required': [('type', 'in', ('bank', 'cash'))], 'invisible': [('type', 'not in', ('bank', 'cash'))]}"
options="{'no_quick_create': True}"
groups="account.group_account_readonly"/>
<field name="profit_account_id" attrs="{'invisible': ['!', ('type', 'in', ('cash', 'bank'))]}"/>
<field name="loss_account_id" attrs="{'invisible': ['!', ('type', 'in', ('cash', 'bank'))]}"/>
<!-- Sales -->
<field name="default_account_id" string="Default Income Account"
attrs="{'required': [('type', '=', 'sale')], 'invisible': [('type', '!=', 'sale')]}"
options="{'no_quick_create': True}"
groups="account.group_account_readonly"/>
<!-- Purchase -->
<field name="default_account_id" string="Default Expense Account"
attrs="{'required': [('type', '=', 'purchase')], 'invisible': [('type', '!=', 'purchase')]}"
options="{'no_quick_create': True}"
groups="account.group_account_readonly"/>
<field name="refund_sequence" attrs="{'invisible': [('type', 'not in', ['sale', 'purchase'])]}"/>
<field name="code" placeholder="e.g. INV"/>