From 71999f9ffd9415c68b701c87b6e577d43bf7be8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CJuan?= Date: Tue, 28 Apr 2026 16:21:06 +0000 Subject: [PATCH] [IMP] google_tag_manager_advanced_tracking: shipping and discount separated from items / fix facebook pixel tracking #115773 X-original-commit: be6ba0946c83160f6ce5539e78af12ee294c399b --- .../static/src/js/website_sale_tracking.js | 116 ++++++++++++++++++ .../src/interactions/website_sale_tracking.js | 105 +++++++++++++--- .../models/sale_order.py | 31 +++-- .../models/sale_order_line.py | 9 +- .../views/templates.xml | 4 +- 5 files changed, 233 insertions(+), 32 deletions(-) create mode 100644 facebook_pixel_tracking/static/src/js/website_sale_tracking.js diff --git a/facebook_pixel_tracking/static/src/js/website_sale_tracking.js b/facebook_pixel_tracking/static/src/js/website_sale_tracking.js new file mode 100644 index 00000000..ec55520a --- /dev/null +++ b/facebook_pixel_tracking/static/src/js/website_sale_tracking.js @@ -0,0 +1,116 @@ +<<<<<<< HEAD +||||||| MERGE BASE +======= +/** @odoo-module **/ + +import publicWidget from "@web/legacy/js/public/public_widget"; + + +const PaymentForm = publicWidget.registry.PaymentForm; + +publicWidget.registry.FacebookPixelTracking = publicWidget.Widget.extend({ + selector: '.oe_website_sale', + events: { + "click #add_to_cart": "_onClickAddToCartProduct", + "click a.add_to_cart_products_item": "_onClickAddToCartProductsItem", + "click a.on_checkout_start_js": "_onCheckoutStartJs", + }, + + _pushInfo: function (event, dict) { + if (typeof fbq !== 'undefined') { + fbq('track', event, dict); + } + console.log(event,dict); + }, + + _onClickAddToCartProduct: function (ev) { + var dataTarget = ev.target.closest('a#add_to_cart'); + var product_id = dataTarget.dataset.product_id; + var product_sku = dataTarget.dataset.product_sku; + var product_name = dataTarget.dataset.product_name; + var product_price = parseFloat(dataTarget.dataset.product_price) || 0; + var currency = dataTarget.dataset.currency; + const dict = { + content_name: product_name, + content_ids: [String(product_sku || product_id)], + content_type: 'product', + value: product_price, + currency: currency, + }; + this._pushInfo('AddToCart', dict); + }, + + _onClickAddToCartProductsItem: function (ev) { + var dataTarget = ev.target.closest('div.o_wsale_product_btn'); + var product_id = dataTarget.dataset.product_id; + var product_sku = dataTarget.dataset.product_sku; + var product_name = dataTarget.dataset.product_name; + var product_price = parseFloat(dataTarget.dataset.product_price) || 0; + const dict = { + content_name: product_name, + content_ids: [String(product_sku || product_id)], + content_type: 'product', + value: product_price, + }; + this._pushInfo('AddToCart', dict); + }, + + _onCheckoutStartJs: function () { + var dataTarget = $("#cart_products")[0]; + const items = JSON.parse(dataTarget.dataset.cart_info || '[]'); + const contents = items + .filter(item => !item.is_reward_line) + .map(item => ({ + id: String(item.item_id), + quantity: item.quantity, + item_price: item.price, + })); + if (!contents.length) { + return; + } + const dict = { + content_ids: contents.map(item => item.id), + content_type: 'product', + contents: contents, + num_items: contents.length, + value: parseFloat(dataTarget.dataset.value) || 0, + currency: dataTarget.dataset.currency, + }; + this._pushInfo('InitiateCheckout', dict); + }, +}); + +// Heredamos PaymentForm porque el método _submitForm tiene stopPropagation y preventDefault, +// impidiéndonos capturarlo desde el widget FacebookPixelTracking. +PaymentForm.include({ + + _pushInfo: function (event, dict) { + if (typeof fbq !== 'undefined') { + fbq('track', event, dict); + } + }, + + // @override + _submitForm: async function (ev) { + const info_div = $("#o_wsale_accordion_item")[0]; + if (info_div) { + const info = JSON.parse(info_div.dataset.purchase_info || '{}'); + const contents = (info.items || []).map(item => ({ + id: String(item.item_id), + quantity: item.quantity, + item_price: item.price, + })); + const dict = { + value: info.value, + currency: info.currency, + content_ids: contents.map(item => item.id), + contents: contents, + content_type: 'product', + }; + this._pushInfo('Purchase', dict); + } + await this._super(...arguments); + }, +}) + +>>>>>>> FORWARD PORTED diff --git a/google_tag_manager_advanced_tracking/static/src/interactions/website_sale_tracking.js b/google_tag_manager_advanced_tracking/static/src/interactions/website_sale_tracking.js index 36a5ee69..32da83db 100644 --- a/google_tag_manager_advanced_tracking/static/src/interactions/website_sale_tracking.js +++ b/google_tag_manager_advanced_tracking/static/src/interactions/website_sale_tracking.js @@ -44,6 +44,7 @@ export class GoogleTagManagerAdvancedTracking extends Interaction { dataLayer.push(dict); console.log(dict); } +<<<<<<< e9aa8e7dd3e7f495758de7710f7b5500db4fb4ca:google_tag_manager_advanced_tracking/static/src/interactions/website_sale_tracking.js } onClickAddToCartProduct(ev) { @@ -54,6 +55,28 @@ export class GoogleTagManagerAdvancedTracking extends Interaction { const product_price = dataTarget.dataset.product_price; const product_amount = document.querySelector("[name=add_qty]").value; const amount = parseFloat(product_price * product_amount).toFixed(2); +||||||| 3f4ea8024a54c6554045e9b425387a4c0a8d2755:google_tag_manager_advanced_tracking/static/src/js/website_sale_tracking.js + }, + _onClickAddToCartProduct: function (ev) { + var dataTarget = ev.target.closest('a#add_to_cart'); + var product_id = dataTarget.dataset.product_id; + var product_name = dataTarget.dataset.product_name; + var currency = dataTarget.dataset.currency; + var product_price = dataTarget.dataset.product_price; + var product_amount = $("[name=add_qty]").val(); + var amount = parseFloat(product_price * product_amount).toFixed(2); +======= + }, + _onClickAddToCartProduct: function (ev) { + var dataTarget = ev.target.closest('a#add_to_cart'); + var product_id = dataTarget.dataset.product_id; + var product_sku = dataTarget.dataset.product_sku; + var product_name = dataTarget.dataset.product_name; + var currency = dataTarget.dataset.currency; + var product_price = dataTarget.dataset.product_price; + var product_amount = $('[name=add_qty]').val(); + var amount = parseFloat(product_price * product_amount).toFixed(2); +>>>>>>> e7ed3d553e640efbe0aedd33f438214083ef1525:google_tag_manager_advanced_tracking/static/src/js/website_sale_tracking.js const dict = { 'event': 'add_to_cart', 'ecommerce': { @@ -61,7 +84,7 @@ export class GoogleTagManagerAdvancedTracking extends Interaction { 'value': amount, 'items': [{ 'item_name': product_name, - 'item_id': product_id, + 'item_id': product_sku || product_id, 'price': product_price, 'quantity': product_amount, }] @@ -90,29 +113,52 @@ export class GoogleTagManagerAdvancedTracking extends Interaction { } } this._pushInfo(dict); +<<<<<<< e9aa8e7dd3e7f495758de7710f7b5500db4fb4ca:google_tag_manager_advanced_tracking/static/src/interactions/website_sale_tracking.js } onCheckoutStartJs(ev) { const dataTarget = document.querySelector("#cart_products"); if( dataTarget && Object.keys(dataTarget.dataset).length > 0) { +||||||| 3f4ea8024a54c6554045e9b425387a4c0a8d2755:google_tag_manager_advanced_tracking/static/src/js/website_sale_tracking.js + }, + _onCheckoutStartJs: function () { + var dataTarget = $("#cart_products")[0]; + if( Object.keys(dataTarget.dataset).length > 0) { +======= + }, + _onCheckoutStartJs: function () { + var dataTarget = $("#cart_products")[0]; + if(dataTarget && Object.keys(dataTarget.dataset).length > 0) { +>>>>>>> e7ed3d553e640efbe0aedd33f438214083ef1525:google_tag_manager_advanced_tracking/static/src/js/website_sale_tracking.js try { const currency = dataTarget.dataset.currency; const value = dataTarget.dataset.value; const info_string = dataTarget.dataset.cart_info; - // Robust parsing - let jsonString = info_string.replace(/\\/g, '\\\\').replace(/\'/g, '"'); - jsonString = jsonString.replace(/:\s*None([,\}])/g, ': null$1'); - jsonString = jsonString.replace(/:\s*True([,\}])/g, ': true$1'); - jsonString = jsonString.replace(/:\s*False([,\}])/g, ': false$1'); - - const info = JSON.parse(jsonString); + const info = JSON.parse(info_string || '[]'); + const allLines = Array.isArray(info) ? info : (info.items || []); + const rewardTotal = allLines + .filter((line) => line && line.is_reward_line) + .reduce((sum, line) => { + const price = Number(line.price || 0); + const quantity = Number(line.quantity || 1); + return sum + (price * quantity); + }, 0); + const items = allLines + .filter((line) => line && !line.is_reward_line) + .map((line) => ({ + item_name: line.item_name, + item_id: line.item_id, + price: line.price, + quantity: line.quantity, + })); const dict = { 'event':'begin_checkout', 'ecommerce':{ 'currency': currency, - 'value': value, - 'items':info + 'value': Number(value || 0), + 'discount': Math.abs(rewardTotal), + 'items': items, } } this._pushInfo(dict); @@ -124,6 +170,7 @@ export class GoogleTagManagerAdvancedTracking extends Interaction { } onCartView(element) { try { +<<<<<<< e9aa8e7dd3e7f495758de7710f7b5500db4fb4ca:google_tag_manager_advanced_tracking/static/src/interactions/website_sale_tracking.js if(element){ const info_string = element.dataset.cart_info; // Attempt to fix common Python dict string issues for JSON parsing @@ -136,6 +183,26 @@ export class GoogleTagManagerAdvancedTracking extends Interaction { jsonString = jsonString.replace(/:\s*False([,\}])/g, ': false$1'); const info = JSON.parse(jsonString); +||||||| 3f4ea8024a54c6554045e9b425387a4c0a8d2755:google_tag_manager_advanced_tracking/static/src/js/website_sale_tracking.js + const cart = element[0] + if(cart){ + const info_string = element[0].dataset.cart_info; + // Attempt to fix common Python dict string issues for JSON parsing + // 1. Escape existing backslashes + // 2. Replace single quotes with doublequotes + // 3. Replace Python boolean/none literals (only when they appear as values) + let jsonString = info_string.replace(/\\/g, '\\\\').replace(/\'/g, '"'); + jsonString = jsonString.replace(/:\s*None([,\}])/g, ': null$1'); + jsonString = jsonString.replace(/:\s*True([,\}])/g, ': true$1'); + jsonString = jsonString.replace(/:\s*False([,\}])/g, ': false$1'); + + const info = JSON.parse(jsonString); +======= + const cart = element[0] + if(cart){ + const info_string = element[0].dataset.cart_info; + const info = JSON.parse(info_string || '{}'); +>>>>>>> e7ed3d553e640efbe0aedd33f438214083ef1525:google_tag_manager_advanced_tracking/static/src/js/website_sale_tracking.js const dict = { 'event': 'view_cart', 'ecommerce': info @@ -151,13 +218,17 @@ export class GoogleTagManagerAdvancedTracking extends Interaction { try { const info_string = confirmation.dataset.purchase_info; - // Robust parsing - let jsonString = info_string.replace(/\\/g, '\\\\').replace(/\'/g, '"'); - jsonString = jsonString.replace(/:\s*None([,\}])/g, ': null$1'); - jsonString = jsonString.replace(/:\s*True([,\}])/g, ': true$1'); - jsonString = jsonString.replace(/:\s*False([,\}])/g, ': false$1'); - - const info = JSON.parse(jsonString); + // jQuery .data() auto-parses valid JSON into an object, handle both cases + let info; + if (typeof info_string === 'object') { + info = info_string; + } else { + let jsonString = info_string.replace(/\\/g, '\\\\').replace(/\'/g, '"'); + jsonString = jsonString.replace(/:\s*None([,\}])/g, ': null$1'); + jsonString = jsonString.replace(/:\s*True([,\}])/g, ': true$1'); + jsonString = jsonString.replace(/:\s*False([,\}])/g, ': false$1'); + info = JSON.parse(jsonString); + } const dict = { 'event': 'purchase', 'ecommerce': info diff --git a/website_sale_advanced_tracking/models/sale_order.py b/website_sale_advanced_tracking/models/sale_order.py index 7e824583..846028f8 100644 --- a/website_sale_advanced_tracking/models/sale_order.py +++ b/website_sale_advanced_tracking/models/sale_order.py @@ -1,3 +1,5 @@ +import json + from odoo import models @@ -6,21 +8,30 @@ class SaleOrder(models.Model): def prepare_purchase_information(self): products = [] + discount = 0.0 + shipping = 0.0 for line in self.order_line: - products.append( - { - "item_id": line.product_id.default_code or line.product_id.id, - "item_name": line.product_id.name, - "item_category": line.product_id.categ_id.name, - "quantity": line.product_uom_qty, - "price": line.price_reduce_taxinc, - } - ) + if getattr(line, "is_reward_line", False): + discount += abs(line.price_reduce_taxinc * line.product_uom_qty) + elif getattr(line, "is_delivery", False): + shipping += line.price_reduce_taxinc * line.product_uom_qty + else: + products.append( + { + "item_id": line.product_id.default_code or line.product_id.id, + "item_name": line.product_id.name, + "item_category": line.product_id.categ_id.name, + "quantity": line.product_uom_qty, + "price": line.price_reduce_taxinc, + } + ) res = { "transaction_id": self.id, "value": self.amount_total, "tax": self.amount_tax, + "shipping": shipping, + "discount": discount, "currency": self.currency_id.name, "items": products, } - return res + return json.dumps(res) diff --git a/website_sale_advanced_tracking/models/sale_order_line.py b/website_sale_advanced_tracking/models/sale_order_line.py index 6fbcc5a4..ddb4717d 100644 --- a/website_sale_advanced_tracking/models/sale_order_line.py +++ b/website_sale_advanced_tracking/models/sale_order_line.py @@ -1,3 +1,5 @@ +import json + from odoo import models @@ -9,10 +11,11 @@ def prepare_checkout_information(self): for line in self: res.append( { - "item_name": line.name, + "item_name": line.product_id.name, "item_id": line.product_id.default_code or line.product_id.id, - "price": (line.price_reduce_taxinc), + "price": line.price_reduce_taxinc, "quantity": line.product_uom_qty, + "is_reward_line": getattr(line, "is_reward_line", False), } ) - return res + return json.dumps(res) diff --git a/website_sale_advanced_tracking/views/templates.xml b/website_sale_advanced_tracking/views/templates.xml index c732691d..20871435 100644 --- a/website_sale_advanced_tracking/views/templates.xml +++ b/website_sale_advanced_tracking/views/templates.xml @@ -2,7 +2,7 @@