[IMP] *: change the override of read_group

The new backend version of read_group can simplify the
current overrides of read_group. Do it for each of them and
avoid making extra search (done with __domain) when it is possible.

Part-of: odoo/odoo#110737
This commit is contained in:
Rémy Voet (ryv)
2023-04-19 21:58:27 +02:00
parent 234db70d86
commit 687d0ed285
6 changed files with 79 additions and 118 deletions
+20 -16
View File
@@ -2,6 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
import itertools
from odoo import api, fields, models, _
from odoo.exceptions import UserError
@@ -131,22 +132,25 @@ class AccountAnalyticAccount(models.Model):
return super().copy_data(default)
@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
def _read_group(self, domain, groupby=(), aggregates=(), having=(), offset=0, limit=None, order=None):
""" Override _read_group to aggregate no-store compute: balance/debit/credit """
SPECIAL = {'balance:sum', 'debit:sum', 'credit:sum'}
if SPECIAL.isdisjoint(aggregates):
return super()._read_group(domain, groupby, aggregates, having, offset, limit, order)
base_aggregates = [*(agg for agg in aggregates if agg not in SPECIAL), 'id:recordset']
base_result = super()._read_group(domain, groupby, base_aggregates, having, offset, limit, order)
# base_result = [(a1, b1, records), (a2, b2, records), ...]
result = []
for *other, records in base_result:
for index, spec in enumerate(itertools.chain(groupby, aggregates)):
if spec in SPECIAL:
field_name = spec.split(':')[0]
other.insert(index, sum(records.mapped(field_name)))
result.append(tuple(other))
return result
@api.depends('line_ids.amount')
def _compute_debit_credit_balance(self):
+24 -36
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import itertools
import time
from odoo import api, fields, models
@@ -47,50 +48,37 @@ class ProductProduct(models.Model):
help="Expected margin * 100 / Expected Sale")
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
def _read_group(self, domain, groupby=(), aggregates=(), having=(), offset=0, limit=None, order=None):
"""
Inherit read_group to calculate the sum of the non-stored fields, as it is not automatically done anymore through the XML.
Inherit _read_group to calculate the sum of the non-stored fields, as it is not automatically done anymore through the XML.
"""
fields_list = ['turnover', 'sale_avg_price', 'sale_purchase_price', 'sale_num_invoiced', 'purchase_num_invoiced',
'sales_gap', 'purchase_gap', 'total_cost', 'sale_expected', 'normal_cost', 'total_margin',
'expected_margin', 'total_margin_rate', 'expected_margin_rate']
SPECIAL = {f'{field_name}:sum' for field_name in fields_list} # Only sum is allowed
# Not any of the fields_list support aggregate function like :sum
def truncate_aggr(field):
field_no_aggr, _sep, agg = field.partition(':')
if field_no_aggr in fields_list:
if agg and agg != 'sum':
raise NotImplementedError('Aggregate functions other than \':sum\' are not allowed.')
return field_no_aggr
return field
fields = {truncate_aggr(field) for field in fields}
if SPECIAL.isdisjoint(aggregates):
return super()._read_group(domain, groupby, aggregates, having, offset, limit, order)
res = super(ProductProduct, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
if any(x in fields for x in fields_list):
# Calculate first for every product in which line it needs to be applied
re_ind = 0
prod_re = {}
tot_products = self.browse([])
for re in res:
if re.get('__domain'):
products = self.search(re['__domain'])
tot_products |= products
for prod in products:
prod_re[prod.id] = re_ind
re_ind += 1
res_val = tot_products._compute_product_margin_fields_values(field_names=[x for x in fields if fields in fields_list])
for key in res_val:
for l in res_val[key]:
re = res[prod_re[key]]
if re.get(l):
re[l] += res_val[key][l]
else:
re[l] = res_val[key][l]
return res
base_aggregates = [*(agg for agg in aggregates if agg not in SPECIAL), 'id:recordset']
base_result = super()._read_group(domain, groupby, base_aggregates, having, offset, limit, order)
def _compute_product_margin_fields_values(self, field_names=None):
if field_names is None:
field_names = []
# Force the compute with all records
all_records = self.browse().union(*(item[-1] for item in base_result))
all_records._compute_product_margin_fields_values()
# base_result = [(a1, b1, records), (a2, b2, records), ...]
result = []
for *other, records in base_result:
for index, spec in enumerate(itertools.chain(groupby, aggregates)):
if spec in SPECIAL:
field_name = spec.split(':')[0]
other.insert(index, sum(records.mapped(field_name)))
result.append(tuple(other))
return result
def _compute_product_margin_fields_values(self):
date_from = self.env.context.get('date_from', time.strftime('%Y-01-01'))
date_to = self.env.context.get('date_to', time.strftime('%Y-12-31'))
invoice_state = self.env.context.get('invoice_state', 'open_paid')
@@ -2,9 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _, api, fields, models
from odoo.models import regex_field_agg, VALID_AGGREGATE_FUNCTIONS
from odoo.exceptions import UserError
from odoo.tools import OrderedSet
from odoo.addons.resource.models.utils import filter_domain_leaf
@@ -54,37 +54,9 @@ FROM stock_move m
GROUP BY m.id
)""")
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
if all('on_time_rate' not in field for field in fields):
res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
return res
for field in fields:
if 'on_time_rate' not in field:
continue
fields.remove(field)
agg = field.split(':')[1:]
if agg and agg[0] != 'sum':
raise NotImplementedError('Aggregate functions other than \':sum\' are not allowed.')
qty_total = field.replace('on_time_rate', 'qty_total')
if qty_total not in fields:
fields.append(qty_total)
qty_on_time = field.replace('on_time_rate', 'qty_on_time')
if qty_on_time not in fields:
fields.append(qty_on_time)
break
res = super().read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
for group in res:
if group['qty_total'] == 0:
on_time_rate = 100
else:
on_time_rate = group['qty_on_time'] / group['qty_total'] * 100
group.update({'on_time_rate': on_time_rate})
return res
def _read_group_select(self, aggregate_spec, query):
if aggregate_spec == 'on_time_rate:sum':
# Make a weigthed average instead of simple average for these fields
sql_expression = 'SUM(qty_on_time) / SUM(qty_total) * 100'
return sql_expression, ['on_time_rate', 'qty_on_time', 'qty_total']
return super()._read_group_select(aggregate_spec, query)
+10 -21
View File
@@ -293,27 +293,16 @@ class StockQuant(models.Model):
""" Only allowed fields should be modified """
return super(StockQuant, self.with_context(inventory_mode=True))._load_records_write(values)
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
""" Override to set the `inventory_quantity` field if we're in "inventory mode" as well
as to compute the sum of the `available_quantity` field.
"""
if 'available_quantity' in fields:
if 'quantity' not in fields:
fields.append('quantity')
if 'reserved_quantity' not in fields:
fields.append('reserved_quantity')
if 'inventory_quantity_auto_apply' in fields and 'quantity' not in fields:
fields.append('quantity')
result = super(StockQuant, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
for group in result:
if self.env.context.get('inventory_report_mode'):
group['inventory_quantity'] = False
if 'available_quantity' in fields:
group['available_quantity'] = group['quantity'] - group['reserved_quantity']
if 'inventory_quantity_auto_apply' in fields:
group['inventory_quantity_auto_apply'] = group['quantity']
return result
def _read_group_select(self, aggregate_spec, query):
if aggregate_spec == 'inventory_quantity:sum' and self.env.context.get('inventory_report_mode'):
return "NULL", []
if aggregate_spec == 'available_quantity:sum':
quantity_expr, quantity_fnames = self._read_group_select('quantity:sum', query)
reserved_quantity_expr, reserved_quantity_fnames = self._read_group_select('reserved_quantity:sum', query)
return f'{quantity_expr} - {reserved_quantity_expr}', quantity_fnames + reserved_quantity_fnames
if aggregate_spec == 'inventory_quantity_auto_apply:sum':
return self._read_group_select('quantity:sum', query)
return super()._read_group_select(aggregate_spec, query)
@api.model
def get_import_templates(self):
+19 -9
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import itertools
from odoo import api, fields, models, _
from odoo.tools.float_utils import float_is_zero
from odoo.tools.misc import groupby
@@ -37,19 +38,28 @@ class StockQuant(models.Model):
quant.value = quant.quantity * quant.product_id.with_company(quant.company_id).value_svl / quantity
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
def _read_group(self, domain, groupby=(), aggregates=(), having=(), offset=0, limit=None, order=None):
""" This override is done in order for the grouped list view to display the total value of
the quants inside a location. This doesn't work out of the box because `value` is a computed
field.
"""
if 'value' not in fields:
return super(StockQuant, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
res = super(StockQuant, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy)
for group in res:
if group.get('__domain'):
quants = self.search(group['__domain'])
group['value'] = sum(quant.value for quant in quants)
return res
SPECIAL = {'value:sum'}
if SPECIAL.isdisjoint(aggregates):
return super()._read_group(domain, groupby, aggregates, having, offset, limit, order)
base_aggregates = [*(agg for agg in aggregates if agg not in SPECIAL), 'id:recordset']
base_result = super()._read_group(domain, groupby, base_aggregates, having, offset, limit, order)
# base_result = [(a1, b1, records), (a2, b2, records), ...]
result = []
for *other, records in base_result:
for index, spec in enumerate(itertools.chain(groupby, aggregates)):
if spec in SPECIAL:
field_name = spec.split(':')[0]
other.insert(index, sum(records.mapped(field_name)))
result.append(tuple(other))
return result
def _apply_inventory(self):
for accounting_date, inventory_ids in groupby(self, key=lambda q: q.accounting_date):