[IMP] website, *: migrate Universal Analytics to Google Analytics 4
*: website_sale, website_sale_delivery The old analytics snippet (isogram) is replaced by the newer gtags snippet. Virtual page views and ecommerce transactions are now tracked using gtags. The e-commerce data format is updated to correspond with the new GA4 format. task-2500769 closes odoo/odoo#72233 Signed-off-by: Romain Derie (rde) <rde@odoo.com> Co-authored-by: Romain Derie <rde@odoo.com> Co-authored-by: Tom De Caluwé <tdc@odoo.com>
This commit is contained in:
co-authored by
Tom De Caluwé
parent
f19a4b99aa
commit
78bc86cbec
@@ -61,10 +61,10 @@
|
||||
</div>
|
||||
|
||||
<div t-name="website.ga_dialog_content">
|
||||
Your Tracking ID: <input type="text" name="ga_analytics_key" placeholder="UA-XXXXXXXX-Y" t-att-value="ga_analytics_key" style="width: 100%"></input>
|
||||
<a href="https://www.odoo.com/documentation/14.0/applications/websites/website/optimize/google_analytics.html" target="_blank">
|
||||
Your Measurement ID: <input type="text" name="ga_analytics_key" placeholder="G-XXXXXXXXXX" t-att-value="ga_analytics_key" style="width: 100%"></input>
|
||||
<a href="https://support.google.com/analytics/answer/9304153" target="_blank">
|
||||
<i class="fa fa-arrow-right"/>
|
||||
How to get my Tracking ID
|
||||
How to get my Measurement ID
|
||||
</a>
|
||||
<br/><br/>
|
||||
Your Client ID: <input type="text" name="ga_client_id" t-att-value="ga_key" style="width: 100%"></input>
|
||||
|
||||
@@ -9,14 +9,10 @@
|
||||
consent in your country.
|
||||
</p>
|
||||
<p>
|
||||
For session cookies, authentification and analytics*,
|
||||
For session cookies, authentification and analytics,
|
||||
<b>you do not need to ask for the consent</b> (see e.g. Opinion
|
||||
04/2012 on Cookie Consent Exemption by the EU Art.29 WP).
|
||||
</p>
|
||||
<p>
|
||||
* provided that your analytics is anonymized, which is not the
|
||||
case by default with Google Analytics.
|
||||
</p>
|
||||
</main>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -284,8 +284,8 @@
|
||||
</div>
|
||||
<div class="content-group" attrs="{'invisible': [('has_google_analytics', '=', False)]}">
|
||||
<div class="row mt16">
|
||||
<label class="col-lg-3 o_light_label" string="Tracking ID" for="google_analytics_key"/>
|
||||
<field name="google_analytics_key" placeholder="UA-XXXXXXXX-Y"
|
||||
<label class="col-lg-3 o_light_label" string="Measurement ID" for="google_analytics_key"/>
|
||||
<field name="google_analytics_key" placeholder="G-XXXXXXXXXX"
|
||||
attrs="{'required': [('has_google_analytics', '=', True)]}"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -293,7 +293,7 @@
|
||||
<a href="https://www.odoo.com/documentation/14.0/applications/websites/website/optimize/google_analytics.html"
|
||||
class="oe_link" target="_blank">
|
||||
<i class="fa fa-arrow-right"/>
|
||||
How to get my Tracking ID
|
||||
How to get my Measurement ID
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -146,15 +146,15 @@
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[@id='wrapwrap']" position="after">
|
||||
<script id='tracking_code' t-if="website and website.google_analytics_key and not editable">
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '<t t-esc="website.google_analytics_key"/>'.trim(), 'auto');
|
||||
ga('send','pageview');
|
||||
</script>
|
||||
<t t-if="website and website.google_analytics_key and not editable">
|
||||
<script id="tracking_code" async="1" t-attf-src="https://www.googletagmanager.com/gtag/js?id={{ website.google_analytics_key }}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '<t t-esc="website.google_analytics_key"/>');
|
||||
</script>
|
||||
</t>
|
||||
</xpath>
|
||||
|
||||
<!-- Page options -->
|
||||
|
||||
@@ -1095,7 +1095,10 @@ class WebsiteSale(http.Controller):
|
||||
sale_order_id = request.session.get('sale_last_order_id')
|
||||
if sale_order_id:
|
||||
order = request.env['sale.order'].sudo().browse(sale_order_id)
|
||||
return request.render("website_sale.confirmation", {'order': order})
|
||||
return request.render("website_sale.confirmation", {
|
||||
'order': order,
|
||||
'order_tracking_info': self.order_2_return_dict(order),
|
||||
})
|
||||
else:
|
||||
return request.redirect('/shop')
|
||||
|
||||
@@ -1109,16 +1112,6 @@ class WebsiteSale(http.Controller):
|
||||
else:
|
||||
return request.redirect('/shop')
|
||||
|
||||
@http.route(['/shop/tracking_last_order'], type='json', auth="public")
|
||||
def tracking_cart(self, **post):
|
||||
""" return data about order in JSON needed for google analytics"""
|
||||
ret = {}
|
||||
sale_order_id = request.session.get('sale_last_order_id')
|
||||
if sale_order_id:
|
||||
order = request.env['sale.order'].sudo().browse(sale_order_id)
|
||||
ret = self.order_2_return_dict(order)
|
||||
return ret
|
||||
|
||||
# ------------------------------------------------------
|
||||
# Edit
|
||||
# ------------------------------------------------------
|
||||
@@ -1163,10 +1156,9 @@ class WebsiteSale(http.Controller):
|
||||
for line in order_lines:
|
||||
product = line.product_id
|
||||
ret.append({
|
||||
'id': line.order_id.id,
|
||||
'sku': product.barcode or product.id,
|
||||
'name': product.name or '-',
|
||||
'category': product.categ_id.name or '-',
|
||||
'item_id': product.barcode or product.id,
|
||||
'item_name': product.name or '-',
|
||||
'item_category': product.categ_id.name or '-',
|
||||
'price': line.price_unit,
|
||||
'quantity': line.product_uom_qty,
|
||||
})
|
||||
@@ -1175,14 +1167,12 @@ class WebsiteSale(http.Controller):
|
||||
def order_2_return_dict(self, order):
|
||||
""" Returns the tracking_cart dict of the order for Google analytics basically defined to be inherited """
|
||||
return {
|
||||
'transaction': {
|
||||
'id': order.id,
|
||||
'affiliation': order.company_id.name,
|
||||
'revenue': order.amount_total,
|
||||
'tax': order.amount_tax,
|
||||
'currency': order.currency_id.name
|
||||
},
|
||||
'lines': self.order_lines_2_google_api(order.order_line)
|
||||
'transaction_id': order.id,
|
||||
'affiliation': order.company_id.name,
|
||||
'value': order.amount_total,
|
||||
'tax': order.amount_tax,
|
||||
'currency': order.currency_id.name,
|
||||
'items': self.order_lines_2_google_api(order.order_line),
|
||||
}
|
||||
|
||||
@http.route(['/shop/country_infos/<model("res.country"):country>'], type='json', auth="public", methods=['POST'], website=True)
|
||||
|
||||
@@ -5,6 +5,7 @@ from odoo.http import request
|
||||
|
||||
from odoo.addons.sale.controllers.variant import VariantController
|
||||
|
||||
|
||||
class WebsiteSaleVariantController(VariantController):
|
||||
@http.route(['/sale/get_combination_info_website'], type='json', auth="public", methods=['POST'], website=True)
|
||||
def get_combination_info_website(self, product_template_id, product_id, combination, add_qty, **kw):
|
||||
@@ -12,15 +13,17 @@ class WebsiteSaleVariantController(VariantController):
|
||||
This route is called in JS by appending _website to the base route.
|
||||
"""
|
||||
kw.pop('pricelist_id')
|
||||
res = self.get_combination_info(product_template_id, product_id, combination, add_qty, request.website.get_current_pricelist(), **kw)
|
||||
combination = self.get_combination_info(product_template_id, product_id, combination, add_qty, request.website.get_current_pricelist(), **kw)
|
||||
|
||||
carousel_view = request.env['ir.ui.view']._render_template('website_sale.shop_product_carousel',
|
||||
values={
|
||||
'product': request.env['product.template'].browse(res['product_template_id']),
|
||||
'product_variant': request.env['product.product'].browse(res['product_id']),
|
||||
})
|
||||
res['carousel'] = carousel_view
|
||||
return res
|
||||
if request.website.google_analytics_key:
|
||||
combination['product_tracking_info'] = request.env['product.template'].get_google_analytics_data(combination)
|
||||
|
||||
carousel_view = request.env['ir.ui.view']._render_template('website_sale.shop_product_carousel', values={
|
||||
'product': request.env['product.template'].browse(combination['product_template_id']),
|
||||
'product_variant': request.env['product.product'].browse(combination['product_id']),
|
||||
})
|
||||
combination['carousel'] = carousel_view
|
||||
return combination
|
||||
|
||||
@http.route(auth="public")
|
||||
def create_product_variant(self, product_template_id, product_template_attribute_value_ids, **kwargs):
|
||||
|
||||
@@ -405,3 +405,14 @@ class ProductTemplate(models.Model):
|
||||
slugs = [slug(category) for category in product.public_categ_ids]
|
||||
data['category_url'] = '/shop/category/%s' % ','.join(slugs)
|
||||
return results_data
|
||||
|
||||
@api.model
|
||||
def get_google_analytics_data(self, combination):
|
||||
product = self.env['product.product'].browse(combination['product_id'])
|
||||
return {
|
||||
'item_id': product.barcode or product.id,
|
||||
'item_name': combination['display_name'],
|
||||
'item_category': product.categ_id.name or '-',
|
||||
'currency': product.currency_id.name,
|
||||
'price': combination['list_price'],
|
||||
}
|
||||
|
||||
@@ -29,6 +29,17 @@ VariantMixin._onChangeCombination = function (ev, $parent, combination) {
|
||||
$pricePerUom.parents(".o_base_unit_price_wrapper").addClass("d-none");
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers a new JS event with the correct payload, which is then handled
|
||||
// by the google analytics tracking code.
|
||||
// Indeed, every time another variant is selected, a new view_item event
|
||||
// needs to be tracked by google analytics.
|
||||
if ('product_tracking_info' in combination) {
|
||||
const $product = $('#product_detail');
|
||||
$product.data('product-tracking-info', combination['product_tracking_info']);
|
||||
$product.trigger('view_item_event', combination['product_tracking_info']);
|
||||
}
|
||||
|
||||
originalOnChangeCombination.apply(this, [ev, $parent, combination]);
|
||||
};
|
||||
|
||||
|
||||
@@ -148,14 +148,13 @@ odoo.define('website_sale.website_sale', function (require) {
|
||||
var core = require('web.core');
|
||||
var config = require('web.config');
|
||||
var publicWidget = require('web.public.widget');
|
||||
var VariantMixin = require('sale.VariantMixin');
|
||||
var VariantMixin = require('website_sale.VariantMixin');
|
||||
var wSaleUtils = require('website_sale.utils');
|
||||
const cartHandlerMixin = wSaleUtils.cartHandlerMixin;
|
||||
require("web.zoomodoo");
|
||||
const {extraMenuUpdateCallbacks} = require('website.content.menu');
|
||||
const dom = require('web.dom');
|
||||
|
||||
|
||||
publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, cartHandlerMixin, {
|
||||
selector: '.oe_website_sale',
|
||||
events: _.extend({}, VariantMixin.events || {}, {
|
||||
@@ -533,9 +532,16 @@ publicWidget.registry.WebsiteSale = publicWidget.Widget.extend(VariantMixin, car
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_submitForm: function () {
|
||||
let params = this.rootProduct;
|
||||
params.add_qty = params.quantity;
|
||||
const params = this.rootProduct;
|
||||
|
||||
const $product = $('#product_detail');
|
||||
const productTrackingInfo = $product.data('product-tracking-info');
|
||||
if (productTrackingInfo) {
|
||||
productTrackingInfo.quantity = params.quantity;
|
||||
$product.trigger('add_to_cart_event', [productTrackingInfo]);
|
||||
}
|
||||
|
||||
params.add_qty = params.quantity;
|
||||
params.product_custom_attribute_values = JSON.stringify(params.product_custom_attribute_values);
|
||||
params.no_variant_attribute_values = JSON.stringify(params.no_variant_attribute_values);
|
||||
return this.addToCart(params);
|
||||
@@ -961,6 +967,15 @@ publicWidget.registry.websiteSaleProductPageReviews = publicWidget.Widget.extend
|
||||
this.$target.find('.o_portal_chatter_composer').css('top', dom.scrollFixedOffset() + 20);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
WebsiteSale: publicWidget.registry.WebsiteSale,
|
||||
WebsiteSaleLayout: publicWidget.registry.WebsiteSaleLayout,
|
||||
websiteSaleCart: publicWidget.registry.websiteSaleCart,
|
||||
WebsiteSaleCarouselProduct: publicWidget.registry.websiteSaleCarouselProduct,
|
||||
WebsiteSaleProductPageReviews: publicWidget.registry.websiteSaleProductPageReviews,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
odoo.define('website_sale.price_range_option', function (require) {
|
||||
|
||||
@@ -10,6 +10,8 @@ publicWidget.registry.websiteSaleTracking = publicWidget.Widget.extend({
|
||||
'click div.oe_cart a[href^="/web?redirect"][href$="/shop/checkout"]': '_onCustomerSignin',
|
||||
'click form[action="/shop/confirm_order"] a.a-submit': '_onOrder',
|
||||
'click form[target="_self"] button[type=submit]': '_onOrderPayment',
|
||||
'view_item_event': '_onViewItem',
|
||||
'add_to_cart_event': '_onAddToCart',
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -18,32 +20,13 @@ publicWidget.registry.websiteSaleTracking = publicWidget.Widget.extend({
|
||||
start: function () {
|
||||
var self = this;
|
||||
|
||||
// Watching a product
|
||||
if (this.$el.is('#product_detail')) {
|
||||
var productID = this.$('input[name="product_id"]').attr('value');
|
||||
this._vpv('/stats/ecom/product_view/' + productID);
|
||||
}
|
||||
|
||||
// ...
|
||||
if (this.$('div.oe_website_sale_tx_status').length) {
|
||||
this._trackGA('require', 'ecommerce');
|
||||
|
||||
var orderID = this.$('div.oe_website_sale_tx_status').data('order-id');
|
||||
const $confirmation = this.$('div.oe_website_sale_tx_status');
|
||||
if ($confirmation.length) {
|
||||
const orderID = $confirmation.data('order-id');
|
||||
const json = $confirmation.data('order-tracking-info');
|
||||
this._vpv('/stats/ecom/order_confirmed/' + orderID);
|
||||
|
||||
this._rpc({
|
||||
route: '/shop/tracking_last_order/',
|
||||
}).then(function (o) {
|
||||
self._trackGA('ecommerce:clear');
|
||||
|
||||
if (o.transaction && o.lines) {
|
||||
self._trackGA('ecommerce:addTransaction', o.transaction);
|
||||
_.forEach(o.lines, function (line) {
|
||||
self._trackGA('ecommerce:addItem', line);
|
||||
});
|
||||
}
|
||||
self._trackGA('ecommerce:send');
|
||||
});
|
||||
self._trackGA('event', 'purchase', json);
|
||||
}
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
@@ -57,16 +40,15 @@ publicWidget.registry.websiteSaleTracking = publicWidget.Widget.extend({
|
||||
* @private
|
||||
*/
|
||||
_trackGA: function () {
|
||||
var websiteGA = window.ga || function () {};
|
||||
const websiteGA = window.gtag || function () {};
|
||||
websiteGA.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_vpv: function (page) { //virtual page view
|
||||
this._trackGA('send', 'pageview', {
|
||||
'page': page,
|
||||
'title': document.title,
|
||||
this._trackGA('event', 'page_view', {
|
||||
'page_path': page,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -74,6 +56,30 @@ publicWidget.registry.websiteSaleTracking = publicWidget.Widget.extend({
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onViewItem(event, productTrackingInfo) {
|
||||
const trackingInfo = {
|
||||
'currency': productTrackingInfo['currency'],
|
||||
'value': productTrackingInfo['price'],
|
||||
'items': [productTrackingInfo],
|
||||
};
|
||||
this._trackGA('event', 'view_item', trackingInfo);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onAddToCart(event, ...productsTrackingInfo) {
|
||||
const trackingInfo = {
|
||||
'currency': productsTrackingInfo[0]['currency'],
|
||||
'value': productsTrackingInfo.reduce((acc, val) => acc + val['price'] * val['quantity'], 0),
|
||||
'items': productsTrackingInfo,
|
||||
};
|
||||
this._trackGA('event', 'add_to_cart', trackingInfo);
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -110,4 +116,7 @@ publicWidget.registry.websiteSaleTracking = publicWidget.Widget.extend({
|
||||
this._vpv('/stats/ecom/order_payment/' + method);
|
||||
},
|
||||
});
|
||||
|
||||
return publicWidget.registry.websiteSaleTracking;
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
odoo.define('website_sale.google_analytics', function (require) {
|
||||
'use strict';
|
||||
|
||||
const tour = require("web_tour.tour");
|
||||
const websiteSaleTracking = require('website_sale.tracking');
|
||||
const WebsiteSale = require('website_sale.website_sale').WebsiteSale;
|
||||
|
||||
let itemId;
|
||||
|
||||
tour.register('google_analytics_view_item', {
|
||||
test: true,
|
||||
url: '/shop?search=Customizable Desk',
|
||||
},
|
||||
[
|
||||
{
|
||||
content: "select customizable desk",
|
||||
trigger: '.oe_product_cart a:contains("Customizable Desk")',
|
||||
},
|
||||
{
|
||||
content: "check view_item events",
|
||||
trigger: '#product_detail',
|
||||
run: () => {
|
||||
// If we don't explicitly wait for the getCombinationInfo ajax
|
||||
// call, the tour fails when running in phantomjs. The actual
|
||||
// test is executed in a separate tour stage, triggered when
|
||||
// the ajax call is ready.
|
||||
WebsiteSale.getCombinationInfoPromise().then(() => {
|
||||
$('body').addClass('combination_info_ready');
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: 'body.combination_info_ready',
|
||||
run: () => {
|
||||
const events = websiteSaleTracking.getEvents('view_item');
|
||||
$('body').removeClass('combination_info_ready');
|
||||
if (events.length !== 1) {
|
||||
console.error('No view item was generated');
|
||||
} else {
|
||||
itemId = events[0]['item_id'];
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
content: 'select another variant',
|
||||
trigger: 'ul.js_add_cart_variants ul.list-inline li:has(label.active) + li:has(label) input',
|
||||
},
|
||||
{
|
||||
content: "check view_item events",
|
||||
trigger: '#product_detail',
|
||||
run: () => {
|
||||
WebsiteSale.getCombinationInfoPromise().then(() => {
|
||||
$('body').addClass('combination_info_ready');
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
trigger: 'body.combination_info_ready',
|
||||
run: () => {
|
||||
const events = websiteSaleTracking.getEvents('view_item');
|
||||
$('body').removeClass('combination_info_ready');
|
||||
if (events.length !== 2) {
|
||||
console.error('No second view event was generated');
|
||||
} else if (itemId === events[1]['item_id']) {
|
||||
console.error('The second variant has the same id as the first one');
|
||||
}
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
tour.register('google_analytics_add_to_cart', {
|
||||
test: true,
|
||||
url: '/shop?search=Acoustic Bloc Screens',
|
||||
},
|
||||
[
|
||||
{
|
||||
content: "select Acoustic Bloc Screens",
|
||||
trigger: '.oe_product_cart a:contains("Acoustic Bloc Screens")',
|
||||
},
|
||||
{
|
||||
content: "click add to cart button on product page",
|
||||
trigger: '#add_to_cart',
|
||||
},
|
||||
{
|
||||
content: 'check add to cart event',
|
||||
trigger: 'a:has(.my_cart_quantity:containsExact(1))',
|
||||
run: () => {
|
||||
const events = websiteSaleTracking.getEvents('add_to_cart');
|
||||
if (events.length !== 1) {
|
||||
console.error('No add to cart event was generated');
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
odoo.define('website_sale.tour_mock_tracking', function (require) {
|
||||
|
||||
const publicWidget = require('web.public.widget');
|
||||
const websiteSaleTracking = require('website_sale.tracking');
|
||||
|
||||
require('website_sale.website_sale');
|
||||
|
||||
const events = {
|
||||
view_item: [],
|
||||
add_to_cart: [],
|
||||
remove_from_cart: [],
|
||||
};
|
||||
|
||||
let promise;
|
||||
|
||||
publicWidget.registry.WebsiteSale.include({
|
||||
_getCombinationInfo() {
|
||||
promise = this._super(...arguments);
|
||||
return promise;
|
||||
},
|
||||
});
|
||||
|
||||
publicWidget.registry.WebsiteSale.getCombinationInfoPromise = function () {
|
||||
let result = promise;
|
||||
promise = undefined;
|
||||
return result;
|
||||
};
|
||||
|
||||
websiteSaleTracking.include({
|
||||
_onViewItem(event, data) {
|
||||
events.view_item.push(data);
|
||||
},
|
||||
_onAddToCart(event, data) {
|
||||
events.add_to_cart.push(data);
|
||||
},
|
||||
});
|
||||
|
||||
websiteSaleTracking.getEvents = function (eventName) {
|
||||
return events[eventName];
|
||||
};
|
||||
|
||||
});
|
||||
@@ -99,6 +99,11 @@ class TestUi(HttpCaseWithUserDemo):
|
||||
|
||||
self.start_tour("/", 'website_sale_tour')
|
||||
|
||||
def test_05_google_analytics_tracking(self):
|
||||
self.env['website'].browse(1).write({'google_analytics_key': 'G-XXXXXXXXXXX'})
|
||||
self.start_tour("/shop", 'google_analytics_view_item')
|
||||
self.start_tour("/shop", 'google_analytics_add_to_cart')
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestWebsiteSaleCheckoutAddress(TransactionCaseWithUserDemo):
|
||||
|
||||
@@ -568,7 +568,9 @@
|
||||
<t t-set="additional_title" t-value="product.name" />
|
||||
<div itemscope="itemscope" itemtype="http://schema.org/Product" id="wrap" class="js_sale">
|
||||
<div class="oe_structure oe_empty oe_structure_not_nearest" id="oe_structure_website_sale_product_1" data-editor-message="DROP BUILDING BLOCKS HERE TO MAKE THEM AVAILABLE ACROSS ALL PRODUCTS"/>
|
||||
<section t-attf-class="container py-4 oe_website_sale #{'discount' if combination_info['has_discounted_price'] else ''}" id="product_detail" t-att-data-view-track="view_track and '1' or '0'">
|
||||
<section t-attf-class="container py-4 oe_website_sale #{'discount' if combination_info['has_discounted_price'] else ''}" id="product_detail"
|
||||
t-att-data-view-track="view_track and '1' or '0'"
|
||||
t-att-data-product-tracking-info="json.dumps(request.env['product.template'].get_google_analytics_data(combination_info))">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<ol class="breadcrumb mb-2">
|
||||
@@ -1814,7 +1816,7 @@
|
||||
</template>
|
||||
|
||||
<template id="payment_confirmation_status">
|
||||
<div class="oe_website_sale_tx_status mt-3" t-att-data-order-id="order.id">
|
||||
<div class="oe_website_sale_tx_status mt-3" t-att-data-order-id="order.id" t-att-data-order-tracking-info="json.dumps(order_tracking_info)">
|
||||
<t t-set="payment_tx_id" t-value="order.get_portal_last_transaction()"/>
|
||||
<div t-attf-class="card #{
|
||||
(payment_tx_id.state == 'pending' and 'bg-info') or
|
||||
|
||||
@@ -305,8 +305,14 @@ publicWidget.registry.ProductComparison = publicWidget.Widget.extend(cartHandler
|
||||
this.getCartHandlerOptions(ev);
|
||||
// Override product image container for animation.
|
||||
this.$itemImgContainer = this.$('#o_comparelist_table tr').first().find('td').eq(cellIndex);
|
||||
const productId = parseInt($form.find('input[type="hidden"][name="product_id"]').first().val());
|
||||
const $inputProduct = $form.find('input[type="hidden"][name="product_id"]').first();
|
||||
const productId = parseInt($inputProduct.val());
|
||||
if (productId) {
|
||||
const productTrackingInfo = $inputProduct.data('product-tracking-info');
|
||||
if (productTrackingInfo) {
|
||||
productTrackingInfo.quantity = 1;
|
||||
$inputProduct.trigger('add_to_cart_event', [productTrackingInfo]);
|
||||
}
|
||||
return this.addToCart({product_id: productId, add_qty: 1});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -136,7 +136,8 @@
|
||||
|
||||
<form action="/shop/cart/update" method="post" class="text-center">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
|
||||
<input name="product_id" t-att-value="product.id" type="hidden"/>
|
||||
<input name="product_id" t-att-value="product.id" type="hidden"
|
||||
t-att-data-product-tracking-info="json.dumps(request.env['product.template'].get_google_analytics_data(combination_info))"/>
|
||||
<a role="button" class="btn btn-primary btn-block a-submit" href="#"><i class="fa fa-shopping-cart mr-2"></i>Add to Cart</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -77,9 +77,9 @@ class WebsiteSaleDelivery(WebsiteSale):
|
||||
def order_2_return_dict(self, order):
|
||||
""" Returns the tracking_cart dict of the order for Google analytics """
|
||||
ret = super(WebsiteSaleDelivery, self).order_2_return_dict(order)
|
||||
for line in order.order_line:
|
||||
if line.is_delivery:
|
||||
ret['transaction']['shipping'] = line.price_unit
|
||||
delivery_line = order.order_line.filtered('is_delivery')
|
||||
if delivery_line:
|
||||
ret['shipping'] = delivery_line.price_unit
|
||||
return ret
|
||||
|
||||
def _get_shop_payment_values(self, order, **kwargs):
|
||||
|
||||
@@ -80,6 +80,28 @@ publicWidget.registry.WebsiteSale.include({
|
||||
* @param {Boolean} goToShop Triggers a page refresh to the url "shop/cart"
|
||||
*/
|
||||
_onModalSubmit: function (goToShop) {
|
||||
const $product = $('#product_detail');
|
||||
let currency;
|
||||
if ($product.length) {
|
||||
currency = $product.data('product-tracking-info')['currency'];
|
||||
} else {
|
||||
// Add to cart from /shop page
|
||||
currency = this.$('[itemprop="priceCurrency"]').first().text();
|
||||
}
|
||||
const productsTrackingInfo = [];
|
||||
this.$('.js_product.in_cart').each((i, el) => {
|
||||
productsTrackingInfo.push({
|
||||
'item_id': el.getElementsByClassName('product_id')[0].value,
|
||||
'item_name': el.getElementsByClassName('product_display_name')[0].textContent,
|
||||
'quantity': el.getElementsByClassName('js_quantity')[0].value,
|
||||
'currency': currency,
|
||||
'price': el.getElementsByClassName('oe_price')[0].getElementsByClassName('oe_currency_value')[0].textContent,
|
||||
});
|
||||
});
|
||||
if (productsTrackingInfo) {
|
||||
this.$el.trigger('add_to_cart_event', productsTrackingInfo);
|
||||
}
|
||||
|
||||
this.optionalProductsModal.getAndCreateSelectedProducts()
|
||||
.then((products) => {
|
||||
const productAndOptions = JSON.stringify(products);
|
||||
|
||||
@@ -173,12 +173,18 @@ publicWidget.registry.ProductWishlist = publicWidget.Widget.extend(VariantMixin,
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_addToCart: function (productID, qty_id) {
|
||||
_addToCart: function (productID, qty) {
|
||||
const $tr = this.$(`tr[data-product-id="${productID}"]`);
|
||||
const productTrackingInfo = $tr.data('product-tracking-info');
|
||||
if (productTrackingInfo) {
|
||||
productTrackingInfo.quantity = qty;
|
||||
$tr.trigger('add_to_cart_event', [productTrackingInfo]);
|
||||
}
|
||||
return this._rpc({
|
||||
route: "/shop/cart/update_json",
|
||||
params: {
|
||||
product_id: parseInt(productID, 10),
|
||||
add_qty: parseInt(qty_id, 10),
|
||||
add_qty: parseInt(qty, 10),
|
||||
display: false,
|
||||
},
|
||||
}).then(function (resp) {
|
||||
|
||||
@@ -163,7 +163,9 @@
|
||||
<table class="table table-bordered table-striped table-hover text-center mt16 table-comparator " style="table-layout:auto" id="o_comparelist_table">
|
||||
<body>
|
||||
<t t-foreach="wishes" t-as="wish">
|
||||
<tr t-att-data-wish-id='wish.id' t-att-data-product-id='wish.product_id.id'>
|
||||
<t t-set="combination_info" t-value="wish.product_id._get_combination_info_variant()"/>
|
||||
<tr t-att-data-wish-id='wish.id' t-att-data-product-id='wish.product_id.id'
|
||||
t-att-data-product-tracking-info="json.dumps(request.env['product.template'].get_google_analytics_data(combination_info))">
|
||||
<td class='td-img align-middle'>
|
||||
<a t-att-href="wish.product_id.website_url">
|
||||
<img t-attf-src="/web/image/product.product/#{wish.product_id.id}/image_128" class="img img-fluid" style="margin:auto;" alt="Product image"/>
|
||||
@@ -175,7 +177,6 @@
|
||||
<button type="button" class="btn btn-link o_wish_rm no-decoration"><small><i class='fa fa-trash-o'></i> Remove</small></button>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<t t-set="combination_info" t-value="wish.product_id._get_combination_info_variant()"/>
|
||||
<t t-esc="combination_info['price']" t-options="{'widget': 'monetary', 'display_currency': website.pricelist_id.currency_id}"/>
|
||||
<small t-if="wish.product_id.base_unit_price" class="cart_product_base_unit_price d-block text-muted" groups="website_sale.group_show_uom_price">
|
||||
<t t-call='website_sale.base_unit_price'><t t-set='product' t-value='wish.product_id' /></t>
|
||||
|
||||
Reference in New Issue
Block a user