From 2b95b875719473c92fcb065e14d52c63425e8382 Mon Sep 17 00:00:00 2001 From: Antoine Boonen Date: Mon, 23 Oct 2023 16:04:00 +0200 Subject: [PATCH] [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 --- addons/account/models/account_tax.py | 57 +++++++++++++++++----- addons/hr_expense/models/account_tax.py | 25 ++++++++-- addons/point_of_sale/models/account_tax.py | 25 ++++++++-- addons/purchase/models/account_tax.py | 25 ++++++++-- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index 9607d1af911..eeefbf284f9 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -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): diff --git a/addons/hr_expense/models/account_tax.py b/addons/hr_expense/models/account_tax.py index 5952447f2bb..0f768a1c470 100644 --- a/addons/hr_expense/models/account_tax.py +++ b/addons/hr_expense/models/account_tax.py @@ -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 diff --git a/addons/point_of_sale/models/account_tax.py b/addons/point_of_sale/models/account_tax.py index e84238fb618..0c8978bac0e 100644 --- a/addons/point_of_sale/models/account_tax.py +++ b/addons/point_of_sale/models/account_tax.py @@ -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 diff --git a/addons/purchase/models/account_tax.py b/addons/purchase/models/account_tax.py index 91ffb546d6e..e5ab12d2e61 100644 --- a/addons/purchase/models/account_tax.py +++ b/addons/purchase/models/account_tax.py @@ -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