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
196 lines
9.5 KiB
Python
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'))
|