Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IMP] average calculations in group by's #157293

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions addons/web/static/src/legacy/js/views/list/list_renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ var ListRenderer = BasicRenderer.extend({

_.each(this.columns, this._computeColumnAggregates.bind(this, data));
},
_canBeAggregated: function(fieldName) {
var field = this.state.fields[fieldName];
if (!field) {
return false;
}
var type = field.type;
if (type !== 'integer' && type !== 'float' && type !== 'monetary') {
return false;
}
return true;
},
/**
* Compute the aggregate values for a given column and a set of records.
* The aggregate values are then written, if applicable, in the 'aggregate'
Expand All @@ -157,32 +168,43 @@ var ListRenderer = BasicRenderer.extend({
*/
_computeColumnAggregates: function (data, column) {
var attrs = column.attrs;
var field = this.state.fields[attrs.name];
if (!field) {
return;
}
var type = field.type;
if (type !== 'integer' && type !== 'float' && type !== 'monetary') {

if (!this._canBeAggregated(attrs.name)) {
return;
}
var func = (attrs.sum && 'sum') || (attrs.avg && 'avg') ||
(attrs.max && 'max') || (attrs.min && 'min');
if (func) {
var count = 0;
var aggregateValue = 0;
var aggregateDivisor = 0;
if (func === 'max') {
aggregateValue = -Infinity;
} else if (func === 'min') {
aggregateValue = Infinity;
}
_.each(data, function (d) {
var value = (d.type === 'record') ? d.data[attrs.name] : d.aggregateValues[attrs.name];
var fieldName = attrs.name;
if (func === 'avg' && d.fields[fieldName].average_numerator) {
fieldName = d.fields[fieldName].average_numerator;
}
var value = (d.type === 'record') ? d.data[fieldName] : d.aggregateValues[fieldName];
if (Number(value) !== value) {
return;
}
count += 1;
if (func === 'avg') {
aggregateValue += value;
if (d.fields[attrs.name].average_divisor) {
var divisorField = d.fields[attrs.name].average_divisor
var divisorValue = (d.type === 'record') ? d.data[divisorField] : d.aggregateValues[divisorField];
if (Number(divisorValue) !== divisorValue) {
return;
}
aggregateDivisor += divisorValue;
} else {
aggregateDivisor = count;
}
} else if (func === 'sum') {
aggregateValue += value;
} else if (func === 'max') {
Expand All @@ -191,10 +213,10 @@ var ListRenderer = BasicRenderer.extend({
aggregateValue = Math.min(aggregateValue, value);
}
});
if (func === 'avg') {
aggregateValue = count ? aggregateValue / count : aggregateValue;
}
if (count) {
if (func === 'avg') {
aggregateValue = aggregateDivisor ? aggregateValue / aggregateDivisor : aggregateValue;
}
column.aggregate = {
help: attrs[func],
value: aggregateValue,
Expand Down
15 changes: 15 additions & 0 deletions doc/cla/corporate/circularitgroup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
The Netherlands, 29-2-2023

Circular IT Holding B.V. agrees to the terms of the Odoo Corporate Contributor License
Agreement v1.0.

I declare that I am authorized and able to make this agreement and sign this
declaration.

Signed,

Lennart van den Dool lennart.vandendool@circularitgroup.com https://github.com/lennartvdd

List of contributors:

Lennart van den Dool lennartvdd@gmail.com https://github.com/lennartvdd
19 changes: 18 additions & 1 deletion odoo/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,20 @@ class Field(MetaField('DummyField', (object,), {})):
* ``bool_or`` : true if at least one value is true, otherwise false
* ``max`` : maximum value of all values
* ``min`` : minimum value of all values
* ``avg`` : the average (arithmetic mean) of all values
* ``avg`` : the average (arithmetic mean) of all values.
Note: In some cases the average needs to be derived from other fields.
See: ``averege_numerator`` and ``average_divisor``.
* ``sum`` : sum of all values

Averaging based on related values and avereging percentages:
In some cases taking the average of all values is not valid, for example when averaging percentages.
To get a valid aggragete percentage, we have to calculated it based on the aggregates of other values.
These fields can be defined by setting the ``average_numerator`` and ``average_divisor`` parameters.

:param str average_numerator: optional aggragate field used in conjunction with ``group_operator`` 'avg'.

:param str average_divisor: optional aggragate field used in conjunction with ``group_operator`` 'avg'.

:param str group_expand: function used to expand read_group results when grouping on
the current field.

Expand Down Expand Up @@ -283,6 +294,8 @@ def _read_group_many2one_field(self, records, domain, order):

related_field = None # corresponding related field
group_operator = None # operator for aggregating values
average_numerator = None # field for aggregating values to averages based on related field
average_divisor = None # field for aggregating values to averages based on related field
group_expand = None # name of method to expand groups in read_group()
prefetch = True # whether the field is prefetched

Expand Down Expand Up @@ -675,6 +688,8 @@ def _search_related(self, records, operator, value):
_related_help = property(attrgetter('help'))
_related_groups = property(attrgetter('groups'))
_related_group_operator = property(attrgetter('group_operator'))
_related_average_divisor = property(attrgetter('average_divisor'))
_related_average_numerator = property(attrgetter('average_numerator'))

@property
def base_field(self):
Expand Down Expand Up @@ -790,6 +805,8 @@ def get_description(self, env):
_description_change_default = property(attrgetter('change_default'))
_description_deprecated = property(attrgetter('deprecated'))
_description_group_operator = property(attrgetter('group_operator'))
_description_average_numerator = property(attrgetter('average_numerator'))
_description_average_divisor = property(attrgetter('average_divisor'))

def _description_depends(self, env):
return env.registry.field_depends[self]
Expand Down
8 changes: 8 additions & 0 deletions odoo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2550,6 +2550,14 @@ def _read_group_raw(self, domain, fields, groupby, offset=0, limit=None, orderby
expr = self._inherits_join_calc(self._table, fname, query)
if func.lower() == 'count_distinct':
term = 'COUNT(DISTINCT %s) AS "%s"' % (expr, name)
elif func.lower() == 'avg':
if field.average_numerator:
expr = self._inherits_join_calc(self._table, field.average_numerator, query)
if field.average_divisor:
expr_divisor = self._inherits_join_calc(self._table, field.average_divisor, query)
term = 'sum(%s) / sum(%s) AS "%s"' % (expr, expr_divisor, name)
else:
term = '%s(%s) AS "%s"' % (func, expr, name)
else:
term = '%s(%s) AS "%s"' % (func, expr, name)
select_terms.append(term)
Expand Down