From 89f4370043f53d93e6f07835b03f9f355be2edc9 Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Fri, 26 Nov 2021 15:18:19 +0100 Subject: [PATCH] [IMP] rating,project: add average ratings field in parent rating mixin Before this commit, we don't have any filters to the models using the parent.rating.mixin to know if the customers are globally satisfied or not to the projects for instance. This commit adds the `rating_avg` field to compute the average of all ratings linked to the parent model (in our example, `project.project` model). This field is also used in the project search view to add 3 filters. 1. satisfied: projects with an average customer satisfaction between 66% and 100%. 2. okay: projects with an average customer satisfaction between 33% and 65%. 3. dissatisfied: projects with an average customer satisfaction between 0% and 32%. Another filter is added to find all projects without any ratings linked, called `No Rating`. task-2671848 Closes #81027 --- addons/project/views/project_views.xml | 5 +++++ addons/rating/models/rating_mixin.py | 29 +++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 0bb19d55a8a..46e8a77ffc3 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -535,6 +535,11 @@ + + + + + diff --git a/addons/rating/models/rating_mixin.py b/addons/rating/models/rating_mixin.py index ff5f55d1583..f6ef3ce06b5 100644 --- a/addons/rating/models/rating_mixin.py +++ b/addons/rating/models/rating_mixin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import operator +from collections import defaultdict from datetime import timedelta from odoo import api, fields, models, tools @@ -32,11 +33,14 @@ class RatingParentMixin(models.AbstractModel): compute="_compute_rating_percentage_satisfaction", compute_sudo=True, store=False, help="Percentage of happy ratings") rating_count = fields.Integer(string='# Ratings', compute="_compute_rating_percentage_satisfaction", compute_sudo=True) + rating_avg = fields.Float('Average Rating', groups='base.group_user', + compute='_compute_rating_percentage_satisfaction', compute_sudo=True, search='_search_rating_avg') + rating_last_value = fields.Float('Rating Last Value', groups='base.group_user', related='rating_ids.rating') @api.depends('rating_ids.rating', 'rating_ids.consumed') def _compute_rating_percentage_satisfaction(self): # build domain and fetch data - domain = [('parent_res_model', '=', self._name), ('parent_res_id', 'in', self.ids), ('rating', '>=', 1), ('consumed', '=', True)] + domain = [('parent_res_model', '=', self._name), ('parent_res_id', 'in', self.ids), ('rating', '>=', RATING_LIMIT_MIN), ('consumed', '=', True)] if self._rating_satisfaction_days: domain += [('write_date', '>=', fields.Datetime.to_string(fields.datetime.now() - timedelta(days=self._rating_satisfaction_days)))] data = self.env['rating.rating'].read_group(domain, ['parent_res_id', 'rating'], ['parent_res_id', 'rating'], lazy=False) @@ -44,6 +48,7 @@ class RatingParentMixin(models.AbstractModel): # get repartition of grades per parent id default_grades = {'great': 0, 'okay': 0, 'bad': 0} grades_per_parent = dict((parent_id, dict(default_grades)) for parent_id in self.ids) # map: {parent_id: {'great': 0, 'bad': 0, 'ok': 0}} + rating_scores_per_parent = defaultdict(int) # contains the total of the rating values per record for item in data: parent_id = item['parent_res_id'] rating = item['rating'] @@ -53,12 +58,30 @@ class RatingParentMixin(models.AbstractModel): grades_per_parent[parent_id]['okay'] += item['__count'] else: grades_per_parent[parent_id]['bad'] += item['__count'] + rating_scores_per_parent[parent_id] += rating * item['__count'] # compute percentage per parent for record in self: repartition = grades_per_parent.get(record.id, default_grades) - record.rating_count = sum(repartition.values()) - record.rating_percentage_satisfaction = repartition['great'] * 100 / sum(repartition.values()) if sum(repartition.values()) else -1 + rating_count = sum(repartition.values()) + record.rating_count = rating_count + record.rating_percentage_satisfaction = repartition['great'] * 100 / rating_count if rating_count else -1 + record.rating_avg = rating_scores_per_parent[record.id] / rating_count if rating_count else 0 + + def _search_rating_avg(self, operator, value): + if operator not in OPERATOR_MAPPING: + raise NotImplementedError('This operator %s is not supported in this search method.' % operator) + domain = [('parent_res_model', '=', self._name), ('consumed', '=', True), ('rating', '>=', RATING_LIMIT_MIN)] + if self._rating_satisfaction_days: + min_date = fields.datetime.now() - timedelta(days=self._rating_satisfaction_days) + domain = expression.AND([domain, [('write_date', '>=', fields.Datetime.to_string(min_date))]]) + rating_read_group = self.env['rating.rating'].sudo().read_group(domain, ['parent_res_id', 'rating_avg:avg(rating)'], ['parent_res_id']) + parent_res_ids = [ + res['parent_res_id'] + for res in rating_read_group + if OPERATOR_MAPPING[operator](float_compare(res['rating_avg'], value, 2), 0) + ] + return [('id', 'in', parent_res_ids)] class RatingMixin(models.AbstractModel):