[IMP] account: improve performance of is_used computation on account.tax

Problem
---------
The compute_is_used function currently parses through a huge amount of
data using the _read_group ORM function. Which causes some performance
issues in large databases.

Objective
---------
Improve performance of the said function.

Solution
---------
Instead of reading the data on whole tables (ex.account_move_line), we
read the relation table between the modules and the taxes. This is done
using SQL queries with the EXISTS statement.
Furthermore, the code logic is modified so that if all the taxes for
which we compute `is_used` have been found, we don't look further in
other modules since doing this cannot add an new relevant information
and actually slows down the computation.

closes odoo/odoo#139497

Signed-off-by: Quentin De Paoli <qdp@odoo.com>
This commit is contained in:
Antoine Boonen
2023-11-04 00:38:16 +00:00
parent 7f1c96995f
commit 2b95b87571
4 changed files with 104 additions and 28 deletions
+44 -13
View File
@@ -6,7 +6,7 @@ from odoo.exceptions import UserError, ValidationError
from odoo.tools.misc import clean_context, formatLang
from odoo.tools import frozendict, groupby
from collections import Counter, defaultdict
from collections import defaultdict
from markupsafe import Markup
import ast
@@ -238,23 +238,54 @@ class AccountTax(models.Model):
('country_id', '=', False),
], limit=1)
def _hook_compute_is_used(self):
def _hook_compute_is_used(self, tax_to_compute):
'''
To be overriden to add taxed transactions in the computation of `is_used`
Should return a Counter containing a dictionary {record: int} where
the record is an account.tax object. The int should be greater than 0
if the tax is used in a transaction.
Override to compute the ids of taxes used in other modules. It takes
as parameter a set of tax ids. It should return a set containing the
ids of the taxes from that input set that are used in transactions.
'''
return Counter()
return set()
def _compute_is_used(self):
taxes_in_transactions_ctr = (
Counter(dict(self.env['account.move.line']._read_group([], groupby=['tax_ids'], aggregates=['__count']))) +
Counter(dict(self.env['account.reconcile.model.line']._read_group([], groupby=['tax_ids'], aggregates=['__count']))) +
self._hook_compute_is_used()
)
used_taxes = set()
# Fetch for taxes used in account moves
self.env['account.move.line'].flush_model(['tax_ids'])
self.env.cr.execute("""
SELECT id
FROM account_tax
WHERE EXISTS(
SELECT 1
FROM account_move_line_account_tax_rel AS line
WHERE account_tax_id IN %s
AND account_tax.id = line.account_tax_id
)
""", [tuple(self.ids)])
used_taxes.update([tax[0] for tax in self.env.cr.fetchall()])
taxes_to_compute = set(self.ids) - used_taxes
# Fetch for taxes used in reconciliation
if taxes_to_compute:
self.env['account.reconcile.model.line'].flush_model(['tax_ids'])
self.env.cr.execute("""
SELECT id
FROM account_tax
WHERE EXISTS(
SELECT 1
FROM account_reconcile_model_line_account_tax_rel AS reco
WHERE account_tax_id IN %s
AND account_tax.id = reco.account_tax_id
)
""", [tuple(taxes_to_compute)])
used_taxes.update([tax[0] for tax in self.env.cr.fetchall()])
taxes_to_compute -= used_taxes
# Fetch for tax used in other modules
if taxes_to_compute:
used_taxes.update(self._hook_compute_is_used(taxes_to_compute))
for tax in self:
tax.is_used = bool(taxes_in_transactions_ctr[tax])
tax.is_used = tax.id in used_taxes
@api.depends('repartition_line_ids.account_id', 'repartition_line_ids.factor_percent', 'repartition_line_ids.use_in_tax_closing', 'repartition_line_ids.tag_ids')
def _compute_repartition_lines_str(self):
+20 -5
View File
@@ -1,15 +1,30 @@
# -*- coding: utf-8 -*-
from collections import Counter
from odoo import models
class AccountTax(models.Model):
_inherit = "account.tax"
def _hook_compute_is_used(self):
# OVERRIDE in order to count the usage of taxes in expenses
def _hook_compute_is_used(self, taxes_to_compute):
# OVERRIDE in order to fetch taxes used in expenses
taxes_in_transactions_ctr = Counter(dict(self.env['hr.expense']._read_group([], groupby=['tax_ids'], aggregates=['__count'])))
used_taxes = super()._hook_compute_is_used(taxes_to_compute)
taxes_to_compute -= used_taxes
return super()._hook_compute_is_used() + taxes_in_transactions_ctr
if taxes_to_compute:
self.env['hr.expense'].flush_model(['tax_ids'])
self.env.cr.execute("""
SELECT id
FROM account_tax
WHERE EXISTS(
SELECT 1
FROM expense_tax AS exp
WHERE tax_id IN %s
AND account_tax.id = exp.tax_id
)
""", [tuple(taxes_to_compute)])
used_taxes.update([tax[0] for tax in self.env.cr.fetchall()])
return used_taxes
+20 -5
View File
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from collections import Counter
from odoo import _, api, models
from odoo.exceptions import UserError
from odoo.tools import split_every
@@ -28,9 +27,25 @@ class AccountTax(models.Model):
lines_chunk.invalidate_recordset(['tax_ids'])
return super(AccountTax, self).write(vals)
def _hook_compute_is_used(self):
# OVERRIDE in order to count the usage of taxes in pos order lines
def _hook_compute_is_used(self, taxes_to_compute):
# OVERRIDE in order to fetch taxes used in pos
taxes_in_transactions_ctr = Counter(dict(self.env['pos.order.line']._read_group([], groupby=['tax_ids'], aggregates=['__count'])))
used_taxes = super()._hook_compute_is_used(taxes_to_compute)
taxes_to_compute -= used_taxes
return super()._hook_compute_is_used() + taxes_in_transactions_ctr
if taxes_to_compute:
self.env['pos.order.line'].flush_model(['tax_ids'])
self.env.cr.execute("""
SELECT id
FROM account_tax
WHERE EXISTS(
SELECT 1
FROM account_tax_pos_order_line_rel AS pos
WHERE account_tax_id IN %s
AND account_tax.id = pos.account_tax_id
)
""", [tuple(taxes_to_compute)])
used_taxes.update([tax[0] for tax in self.env.cr.fetchall()])
return used_taxes
+20 -5
View File
@@ -1,15 +1,30 @@
# -*- coding: utf-8 -*-
from collections import Counter
from odoo import models
class AccountTax(models.Model):
_inherit = "account.tax"
def _hook_compute_is_used(self):
# OVERRIDE in order to count the usage of taxes in purchase order lines
def _hook_compute_is_used(self, taxes_to_compute):
# OVERRIDE in order to fetch taxes used in purchase
taxes_in_transactions_ctr = Counter(dict(self.env['purchase.order.line']._read_group([], groupby=['taxes_id'], aggregates=['__count'])))
used_taxes = super()._hook_compute_is_used(taxes_to_compute)
taxes_to_compute -= used_taxes
return super()._hook_compute_is_used() + taxes_in_transactions_ctr
if taxes_to_compute:
self.env['purchase.order.line'].flush_model(['taxes_id'])
self.env.cr.execute("""
SELECT id
FROM account_tax
WHERE EXISTS(
SELECT 1
FROM account_tax_purchase_order_line_rel AS pur
WHERE account_tax_id IN %s
AND account_tax.id = pur.account_tax_id
)
""", [tuple(taxes_to_compute)])
used_taxes.update([tax[0] for tax in self.env.cr.fetchall()])
return used_taxes