diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml index dce70b92f63..12569477dcd 100644 --- a/addons/sale/views/sale_views.xml +++ b/addons/sale/views/sale_views.xml @@ -391,6 +391,7 @@ + diff --git a/addons/sale_crm/views/sale_order_views.xml b/addons/sale_crm/views/sale_order_views.xml index b6e40ca450f..571614e1cae 100644 --- a/addons/sale_crm/views/sale_order_views.xml +++ b/addons/sale_crm/views/sale_order_views.xml @@ -31,10 +31,14 @@ + + + + + + + - - - diff --git a/addons/utm/static/description/icon.png b/addons/utm/static/description/icon.png new file mode 100644 index 00000000000..07597483d01 Binary files /dev/null and b/addons/utm/static/description/icon.png differ diff --git a/addons/utm/views/utm.xml b/addons/utm/views/utm.xml index 860d6595c95..ca27f7b9b61 100644 --- a/addons/utm/views/utm.xml +++ b/addons/utm/views/utm.xml @@ -1,6 +1,6 @@ - + diff --git a/addons/website/models/res_config_settings.py b/addons/website/models/res_config_settings.py index 8e5c61213dc..60332d9e1a4 100644 --- a/addons/website/models/res_config_settings.py +++ b/addons/website/models/res_config_settings.py @@ -27,6 +27,7 @@ class ResConfigSettings(models.TransientModel): cdn_url = fields.Char(related='website_id.cdn_url') cdn_filters = fields.Text(related='website_id.cdn_filters') module_website_version = fields.Boolean("A/B Testing") + module_website_links = fields.Boolean(string="Link Trackers") favicon = fields.Binary('Favicon', related='website_id.favicon') # Set as global config parameter since methods using it are not website-aware. To be changed diff --git a/addons/website/static/src/less/website.backendDashboard.less b/addons/website/static/src/less/website.backendDashboard.less index f920418e9fc..2fa66f00d16 100644 --- a/addons/website/static/src/less/website.backendDashboard.less +++ b/addons/website/static/src/less/website.backendDashboard.less @@ -20,7 +20,7 @@ h4 { margin: 0 0 0 8px; } - .col-md-5, .col-md-7, .col-md-12 { + .col-md-6, .col-md-12 { padding: 15px; .js_field_selection { diff --git a/addons/website/views/res_config_settings_views.xml b/addons/website/views/res_config_settings_views.xml index 8ef61e3db6c..f16592e3eb0 100644 --- a/addons/website/views/res_config_settings_views.xml +++ b/addons/website/views/res_config_settings_views.xml @@ -180,6 +180,17 @@ + + + + + + + + Track clicks on UTM links + + + diff --git a/addons/website_sale/static/src/js/website_sale_backend.js b/addons/website_sale/static/src/js/website_sale_backend.js index 543eed147ea..677219c5ef5 100644 --- a/addons/website_sale/static/src/js/website_sale_backend.js +++ b/addons/website_sale/static/src/js/website_sale_backend.js @@ -30,4 +30,6 @@ WebsiteBackend.include({ }); }, }); +return WebsiteBackend; + }); diff --git a/addons/website_sale/static/src/xml/website_sale_dashboard.xml b/addons/website_sale/static/src/xml/website_sale_dashboard.xml index 1f155c07f5f..8aac2b4d65d 100644 --- a/addons/website_sale/static/src/xml/website_sale_dashboard.xml +++ b/addons/website_sale/static/src/xml/website_sale_dashboard.xml @@ -30,14 +30,14 @@ - + Sales Since Last Sales - + @@ -62,58 +62,62 @@ - AT A GLANCE - - - - + + AT A GLANCE + + + + + + Orders - Orders - - - - - - + + + + + + + Sold - Sold + + + + + Carts + + + + + Orders/Day - - - - - Carts + + + Average Order + + + + Conversion - - - - Orders/Day - - - - Average Order - - - - Conversion - Best Sellers - - - Product - Quantity - Sold - - - - - - - + + Best Sellers + + + Product + Quantity + Sold + + + + + + + + diff --git a/addons/website_sale_link_tracker/__init__.py b/addons/website_sale_link_tracker/__init__.py new file mode 100644 index 00000000000..8c67f893b37 --- /dev/null +++ b/addons/website_sale_link_tracker/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models +from . import controllers diff --git a/addons/website_sale_link_tracker/__manifest__.py b/addons/website_sale_link_tracker/__manifest__.py new file mode 100644 index 00000000000..f7c16757774 --- /dev/null +++ b/addons/website_sale_link_tracker/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'eCommerce Link Tracker', + 'description': """ +View Link Tracker Statistics on eCommerce dashboard +===================================================== + + """, + 'depends': ['website_links', 'website_sale'], + 'data': [ + 'views/sale_order_views.xml', + 'views/assets.xml', + ], + 'qweb': ['static/src/xml/*.xml'], + 'auto_install': True, +} diff --git a/addons/website_sale_link_tracker/controllers/__init__.py b/addons/website_sale_link_tracker/controllers/__init__.py new file mode 100644 index 00000000000..1be89c8b5f5 --- /dev/null +++ b/addons/website_sale_link_tracker/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import backend diff --git a/addons/website_sale_link_tracker/controllers/backend.py b/addons/website_sale_link_tracker/controllers/backend.py new file mode 100644 index 00000000000..d80261238b1 --- /dev/null +++ b/addons/website_sale_link_tracker/controllers/backend.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import http +from odoo.addons.website_sale.controllers.backend import WebsiteSaleBackend +from odoo.http import request + + +class WebsiteSaleLinkTrackerBackend(WebsiteSaleBackend): + + @http.route() + def fetch_dashboard_data(self, date_from, date_to): + results = super(WebsiteSaleLinkTrackerBackend, self).fetch_dashboard_data(date_from, date_to) + results['dashboards']['sales']['utm_graph'] = self.fetch_utm_data(date_from, date_to) + return results + + def fetch_utm_data(self, date_from, date_to): + sale_utm_domain = [ + ('team_id.team_type', '=', 'website'), + ('state', 'in', ['sale', 'done']), + ('confirmation_date', '>=', date_from), + ('confirmation_date', '<=', date_to) + ] + + orders_data_groupby_campaign_id = request.env['sale.order'].read_group( + domain=sale_utm_domain + [('campaign_id', '!=', False)], + fields=['amount_total', 'id', 'campaign_id'], + groupby='campaign_id') + + orders_data_groupby_medium_id = request.env['sale.order'].read_group( + domain=sale_utm_domain + [('medium_id', '!=', False)], + fields=['amount_total', 'id', 'medium_id'], + groupby='medium_id') + + orders_data_groupby_source_id = request.env['sale.order'].read_group( + domain=sale_utm_domain + [('source_id', '!=', False)], + fields=['amount_total', 'id', 'source_id'], + groupby='source_id') + + return { + 'campaign_id': self.compute_utm_graph_data('campaign_id', orders_data_groupby_campaign_id), + 'medium_id': self.compute_utm_graph_data('medium_id', orders_data_groupby_medium_id), + 'source_id': self.compute_utm_graph_data('source_id', orders_data_groupby_source_id), + } + + def compute_utm_graph_data(self, utm_type, utm_graph_data): + return [{ + 'utm_type': data[utm_type][1], + 'amount_total': data['amount_total'] + } for data in utm_graph_data] diff --git a/addons/website_sale_link_tracker/models/__init__.py b/addons/website_sale_link_tracker/models/__init__.py new file mode 100644 index 00000000000..00b62d54b4a --- /dev/null +++ b/addons/website_sale_link_tracker/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import sale_order diff --git a/addons/website_sale_link_tracker/models/sale_order.py b/addons/website_sale_link_tracker/models/sale_order.py new file mode 100644 index 00000000000..2356c0b01a0 --- /dev/null +++ b/addons/website_sale_link_tracker/models/sale_order.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + + +class SaleOrder(models.Model): + _name = "sale.order" + _inherit = ['utm.mixin', 'sale.order'] diff --git a/addons/website_sale_link_tracker/static/src/img/website_sale_link_tracker_chart_demo.png b/addons/website_sale_link_tracker/static/src/img/website_sale_link_tracker_chart_demo.png new file mode 100644 index 00000000000..0cb7a5cf97b Binary files /dev/null and b/addons/website_sale_link_tracker/static/src/img/website_sale_link_tracker_chart_demo.png differ diff --git a/addons/website_sale_link_tracker/static/src/js/website_sale_dashborad_utm_chart.js b/addons/website_sale_link_tracker/static/src/js/website_sale_dashborad_utm_chart.js new file mode 100644 index 00000000000..16a32bc37dc --- /dev/null +++ b/addons/website_sale_link_tracker/static/src/js/website_sale_dashborad_utm_chart.js @@ -0,0 +1,89 @@ +odoo.define('website_sale_link_tracker.backend', function (require) { +"use strict"; + +var WebsiteSaleBackend = require('website_sale.backend'); + +WebsiteSaleBackend.include({ + + events: _.defaults({ + 'click .js_utm_selector': '_onClickUtmButton', // Click event on select UTM drop-down button + }, WebsiteSaleBackend.prototype.events), + + /** + * @override method from website backendDashboard + * @private + */ + render_graphs: function() { + this._super(); + this.utmGraphData = this.dashboards_data.sales.utm_graph; + this._renderUtmGraph(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Method used to generate Pie chart, depending on user selected UTM option(campaign, medium, source) + * + * @private + */ + _renderUtmGraph: function() { + var self = this; + this.$(".utm_button_name").html(this.btnName); // change drop-down button name + var utmDataType = this.utmType || 'campaign_id'; + var graphData = this.utmGraphData[utmDataType]; + if (graphData.length) { + this.$(".o_utm_no_data_img").hide(); + this.$(".o_utm_data_graph").show(); + + this.$(".o_utm_data_graph").empty(); + nv.addGraph(function() { + var utmChart = nv.models.pieChart() + .x(function(d) {return d.utm_type; }) + .y(function(d) {return d.amount_total; }) + .showLabels(true) + .labelThreshold(0.1) + .labelType("percent") + .showLegend(false) + .margin({ "left": 0, "right": 0, "top": 0, "bottom": 0 }) + .color(['#875a7b', '#21b799', '#E4A900', '#D5653E', '#5B899E', '#E46F78', '#8F8F8F']); + + utmChart.tooltip.valueFormatter(function(value, i) { + return self.render_monetary_field(value, self.data.currency); + }); + + var svg = d3.select(".o_utm_data_graph").append("svg"); + + svg + .attr("height", "15em") + .datum(graphData) + .call(utmChart); + + nv.utils.windowResize(utmChart.update); + return utmChart; + }); + } else { + this.$(".o_utm_no_data_img").show(); + this.$(".o_utm_data_graph").hide(); + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Onchange on UTM dropdown button, this method is called. + * + * @private + */ + _onClickUtmButton: function(ev) { + this.utmType = $(ev.currentTarget).attr('name'); + this.btnName = $(ev.currentTarget).text(); + this._renderUtmGraph(); + }, + +}); + +}); diff --git a/addons/website_sale_link_tracker/static/src/less/website_sale_link_tracker.less b/addons/website_sale_link_tracker/static/src/less/website_sale_link_tracker.less new file mode 100644 index 00000000000..836eebc366f --- /dev/null +++ b/addons/website_sale_link_tracker/static/src/less/website_sale_link_tracker.less @@ -0,0 +1,19 @@ +.o_dashboard_utms { + .o_utm_data_graph { + .nv-pieLabels text { + fill: white !important; + } + .nvd3.nv-pie path { + fill-opacity: .9; + } + } + + .utm_chart_image { + display: block; + margin: auto; + } + + .utm_dropdown { + margin-top: -5px; + } +} diff --git a/addons/website_sale_link_tracker/static/src/xml/website_sale_dashboard_utm_chart.xml b/addons/website_sale_link_tracker/static/src/xml/website_sale_dashboard_utm_chart.xml new file mode 100644 index 00000000000..04a88cd59ef --- /dev/null +++ b/addons/website_sale_link_tracker/static/src/xml/website_sale_dashboard_utm_chart.xml @@ -0,0 +1,32 @@ + + + + + col-md-6 col-xs-12 + + + + + REVENUE BY + + + + + + + + + + + + + Campaigns + + + Campaigns + Medium + Sources + + + + diff --git a/addons/website_sale_link_tracker/views/assets.xml b/addons/website_sale_link_tracker/views/assets.xml new file mode 100644 index 00000000000..43beda3cee3 --- /dev/null +++ b/addons/website_sale_link_tracker/views/assets.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/addons/website_sale_link_tracker/views/sale_order_views.xml b/addons/website_sale_link_tracker/views/sale_order_views.xml new file mode 100644 index 00000000000..700aeef8776 --- /dev/null +++ b/addons/website_sale_link_tracker/views/sale_order_views.xml @@ -0,0 +1,17 @@ + + + + sale.order.form.inherit.sale + sale.order + + + + + + + + + + + +