[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
This commit is contained in:
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -33,7 +33,10 @@
|
||||
<separator/>
|
||||
<filter string="Open Tasks" name="open_tasks" domain="[('is_closed', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Rated Tasks" name="rating_task" domain="[('rating_last_value', '!=', 0.0)]" groups="project.group_project_rating"/>
|
||||
<filter name="rating_satisfied" string="Satisfied" domain="[('rating_avg', '>=', 3.66)]" groups="project.group_project_rating"/>
|
||||
<filter name="rating_okay" string="Okay" domain="[('rating_avg', '<', 3.66), ('rating_avg', '>=', 2.33)]" groups="project.group_project_rating"/>
|
||||
<filter name="dissatisfied" string="Dissatisfied" domain="[('rating_avg', '<', 2.33), ('rating_last_value', '!=', 0)]" groups="project.group_project_rating"/>
|
||||
<filter name="no_rating" string="No Rating" domain="[('rating_last_value', '=', 0)]" groups="project.group_project_rating"/>
|
||||
<separator/>
|
||||
<filter string="Unread Messages" name="message_needaction" domain="[('message_needaction', '=', True)]"/>
|
||||
<separator/>
|
||||
@@ -1121,8 +1124,8 @@
|
||||
<field name="legend_done"/>
|
||||
<field name="activity_ids"/>
|
||||
<field name="activity_state"/>
|
||||
<field name="rating_last_value"/>
|
||||
<field name="rating_ids"/>
|
||||
<field name="rating_count"/>
|
||||
<field name="rating_avg"/>
|
||||
<field name="allow_subtasks"/>
|
||||
<field name="child_text"/>
|
||||
<field name="is_private"/>
|
||||
@@ -1181,10 +1184,10 @@
|
||||
<div class="oe_kanban_bottom_left">
|
||||
<field name="priority" widget="priority"/>
|
||||
<field name="activity_ids" widget="kanban_activity"/>
|
||||
<b t-if="record.rating_ids.raw_value.length">
|
||||
<span style="font-weight:bold;" class="fa fa-fw mt4 fa-smile-o text-success" t-if="record.rating_last_value.value == 5" title="Latest Rating: Satisfied" role="img" aria-label="Happy face"/>
|
||||
<span style="font-weight:bold;" class="fa fa-fw mt4 fa-meh-o text-warning" t-if="record.rating_last_value.value == 3" title="Latest Rating: Okay" role="img" aria-label="Neutral face"/>
|
||||
<span style="font-weight:bold;" class="fa fa-fw mt4 fa-frown-o text-danger" t-if="record.rating_last_value.value == 1" title="Latest Rating: Dissatisfied" role="img" aria-label="Sad face"/>
|
||||
<b t-if="record.rating_count.raw_value > 0" groups="project.group_project_rating">
|
||||
<span style="font-weight:bold;" class="fa fa-fw mt4 fa-smile-o text-success" t-if="record.rating_avg.raw_value >= 3.66" title="Average Rating: Satisfied" role="img" aria-label="Happy face"/>
|
||||
<span style="font-weight:bold;" class="fa fa-fw mt4 fa-meh-o text-warning" t-elif="record.rating_avg.raw_value >= 2.33" title="Average Rating: Okay" role="img" aria-label="Neutral face"/>
|
||||
<span style="font-weight:bold;" class="fa fa-fw mt4 fa-frown-o text-danger" t-else="" title="Average Rating: Dissatisfied" role="img" aria-label="Sad face"/>
|
||||
</b>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right" t-if="!selection_mode">
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user