[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
This commit is contained in:
Xavier BOL (xbo)
2022-01-05 13:58:01 +01:00
parent 790cc8698a
commit 89f4370043
2 changed files with 31 additions and 3 deletions
+5
View File
@@ -535,6 +535,11 @@
<filter string="Start Date" name="start_date" date="date_start"/>
<filter string="End Date" name="end_date" date="date"/>
<separator/>
<filter name="rating_satisfied" string="Satisfied" domain="[('rating_active', '=', True), ('rating_avg', '&gt;=', 3.66)]" groups="project.group_project_rating"/>
<filter name="rating_okay" string="Okay" domain="[('rating_active', '=', True), ('rating_avg', '&lt;', 3.66), ('rating_avg', '&gt;=', 2.33)]" groups="project.group_project_rating"/>
<filter name="dissatisfied" string="Dissatisfied" domain="[('rating_active', '=', True), ('rating_avg', '&lt;', 2.33), ('rating_last_value', '!=', 0)]" groups="project.group_project_rating"/>
<filter name="no_rating" string="No Rating" domain="['|', ('rating_active', '=', False), ('rating_avg', '=', 0)]" groups="project.group_project_rating"/>
<separator/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<group expand="0" string="Group By">
<filter string="Project Manager" name="Manager" context="{'group_by': 'user_id'}"/>
+26 -3
View File
@@ -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):