summaryrefslogtreecommitdiff
path: root/addons/project
diff options
context:
space:
mode:
Diffstat (limited to 'addons/project')
-rw-r--r--addons/project/__manifest__.py3
-rw-r--r--addons/project/controllers/__init__.py1
-rw-r--r--addons/project/controllers/rating.py92
-rw-r--r--addons/project/models/project.py15
-rw-r--r--addons/project/static/src/js/portal_rating.js27
-rw-r--r--addons/project/static/src/less/portal_rating.less34
-rw-r--r--addons/project/views/project_assets.xml (renamed from addons/project/views/project_templates.xml)8
-rw-r--r--addons/project/views/project_rating_templates.xml203
-rw-r--r--addons/project/views/project_views.xml1
9 files changed, 380 insertions, 4 deletions
diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py
index 43b36689d88..e80837b5631 100644
--- a/addons/project/__manifest__.py
+++ b/addons/project/__manifest__.py
@@ -29,8 +29,9 @@
'views/project_views.xml',
'views/res_partner_views.xml',
'views/res_config_settings_views.xml',
- 'views/project_templates.xml',
+ 'views/project_assets.xml',
'views/project_portal_templates.xml',
+ 'views/project_rating_templates.xml',
'data/project_mail_template_data.xml',
'wizard/project_task_merge_wizard_views.xml',
],
diff --git a/addons/project/controllers/__init__.py b/addons/project/controllers/__init__.py
index 903b755e71e..10825353c81 100644
--- a/addons/project/controllers/__init__.py
+++ b/addons/project/controllers/__init__.py
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import portal
+from . import rating
diff --git a/addons/project/controllers/rating.py b/addons/project/controllers/rating.py
new file mode 100644
index 00000000000..3343674c0e8
--- /dev/null
+++ b/addons/project/controllers/rating.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from werkzeug.exceptions import NotFound
+
+from odoo import http
+from odoo.http import request
+
+
+class RatingProject(http.Controller):
+
+ @http.route(['/project/rating/'], type='http', auth="public", website=True)
+ def index(self, **kw):
+ projects = request.env['project.project'].sudo().search([('rating_status', '!=', 'no'), ('portal_show_rating', '=', True)])
+ values = {'projects': projects}
+ return request.render('project.rating_index', values)
+
+ def _calculate_period_partner_stats(self, project_id):
+ # get raw data: number of rating by rated partner, by rating value, by period
+ request.env.cr.execute("""
+ SELECT
+ rated_partner_id,
+ rating,
+ COUNT(rating) as rating_count,
+ CASE
+ WHEN now()::date - create_date::date BETWEEN 0 AND 6 Then 'days_06'
+ WHEN now()::date - create_date::date BETWEEN 0 AND 15 Then 'days_15'
+ WHEN now()::date - create_date::date BETWEEN 0 AND 30 Then 'days_30'
+ WHEN now()::date - create_date::date BETWEEN 0 AND 90 Then 'days_90'
+ END AS period
+ FROM
+ rating_rating
+ WHERE
+ parent_res_model = 'project.project'
+ AND parent_res_id = %s
+ AND res_model = 'project.task'
+ AND rated_partner_id IS NOT NULL
+ AND create_date >= current_date - interval '90' day
+ AND rating IN (1,5,10)
+ GROUP BY
+ rated_partner_id, rating, period
+ """ % (project_id))
+
+ raw_data = request.env.cr.dictfetchall()
+
+ # periodical statistics
+ default_period_dict = {'rating_10': 0, 'rating_5': 0, 'rating_1': 0, 'total': 0}
+ period_statistics = {
+ 'days_06': dict(default_period_dict),
+ 'days_15': dict(default_period_dict),
+ 'days_30': dict(default_period_dict),
+ 'days_90': dict(default_period_dict),
+ }
+ for period_statistics_key in period_statistics.keys():
+ for row in raw_data:
+ if row['period'] <= period_statistics_key:
+ period_statistics[period_statistics_key]['rating_%s' % (int(row['rating']),)] += row['rating_count']
+ period_statistics[period_statistics_key]['total'] += row['rating_count']
+
+ # partner statistics
+ default_partner_dict = {'rating_10': 0, 'rating_5': 0, 'rating_1': 0, 'total': 0, 'rated_partner': None, 'percentage_happy': 0.0}
+ partner_statistics = {}
+ for row in raw_data:
+ if row['period'] <= 'days_15':
+ if row['rated_partner_id'] not in partner_statistics:
+ partner_statistics[row['rated_partner_id']] = dict(default_partner_dict)
+ partner_statistics[row['rated_partner_id']]['rated_partner'] = request.env['res.partner'].sudo().browse(row['rated_partner_id'])
+ partner_statistics[row['rated_partner_id']]['rating_%s' % (int(row['rating']),)] += row['rating_count']
+ partner_statistics[row['rated_partner_id']]['total'] += row['rating_count']
+
+ for partner_id, stat_values in partner_statistics.items():
+ stat_values['percentage_happy'] = (stat_values['rating_10'] / float(stat_values['total'])) * 100 if stat_values['total'] else 0.0
+
+ return {
+ 'partner_statistics': partner_statistics,
+ 'period_statistics': period_statistics,
+ }
+
+ @http.route(['/project/rating/<int:project_id>'], type='http', auth="public", website=True)
+ def page(self, project_id=None, **kw):
+ user = request.env.user
+ project = request.env['project.project'].sudo().browse(project_id)
+ # to avoid giving any access rights on projects to the public user, let's use sudo
+ # and check if the user should be able to view the project (project managers only if it's unpublished or has no rating)
+ if not ((project.rating_status != 'no') and project.portal_show_rating) and not user.sudo(user).has_group('project.group_project_manager'):
+ raise NotFound()
+
+ return request.render('project.rating_project_rating_page', {
+ 'project': project,
+ 'ratings': request.env['rating.rating'].sudo().search([('consumed', '=', True), ('parent_res_model', '=', 'project.project'), ('parent_res_id', '=', project_id)], order='write_date DESC', limit=50),
+ 'statistics': self._calculate_period_partner_stats(project_id),
+ })
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index c1e802839da..1df6bbad540 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -235,9 +235,11 @@ class Project(models.Model):
"- Periodical Rating: Email will be sent periodically\n\n"
"Don't forget to set up the mail templates on the stages for which you want to get the customer's feedbacks.", default="no", required=True)
rating_status_period = fields.Selection([
- ('daily', 'Daily'), ('weekly', 'Weekly'), ('bimonthly', 'Twice a Month'),
- ('monthly', 'Once a Month'), ('quarterly', 'Quarterly'), ('yearly', 'Yearly')
- ], 'Rating Frequency')
+ ('daily', 'Daily'), ('weekly', 'Weekly'), ('bimonthly', 'Twice a Month'),
+ ('monthly', 'Once a Month'), ('quarterly', 'Quarterly'), ('yearly', 'Yearly')
+ ], 'Rating Frequency')
+
+ portal_show_rating = fields.Boolean('Rating visible publicly', copy=False, oldname='website_published')
_sql_constraints = [
('project_date_greater', 'check(date >= date_start)', 'Error! project start-date must be lower than project end-date.')
@@ -405,6 +407,13 @@ class Project(models.Model):
@api.multi
def action_view_all_rating(self):
""" return the action to see all the rating of the project, and activate default filters """
+ if self.portal_show_rating:
+ return {
+ 'type': 'ir.actions.act_url',
+ 'name': "Redirect to the Website Projcet Rating Page",
+ 'target': 'self',
+ 'url': "/project/rating/%s" % (self.id,)
+ }
action = self.env['ir.actions.act_window'].for_xml_id('project', 'rating_rating_action_view_project_rating')
action['name'] = _('Ratings of %s') % (self.name,)
action_context = safe_eval(action['context']) if action['context'] else {}
diff --git a/addons/project/static/src/js/portal_rating.js b/addons/project/static/src/js/portal_rating.js
new file mode 100644
index 00000000000..12740b9bbba
--- /dev/null
+++ b/addons/project/static/src/js/portal_rating.js
@@ -0,0 +1,27 @@
+odoo.define('website_rating_project.rating', function (require) {
+'use strict';
+
+ var time = require('web.time');
+ require('web.dom_ready');
+
+ if(!$('.o_portal_project_rating').length) {
+ return $.Deferred().reject("DOM doesn't contain '.o_portal_project_rating'");
+ }
+
+ /**
+ * Rating popover with some informations
+ */
+ $('.o_portal_project_rating .o_rating_image').popover({
+ placement: 'bottom',
+ trigger: 'hover',
+ html: 'true',
+ content: function () {
+ var id = $(this).data('id');
+ var rating_date = $(this).data('rating-date');
+ var base_date = time.auto_str_to_date(rating_date);
+ var duration = moment(base_date).fromNow();
+ $("#rating_"+ id).find(".rating_timeduration").text(duration);
+ return $("#rating_"+ id).html();
+ }
+ });
+});
diff --git a/addons/project/static/src/less/portal_rating.less b/addons/project/static/src/less/portal_rating.less
new file mode 100644
index 00000000000..4ae3290a75b
--- /dev/null
+++ b/addons/project/static/src/less/portal_rating.less
@@ -0,0 +1,34 @@
+.o_portal_project_rating {
+ .thumbnail{
+ height: 240px;
+ }
+ .o_top_partner_rating_image {
+ height: 15px;
+ }
+ .o_top_partner_image {
+ height: 30px;
+ width: 30px;
+ }
+ .o_top_partner_feedback{
+ word-wrap: break-word;
+ }
+ .o_vertical_separator {
+ border-left: 1px solid #eeeeee
+ }
+ .o_rating_progress {
+ margin-bottom: 10px;
+ }
+ .o_rating_count {
+ display: inline-block;
+ min-width: 22px
+ }
+ .o_smiley_no_padding_left {
+ padding-left: 0;
+ }
+ .o_smiley_no_padding_right {
+ padding-right: 0;
+ }
+ .o_lighter_smileys {
+ opacity: 0.4
+ }
+} \ No newline at end of file
diff --git a/addons/project/views/project_templates.xml b/addons/project/views/project_assets.xml
index 9b6d13d2582..7a2c4647984 100644
--- a/addons/project/views/project_templates.xml
+++ b/addons/project/views/project_assets.xml
@@ -8,6 +8,14 @@
<link rel="stylesheet" href="/project/static/src/less/project_dashboard.less"/>
</xpath>
</template>
+
+ <template id="assets_frontend" inherit_id="web.assets_frontend" name="Rating Project Assets">
+ <xpath expr="." position="inside">
+ <link rel="stylesheet" href="/project/static/src/less/portal_rating.less"/>
+ <script type="text/javascript" src="/project/static/src/js/portal_rating.js"/>
+ </xpath>
+ </template>
+
<template id="qunit_suite" name="project_test" inherit_id="web.qunit_suite">
<xpath expr="//t[@t-set='head']" position="inside">
<script type="text/javascript" src="/project/static/tests/project_tests.js"></script>
diff --git a/addons/project/views/project_rating_templates.xml b/addons/project/views/project_rating_templates.xml
new file mode 100644
index 00000000000..7ac2e9463ba
--- /dev/null
+++ b/addons/project/views/project_rating_templates.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+
+ <!-- Page : List of project -->
+ <template id="rating_index" name="Project Rating List">
+ <t t-call="portal.frontend_layout">
+ <t t-set="additional_title">Project Satisfaction</t>
+ <div id="wrap">
+ <div class="oe_structure"/>
+ <div class="container o_portal_project_rating">
+ <div class="row text-center">
+ <h2>Projects</h2>
+ </div>
+ <div class="oe_structure"/>
+ <div class="row">
+ <t t-if="not projects">
+ <div class="mt64 text-center">
+ No project rating puslished for now.
+ </div>
+ </t>
+ <t t-foreach="projects" t-as="project">
+ <div t-if="project.percentage_satisfaction_project != -1" class="col-sm-6 col-xs-4 col-md-4 col-lg-4">
+ <div class="thumbnail">
+ <div>
+ <div class="caption">
+ <span class="label label-default pull-right"><t t-esc="project.privacy_visibility"/></span>
+ <h4><t t-esc="project.name"/></h4>
+ <p t-if="project.date" class="text-muted">
+ <i class="fa fa-calendar"/> End date : <t t-esc="project.date"/>
+ </p>
+ <p t-if="project.alias_name and project.alias_domain" class="text-muted">
+ <i class="fa fa-envelope"/> Email : <t t-esc="project.alias_name"/>@<t t-esc="project.alias_domain"/>
+ </p>
+ <div class="row">
+ <div class="col-md-6 text-center">
+ <h2><t t-esc="len(project.task_ids)"/></h2>
+ <p><t t-esc="project.label_tasks"/></p>
+ </div>
+ </div>
+ </div>
+ <div class="caption">
+ <p>
+ <a t-att-href="'/project/rating/%s' % project.id" class="btn btn-primary btn-lg btn-block">
+ <i class="fa fa-arrow-circle-right "/> See the feedbacks
+ </a>
+ </p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </t>
+ </div>
+ </div>
+ <div class="oe_structure"/>
+ </div>
+ </t>
+ </template>
+
+ <!-- Page : Project Rating Page (and widgets) -->
+ <template id="portal_project_rating_progressbar">
+ <div class="progress o_rating_progress">
+ <t t-set="percent_rating_10" t-value="(stats['rating_10'] / float(stats['total'])) * 100 if stats['total'] else 0.0"/>
+ <div class="progress-bar progress-bar-success" t-attf-style="width: #{percent_rating_10}%;" title="Happy">
+ <t t-esc="percent_rating_10"/>%
+ </div>
+ <t t-set="percent_rating_5" t-value="(stats['rating_5'] / float(stats['total'])) * 100 if stats['total'] else 0.0"/>
+ <div class="progress-bar progress-bar-warning" t-attf-style="width: #{percent_rating_5}%;" title="Average">
+ <t t-esc="percent_rating_5"/>%
+ </div>
+ <t t-set="percent_rating_1" t-value="(stats['rating_1'] / float(stats['total'])) * 100 if stats['total'] else 0.0"/>
+ <div class="progress-bar progress-bar-danger" t-attf-style="width: #{percent_rating_1}%;" title="Bad">
+ <t t-esc="percent_rating_1"/>%
+ </div>
+ </div>
+ </template>
+
+ <template id="portal_project_rating_partner_stat">
+ <t t-foreach="partner_stats.values()" t-as="partner_stat">
+ <div class="row mt8" t-if="partner_stat['total']" title="Top 5 partner ratings of last 15 days.">
+ <div class="col-sm-1">
+ <img t-if="partner_stat['rated_partner'].image" class="o_top_partner_image" t-attf-src="data:image/png;base64,#{partner_stat['rated_partner'].image}"/>
+ <img t-if="not partner_stat['rated_partner'].image" class="o_top_partner_image" src='/web/static/src/img/placeholder.png'/>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right">
+ <span class="ml8" t-esc="partner_stat['rated_partner'].name"/>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right">
+ <div class="col-sm-4 o_smiley_no_padding_right o_smiley_no_padding_left">
+ <span t-if="partner_stat['rating_10']">
+ <img title="happy" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_10.png'/>
+ <span class="o_rating_count" t-esc="partner_stat['rating_10']"/>
+ </span>
+ <span t-if="not partner_stat['rating_10']">
+ <img title="happy" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_10.png'/>
+ <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_10']"/>
+ </span>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right o_smiley_no_padding_left">
+ <span t-if="partner_stat['rating_5']">
+ <img title="happy" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_5.png'/>
+ <span class="o_rating_count" t-esc="partner_stat['rating_5']"/>
+ </span>
+ <span t-if="not partner_stat['rating_5']">
+ <img title="happy" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_5.png'/>
+ <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_5']"/>
+ </span>
+ </div>
+ <div class="col-sm-4 o_smiley_no_padding_right o_smiley_no_padding_left">
+ <span t-if="partner_stat['rating_1']">
+ <img title="happy" class="o_top_partner_rating_image" src='/rating/static/src/img/rating_1.png'/>
+ <span class="o_rating_count" t-esc="partner_stat['rating_1']"/>
+ </span>
+ <span t-if="not partner_stat['rating_1']">
+ <img title="happy" class="o_top_partner_rating_image o_lighter_smileys" src='/rating/static/src/img/rating_1.png'/>
+ <span class="o_rating_count o_lighter_smileys" t-esc="partner_stat['rating_1']"/>
+ </span>
+ </div>
+ </div>
+ <div class="col-sm-3 o_smiley_no_padding_left">
+ <strong class="pull-right"><t t-esc="partner_stat['percentage_happy']"/>%</strong>
+ </div>
+ </div>
+ </t>
+ </template>
+
+ <template id="portal_project_rating_popover">
+ <div t-attf-id="rating_#{rating.id}" class="container hidden">
+ <div class="row">
+ <div class="col-md-2">
+ <img t-if="rating.partner_id.image" class="o_top_partner_image" t-attf-src="data:image/png;base64,#{rating.partner_id.image}"/>
+ <img t-if="not rating.partner_id.image" class="o_top_partner_image" src='/web/static/src/img/placeholder.png'/>
+ </div>
+ <div class="col-md-10">
+ <div class="mt4">
+ <strong><t t-esc="rating.rated_partner_id.name"/></strong> - <span class="rating_timeduration"><t t-esc="rating.create_date"/></span>
+ </div>
+ <strong>Rated by: </strong><t t-esc="rating.partner_id.name" />
+ </div>
+ </div>
+ <div class="row o_top_partner_feedback" t-if="rating.feedback">
+ <div class="col-md-12">
+ <t t-esc="rating.feedback" />
+ </div>
+ </div>
+ </div>
+ </template>
+
+ <template id="rating_project_rating_page" name="Project Rating Page">
+ <t t-call="portal.frontend_layout">
+ <div id="wrap">
+ <div class="oe_structure"/>
+ <div class="container o_project o_portal_project_rating">
+ <h1 t-esc="project.name" class="text-center"/>
+ <div class="row mb32">
+ <div>
+ <h3 class="page-header">Customer Ratings</h3>
+ <div class="row">
+ <div class="col-md-7">
+ <t t-foreach="ratings" t-as="rating">
+ <img t-attf-src='/rating/static/src/img/rating_#{int(rating.rating)}.png'
+ class="mt4 o_rating_image"
+ t-att-data-id="rating.id"
+ t-att-data-rating-date="rating.write_date"/>
+ <t t-call="project.portal_project_rating_popover"/>
+ </t>
+ </div>
+ <div class="col-md-5 o_vertical_separator">
+ <div t-if="statistics['period_statistics']['days_06']">
+ <h5>Last 7 days</h5>
+ <t t-call="project.portal_project_rating_progressbar">
+ <t t-set="stats" t-value="statistics['period_statistics']['days_06']"/>
+ </t>
+ </div>
+ <div t-if="statistics['period_statistics']['days_30']">
+ <h5>Last 30 days</h5>
+ <t t-call="project.portal_project_rating_progressbar">
+ <t t-set="stats" t-value="statistics['period_statistics']['days_30']"/>
+ </t>
+ </div>
+ <div t-if="statistics['period_statistics']['days_90']">
+ <h5>Last 3 months</h5>
+ <t t-call="project.portal_project_rating_progressbar">
+ <t t-set="stats" t-value="statistics['period_statistics']['days_90']"/>
+ </t>
+ </div>
+ <div>
+ <t t-call="project.portal_project_rating_partner_stat">
+ <t t-set="partner_stats" t-value="statistics['partner_statistics']"/>
+ </t>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="oe_structure"/>
+ </div>
+ </t>
+ </template>
+
+ </data>
+</odoo>
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 3fc53cf4892..cf6ef36411c 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -152,6 +152,7 @@
</p>
</div>
<field name="rating_status_period" class="oe_inline" attrs="{'required': [('rating_status','=','periodic')], 'invisible': [('rating_status','!=','periodic')]}" groups="project.group_project_rating"/>
+ <field name="portal_show_rating" attrs="{'invisible': [('rating_status','==','no')]}" groups="project.group_project_rating"/>
</group>
<group name="misc">
<group string="Configuration" groups="base.group_no_one">