[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:
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user