[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:
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user