Files
odoo_source/addons/analytic/models/analytic_account.py
T
RomainLibert 6dbdfb9bf7 [IMP] various: optimize _name_search
In the overrides of _name_search we should avoid creating domains with
huge lists of ids as it is inefficient.
We can also make sure that we optimize the empty search as in this case
the custom domain doesn't make sense, we can simply search on an empty
domain and, thanks to the limit argument, still keep a fast query.

Linked to task 1918906

Thanks to @odony and @nseinlet

closes odoo/odoo#30155
2019-02-05 11:44:21 +00:00

196 lines
9.5 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.exceptions import ValidationError
class AccountAnalyticDistribution(models.Model):
_name = 'account.analytic.distribution'
_description = 'Analytic Account Distribution'
_rec_name = 'account_id'
account_id = fields.Many2one('account.analytic.account', string='Analytic Account', required=True)
percentage = fields.Float(string='Percentage', required=True, default=100.0)
name = fields.Char(string='Name', related='account_id.name', readonly=False)
tag_id = fields.Many2one('account.analytic.tag', string="Parent tag", required=True)
_sql_constraints = [
('check_percentage', 'CHECK(percentage >= 0 AND percentage <= 100)',
'The percentage of an analytic distribution should be between 0 and 100.')
]
class AccountAnalyticTag(models.Model):
_name = 'account.analytic.tag'
_description = 'Analytic Tags'
name = fields.Char(string='Analytic Tag', index=True, required=True)
color = fields.Integer('Color Index')
active = fields.Boolean(default=True, help="Set active to false to hide the Analytic Tag without removing it.")
active_analytic_distribution = fields.Boolean('Analytic Distribution')
analytic_distribution_ids = fields.One2many('account.analytic.distribution', 'tag_id', string="Analytic Accounts")
company_id = fields.Many2one('res.company', string='Company')
class AccountAnalyticGroup(models.Model):
_name = 'account.analytic.group'
_description = 'Analytic Categories'
_parent_store = True
_rec_name = 'complete_name'
name = fields.Char(required=True)
description = fields.Text(string='Description')
parent_id = fields.Many2one('account.analytic.group', string="Parent", ondelete='cascade')
parent_path = fields.Char(index=True)
children_ids = fields.One2many('account.analytic.group', 'parent_id', string="Childrens")
complete_name = fields.Char('Complete Name', compute='_compute_complete_name', store=True)
company_id = fields.Many2one('res.company', string='Company')
@api.depends('name', 'parent_id.complete_name')
def _compute_complete_name(self):
for group in self:
if group.parent_id:
group.complete_name = '%s / %s' % (group.parent_id.complete_name, group.name)
else:
group.complete_name = group.name
class AccountAnalyticAccount(models.Model):
_name = 'account.analytic.account'
_inherit = ['mail.thread']
_description = 'Analytic Account'
_order = 'code, name asc'
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
"""
Override read_group to calculate the sum of the non-stored fields that depend on the user context
"""
res = super(AccountAnalyticAccount, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
accounts = self.env['account.analytic.account']
for line in res:
if '__domain' in line:
accounts = self.search(line['__domain'])
if 'balance' in fields:
line['balance'] = sum(accounts.mapped('balance'))
if 'debit' in fields:
line['debit'] = sum(accounts.mapped('debit'))
if 'credit' in fields:
line['credit'] = sum(accounts.mapped('credit'))
return res
@api.multi
def _compute_debit_credit_balance(self):
Curr = self.env['res.currency']
analytic_line_obj = self.env['account.analytic.line']
domain = [('account_id', 'in', self.ids)]
if self._context.get('from_date', False):
domain.append(('date', '>=', self._context['from_date']))
if self._context.get('to_date', False):
domain.append(('date', '<=', self._context['to_date']))
if self._context.get('tag_ids'):
tag_domain = expression.OR([[('tag_ids', 'in', [tag])] for tag in self._context['tag_ids']])
domain = expression.AND([domain, tag_domain])
if self._context.get('company_ids'):
domain.append(('company_id', 'in', self._context['company_ids']))
user_currency = self.env.user.company_id.currency_id
credit_groups = analytic_line_obj.read_group(
domain=domain + [('amount', '>=', 0.0)],
fields=['account_id', 'currency_id', 'amount'],
groupby=['account_id', 'currency_id'],
lazy=False,
)
data_credit = defaultdict(float)
for l in credit_groups:
data_credit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
l['amount'], user_currency, self.env.user.company_id, fields.Date.today())
debit_groups = analytic_line_obj.read_group(
domain=domain + [('amount', '<', 0.0)],
fields=['account_id', 'currency_id', 'amount'],
groupby=['account_id', 'currency_id'],
lazy=False,
)
data_debit = defaultdict(float)
for l in debit_groups:
data_debit[l['account_id'][0]] += Curr.browse(l['currency_id'][0])._convert(
l['amount'], user_currency, self.env.user.company_id, fields.Date.today())
for account in self:
account.debit = abs(data_debit.get(account.id, 0.0))
account.credit = data_credit.get(account.id, 0.0)
account.balance = account.credit - account.debit
name = fields.Char(string='Analytic Account', index=True, required=True, tracking=True)
code = fields.Char(string='Reference', index=True, tracking=True)
active = fields.Boolean('Active', help="If the active field is set to False, it will allow you to hide the account without removing it.", default=True)
group_id = fields.Many2one('account.analytic.group', string='Group')
line_ids = fields.One2many('account.analytic.line', 'account_id', string="Analytic Lines")
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.user.company_id)
# use auto_join to speed up name_search call
partner_id = fields.Many2one('res.partner', string='Customer', auto_join=True, tracking=True)
balance = fields.Monetary(compute='_compute_debit_credit_balance', string='Balance')
debit = fields.Monetary(compute='_compute_debit_credit_balance', string='Debit')
credit = fields.Monetary(compute='_compute_debit_credit_balance', string='Credit')
currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True)
@api.multi
def name_get(self):
res = []
for analytic in self:
name = analytic.name
if analytic.code:
name = '[' + analytic.code + '] ' + name
if analytic.partner_id:
name = name + ' - ' + analytic.partner_id.commercial_partner_id.name
res.append((analytic.id, name))
return res
@api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
if operator not in ('ilike', 'like', '=', '=like', '=ilike'):
return super(AccountAnalyticAccount, self)._name_search(name, args, operator, limit, name_get_uid=name_get_uid)
args = args or []
if operator == 'ilike' and not (name or '').strip():
domain = []
else:
domain = ['|', '|', ('code', operator, name), ('name', operator, name), ('partner_id.name', operator, name)]
analytic_account_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
return self.browse(analytic_account_ids).name_get()
class AccountAnalyticLine(models.Model):
_name = 'account.analytic.line'
_description = 'Analytic Line'
_order = 'date desc, id desc'
@api.model
def _default_user(self):
return self.env.context.get('user_id', self.env.user.id)
name = fields.Char('Description', required=True)
date = fields.Date('Date', required=True, index=True, default=fields.Date.context_today)
amount = fields.Monetary('Amount', required=True, default=0.0)
unit_amount = fields.Float('Quantity', default=0.0)
product_uom_id = fields.Many2one('uom.uom', string='Unit of Measure')
account_id = fields.Many2one('account.analytic.account', 'Analytic Account', required=True, ondelete='restrict', index=True)
partner_id = fields.Many2one('res.partner', string='Partner')
user_id = fields.Many2one('res.users', string='User', default=_default_user)
tag_ids = fields.Many2many('account.analytic.tag', 'account_analytic_line_tag_rel', 'line_id', 'tag_id', string='Tags', copy=True)
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True, default=lambda self: self.env.user.company_id)
currency_id = fields.Many2one(related="company_id.currency_id", string="Currency", readonly=True, store=True, compute_sudo=True)
group_id = fields.Many2one('account.analytic.group', related='account_id.group_id', store=True, readonly=True, compute_sudo=True)
@api.multi
@api.constrains('company_id', 'account_id')
def _check_company_id(self):
for line in self:
if line.account_id.company_id and line.company_id.id != line.account_id.company_id.id:
raise ValidationError(_('The selected account belongs to another company that the one you\'re trying to create an analytic item for'))