From 790cc8698afd307add2a7d67697ddbbb4db1054b Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Fri, 19 Nov 2021 17:24:19 +0100 Subject: [PATCH] [IMP] rating,project: use the average of rating value for satisfaction Beforer this commit, we display a emoji to show if the customer is satisfied, okay or not for the task via the last rating value. The problem is we use only the value for the last rating for each task. This commit uses all ratings for each by using the average of rating value for each task to know if all the customers that done a rating are satisfied or not. Moreover, we add 4 filters in the project.task model using the average of rating values. 1. Satisfied => rating_avg >= 3.66 2. Okay => 2.33 >= rating_avg < 3.66 3. Dissatisfied => rating_avg < 2.33 4. No rating => show the tasks without any ratings. task-2671848 Closes #81027 --- addons/project/tests/test_project_flow.py | 8 +++++++ addons/project/views/project_views.xml | 17 ++++++++------ addons/rating/models/rating_mixin.py | 27 ++++++++++++++++++++++- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/addons/project/tests/test_project_flow.py b/addons/project/tests/test_project_flow.py index f4f539d3ec4..0b88260ac1f 100644 --- a/addons/project/tests/test_project_flow.py +++ b/addons/project/tests/test_project_flow.py @@ -248,7 +248,9 @@ class TestProjectFlow(TestProjectCommon, MockEmail): self.assertEqual(rating_good.parent_res_id, self.project_pigs.id) self.assertEqual(self.project_goats.rating_percentage_satisfaction, -1) + self.assertEqual(self.project_goats.rating_avg, 0, 'Since there is no rating in this project, the Average Rating should be equal to 0.') self.assertEqual(self.project_pigs.rating_percentage_satisfaction, 0) # There is a rating but not a "great" on, just an "okay". + self.assertEqual(self.project_pigs.rating_avg, rating_bad.rating, 'Since there is only one rating the Average Rating should be equal to the rating value of this one.') # Consuming rating_good first_task.rating_apply(5, rating_good.access_token) @@ -257,10 +259,14 @@ class TestProjectFlow(TestProjectCommon, MockEmail): # Our One2Many is linked to a res_id (int) for which the orm doesn't create an inverse first_task.invalidate_cache() + rating_avg = (rating_good.rating + rating_bad.rating) / 2 self.assertEqual(first_task.rating_count, 2, "Task should have two ratings associated with it") + self.assertEqual(first_task.rating_avg_text, 'top') self.assertEqual(rating_good.parent_res_id, self.project_pigs.id) self.assertEqual(self.project_goats.rating_percentage_satisfaction, -1) self.assertEqual(self.project_pigs.rating_percentage_satisfaction, 50) + self.assertEqual(self.project_pigs.rating_avg, rating_avg) + self.assertEqual(self.project_pigs.rating_avg_percentage, rating_avg / 5) # We change the task from project_pigs to project_goats, ratings should be associated with the new project first_task.project_id = self.project_goats.id @@ -271,7 +277,9 @@ class TestProjectFlow(TestProjectCommon, MockEmail): self.assertEqual(rating_good.parent_res_id, self.project_goats.id) self.assertEqual(self.project_goats.rating_percentage_satisfaction, 50) + self.assertEqual(self.project_goats.rating_avg, rating_avg) self.assertEqual(self.project_pigs.rating_percentage_satisfaction, -1) + self.assertEqual(self.project_pigs.rating_avg, 0) def test_task_with_no_project(self): """ diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml index 9e2261a573c..0bb19d55a8a 100644 --- a/addons/project/views/project_views.xml +++ b/addons/project/views/project_views.xml @@ -33,7 +33,10 @@ - + + + + @@ -1121,8 +1124,8 @@ - - + + @@ -1181,10 +1184,10 @@
- - - - + + + +
diff --git a/addons/rating/models/rating_mixin.py b/addons/rating/models/rating_mixin.py index 46b9f43e5f5..ff5f55d1583 100644 --- a/addons/rating/models/rating_mixin.py +++ b/addons/rating/models/rating_mixin.py @@ -1,10 +1,21 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import operator from datetime import timedelta from odoo import api, fields, models, tools from odoo.addons.rating.models.rating import RATING_LIMIT_SATISFIED, RATING_LIMIT_OK, RATING_LIMIT_MIN from odoo.osv import expression +from odoo.tools.float_utils import float_compare + +OPERATOR_MAPPING = { + '=': operator.eq, + '!=': operator.ne, + '<': operator.lt, + '<=': operator.le, + '>': operator.gt, + '>=': operator.ge, +} class RatingParentMixin(models.AbstractModel): @@ -59,7 +70,8 @@ class RatingMixin(models.AbstractModel): rating_last_feedback = fields.Text('Rating Last Feedback', groups='base.group_user', related='rating_ids.feedback') rating_last_image = fields.Binary('Rating Last Image', groups='base.group_user', related='rating_ids.rating_image') rating_count = fields.Integer('Rating count', compute="_compute_rating_stats", compute_sudo=True) - rating_avg = fields.Float("Rating Average", compute='_compute_rating_stats', compute_sudo=True) + rating_avg = fields.Float("Average Rating", + compute='_compute_rating_stats', compute_sudo=True, search='_search_rating_avg') rating_percentage_satisfaction = fields.Float("Rating Satisfaction", compute='_compute_rating_satisfaction', compute_sudo=True) rating_last_text = fields.Selection(string="Rating Text", groups='base.group_user', related="rating_ids.rating_text") @@ -79,6 +91,19 @@ class RatingMixin(models.AbstractModel): record.rating_count = mapping.get(record.id, {}).get('rating_count', 0) record.rating_avg = mapping.get(record.id, {}).get('rating_avg', 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) + rating_read_group = self.env['rating.rating'].sudo().read_group( + [('res_model', '=', self._name), ('consumed', '=', True), ('rating', '>=', RATING_LIMIT_MIN)], + ['res_id', 'rating_avg:avg(rating)'], ['res_id']) + res_ids = [ + res['res_id'] + for res in rating_read_group + if OPERATOR_MAPPING[operator](float_compare(res['rating_avg'], value, 2), 0) + ] + return [('id', 'in', res_ids)] + @api.depends('rating_ids.res_id', 'rating_ids.rating') def _compute_rating_satisfaction(self): """ Compute the rating satisfaction percentage, this is done separately from rating_count and rating_avg