[IMP] product,(website_)sale: Product configurator bug fix
Targets commit d3530eb07e
Purpose
=======
[Follow up JCR testing]
Various bug fixes including:
- optional products dont show when they have variants configured on them
- when no variant is created, you cant choose the product one first time in the ecommerce
- when in debug, attribute and attribute value menu is double
- fix design of default picture in cart summary / product comparison
- fix missing href tag on product comparison "add to cart" buttons
- products with the "Can be sold" box unchecked should not appear in the product configurator
This commit is contained in:
@@ -91,6 +91,7 @@ var SectionAndNoteListRenderer = ListRenderer.extend({
|
||||
this.unselectRow().then(function () {
|
||||
var context = ev.currentTarget.dataset.context;
|
||||
|
||||
var pricelistId = self._getPricelistId();
|
||||
if (context && pyUtils.py_eval(context).open_product_configurator){
|
||||
self._rpc({
|
||||
model: 'ir.model.data',
|
||||
@@ -102,7 +103,10 @@ var SectionAndNoteListRenderer = ListRenderer.extend({
|
||||
type: 'ir.actions.act_window',
|
||||
res_model: 'sale.product.configurator',
|
||||
views: [[res_id, 'form']],
|
||||
target: 'new'
|
||||
target: 'new',
|
||||
context: {
|
||||
'default_pricelist_id': pricelistId
|
||||
}
|
||||
}, {
|
||||
on_close: function (products) {
|
||||
if (products && products !== 'special'){
|
||||
@@ -124,6 +128,20 @@ var SectionAndNoteListRenderer = ListRenderer.extend({
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Will try to get the pricelist_id value from the parent sale_order form
|
||||
*
|
||||
* @private
|
||||
* @returns {integer} pricelist_id's id
|
||||
*/
|
||||
_getPricelistId: function () {
|
||||
var saleOrderForm = this.getParent() && this.getParent().getParent();
|
||||
var stateData = saleOrderForm && saleOrderForm.state && saleOrderForm.state.data;
|
||||
var pricelist_id = stateData.pricelist_id && stateData.pricelist_id.data;
|
||||
|
||||
return pricelist_id.id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Will map the products to appropriate record objects that are
|
||||
* ready for the default_get
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<header>
|
||||
<button string="Configure Variants" type="action"
|
||||
name="%(product_attribute_value_action)d"
|
||||
attrs="{'invisible': [('attribute_line_ids', '=', False)]}"
|
||||
attrs="{'invisible': [('attribute_line_ids', '<=', 0)]}"
|
||||
groups="product.group_product_variant"/>
|
||||
</header>
|
||||
<sheet>
|
||||
|
||||
@@ -60,7 +60,7 @@ class ProductConfiguratorController(http.Controller):
|
||||
|
||||
has_optional_products = False
|
||||
for optional_product in product.optional_product_ids:
|
||||
if optional_product.get_filtered_variants(product):
|
||||
if optional_product.has_dynamic_attributes() or optional_product.get_filtered_variants(product):
|
||||
has_optional_products = True
|
||||
break
|
||||
|
||||
|
||||
@@ -159,7 +159,6 @@ class SaleOrder(models.Model):
|
||||
partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Delivery address for current sales order.")
|
||||
|
||||
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current sales order.")
|
||||
pricelist_id_id = fields.Integer(string="Pricelist ID", related='pricelist_id.id', readonly=True)
|
||||
currency_id = fields.Many2one("res.currency", related='pricelist_id.currency_id', string="Currency", readonly=True, required=True)
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a sales order.", copy=False, oldname='project_id')
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ var ProductConfiguratorFormController = FormController.extend({
|
||||
route: '/product_configurator/configure',
|
||||
params: {
|
||||
product_id: event.data.changes.product_template_id.id,
|
||||
pricelist_id: $('.js_sale_order_pricelist_id').html()
|
||||
pricelist_id: this.renderer.pricelistId
|
||||
}
|
||||
}).then(function (configurator) {
|
||||
self.renderer.renderConfigurator(configurator);
|
||||
@@ -87,19 +87,12 @@ var ProductConfiguratorFormController = FormController.extend({
|
||||
];
|
||||
|
||||
var productId = parseInt($modal.find(productSelector.join(', ')).first().val(), 10);
|
||||
var productReady = $.Deferred();
|
||||
if (productId){
|
||||
productReady.resolve(productId);
|
||||
} else {
|
||||
productReady = this._rpc({
|
||||
model: 'product.template',
|
||||
method: 'create_product_variant',
|
||||
args: [
|
||||
$modal.find('.product_template_id').val(),
|
||||
JSON.stringify(self.renderer.getSelectedVariantValues($modal))
|
||||
],
|
||||
});
|
||||
}
|
||||
var productReady = this.renderer.selectOrCreateProduct(
|
||||
$modal,
|
||||
productId,
|
||||
$modal.find('.product_template_id').val(),
|
||||
false
|
||||
);
|
||||
|
||||
productReady.done(function (productId){
|
||||
$modal.find(productSelector.join(', ')).val(productId);
|
||||
@@ -126,7 +119,7 @@ var ProductConfiguratorFormController = FormController.extend({
|
||||
|
||||
self.optionalProductsModal = new OptionalProductsModal($('body'), {
|
||||
rootProduct: self.rootProduct,
|
||||
pricelistId: $('.js_sale_order_pricelist_id').html(),
|
||||
pricelistId: self.renderer.pricelistId,
|
||||
okButtonText: _t('Confirm'),
|
||||
cancelButtonText: _t('Back'),
|
||||
title: _t('Configure')
|
||||
|
||||
@@ -269,6 +269,43 @@ var ProductConfiguratorMixin = {
|
||||
return values.concat(unchangedValues);
|
||||
},
|
||||
|
||||
/**
|
||||
* Will return a deferred:
|
||||
* - If the product already exists, immediately resolves it with the product_id
|
||||
* - If the product does not exist yet ("dynamic" variant creation), this method will
|
||||
* create the product first and then resolve the deferred with the created product's id
|
||||
*
|
||||
* @param {$.Element} $container the container to look into
|
||||
* @param {integer} productId the product id
|
||||
* @param {integer} productTemplateId the corresponding product template id
|
||||
* @param {boolean} useAjax wether the rpc call should be done using ajax.jsonRpc or using _rpc
|
||||
* @returns {$.Deferred} the deferred that will be resolved with a {integer} productId
|
||||
*/
|
||||
selectOrCreateProduct: function ($container, productId, productTemplateId, useAjax) {
|
||||
var self = this;
|
||||
var productReady = $.Deferred();
|
||||
if (productId && productId !== '0'){
|
||||
productReady.resolve(productId);
|
||||
} else {
|
||||
var params = {
|
||||
model: 'product.template',
|
||||
method: 'create_product_variant',
|
||||
args: [
|
||||
productTemplateId,
|
||||
JSON.stringify(self.getSelectedVariantValues($container))
|
||||
]
|
||||
};
|
||||
|
||||
if (useAjax) {
|
||||
productReady = ajax.jsonRpc('/web/dataset/call', 'call', params);
|
||||
} else {
|
||||
productReady = this._rpc(params);
|
||||
}
|
||||
}
|
||||
|
||||
return productReady;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -345,20 +345,12 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, ProductConfiguratorMixi
|
||||
$main_product.after($parent);
|
||||
}
|
||||
|
||||
var productId = $parent.find('.product_id').val();
|
||||
var productReady = $.Deferred();
|
||||
if (productId && productId !== '0'){
|
||||
productReady.resolve(productId);
|
||||
} else {
|
||||
productReady = ajax.jsonRpc('/web/dataset/call', 'call', {
|
||||
model: 'product.template',
|
||||
method: 'create_product_variant',
|
||||
args: [
|
||||
productTemplateId,
|
||||
JSON.stringify(self.getSelectedVariantValues($parent))
|
||||
]
|
||||
});
|
||||
}
|
||||
var productReady = this.selectOrCreateProduct(
|
||||
$parent,
|
||||
$parent.find('.product_id').val(),
|
||||
productTemplateId,
|
||||
true
|
||||
);
|
||||
|
||||
productReady.done(function (productId){
|
||||
$parent.find('.product_id').val(productId);
|
||||
@@ -376,9 +368,12 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, ProductConfiguratorMixi
|
||||
if ($addedItem.find(".product_template_id").length > 0) {
|
||||
// we need to map this product with it's optional products to be able to:
|
||||
// - remove them from the cart if the parent product is removed
|
||||
// - place the optional products under their parent in the interface when they're added to the cart
|
||||
optionalProductsMap[productTemplateId] = $addedItem.find(".product_template_id").map(function () {
|
||||
return $(this).val();
|
||||
// - place the optional products under their parent in
|
||||
// the interface when they're added to the cart
|
||||
optionalProductsMap[productTemplateId] = $addedItem
|
||||
.find(".product_template_id")
|
||||
.map(function () {
|
||||
return $(this).val();
|
||||
}).get();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ var ProductConfiguratorFormRenderer = FormRenderer.extend(ProductConfiguratorMix
|
||||
*/
|
||||
init: function (){
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.pricelistId = $('.js_sale_order_pricelist_id').html();
|
||||
this.pricelistId = this.state.context.default_pricelist_id || 0;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
|
||||
@@ -4,20 +4,20 @@ odoo.define('sale.product.configurator.tests', function (require) {
|
||||
var FormView = require('web.FormView');
|
||||
var ProductConfiguratorFormView = require('sale.ProductConfiguratorFormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
var ajax = require('web.ajax');
|
||||
var createView = testUtils.createView;
|
||||
|
||||
var getArch = function (){
|
||||
return '<form>' +
|
||||
'<sheet>' +
|
||||
'<field name="sale_order_line">' +
|
||||
'<field name="pricelist_id" widget="selection" />' +
|
||||
'<field name="sale_order_line" widget="section_and_note_one2many">' +
|
||||
'<tree editable="top"><control>' +
|
||||
'<create string="Add a product"/>' +
|
||||
'<create string="Configure a product" context="{\'open_product_configurator\': \'true\'}"/>' +
|
||||
'<create string="Configure a product" context="{\'open_product_configurator\': True}"/>' +
|
||||
'<create string="Add a section" context="{\'default_display_type\': \'line_section\'}"/>' +
|
||||
'<create string="Add a note" context="{\'default_display_type\': \'line_note\'}"/>' +
|
||||
'</control>' +
|
||||
'<field name="product_id"/><field name="quantity"/>' +
|
||||
'<field name="product_id"/><field name="product_uom_qty"/>' +
|
||||
'</tree>' +
|
||||
'</field>' +
|
||||
'</sheet>' +
|
||||
@@ -39,11 +39,23 @@ odoo.define('sale.product.configurator.tests', function (require) {
|
||||
product: {
|
||||
fields: {
|
||||
id: {type: 'integer'}
|
||||
}
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
display_name: "Customizable Desk (1)"
|
||||
}, {
|
||||
id: 2,
|
||||
display_name: "Customizable Desk (2)"
|
||||
}]
|
||||
},
|
||||
sale_order: {
|
||||
fields: {
|
||||
id: {type: 'integer'},
|
||||
pricelist_id: {
|
||||
string: 'Pricelist',
|
||||
type: 'one2many',
|
||||
relation: 'pricelist'
|
||||
},
|
||||
sale_order_line: {
|
||||
string: 'lines',
|
||||
type: 'one2many',
|
||||
@@ -58,7 +70,7 @@ odoo.define('sale.product.configurator.tests', function (require) {
|
||||
type: 'many2one',
|
||||
relation: 'product'
|
||||
},
|
||||
quantity: {type: 'integer'}
|
||||
product_uom_qty: {type: 'integer'}
|
||||
}
|
||||
},
|
||||
sale_product_configurator: {
|
||||
@@ -73,6 +85,11 @@ odoo.define('sale.product.configurator.tests', function (require) {
|
||||
product_template_id: 42
|
||||
}]
|
||||
},
|
||||
pricelist: {
|
||||
fields: {
|
||||
id: {type: 'integer'}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, function (){
|
||||
@@ -108,10 +125,10 @@ odoo.define('sale.product.configurator.tests', function (require) {
|
||||
arch: getArch()
|
||||
});
|
||||
|
||||
var list = form.renderer.allFieldWidgets[form.handle][0];
|
||||
var list = form.renderer.allFieldWidgets[form.handle][1];
|
||||
|
||||
list.trigger_up('add_record', {
|
||||
context: [{product_id: 1, quantity: 2}, {product_id: 2, quantity: 3}],
|
||||
context: [{default_product_id: 1, default_product_uom_qty: 2}, {default_product_id: 2, default_product_uom_qty: 3}],
|
||||
forceEditable: "bottom" ,
|
||||
allowWarning: true
|
||||
});
|
||||
@@ -144,9 +161,6 @@ odoo.define('sale.product.configurator.tests', function (require) {
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
var div = '<div>';
|
||||
var $div = $(div).addClass('js_sale_order_pricelist_id').html(43);
|
||||
product_configurator_form.$('.o_input').after($div);
|
||||
product_configurator_form.$('.o_input').click();
|
||||
$("ul.ui-autocomplete li a:contains('Customizable Desk')").mouseenter().click();
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
|
||||
<template id="optional_product_items">
|
||||
<t t-foreach="product.optional_product_ids" t-as="product">
|
||||
<t t-if="len(product.get_filtered_variants(reference_product)) > 0">
|
||||
<t t-if="len(product.get_filtered_variants(reference_product)) > 0 or product.has_dynamic_attributes()">
|
||||
<tr class="js_product">
|
||||
<td class="td-img">
|
||||
<input type="hidden" class="product_template_id" t-att-value="product.id"/>
|
||||
|
||||
@@ -94,18 +94,6 @@
|
||||
<menuitem id="menu_product" name="Product Variants" parent="product_menu_catalog" sequence="2" groups="base.group_no_one" active="False"/>
|
||||
<menuitem action="product_template_action" id="menu_product_template_action" parent="product_menu_catalog" sequence="1" active="False"/>
|
||||
<menuitem id="prod_config_main" name="Products" parent="menu_sale_config" sequence="5"/>
|
||||
<menuitem id="menu_attribute_action" action="product.attribute_action" parent="prod_config_main" sequence="1" groups="base.group_no_one" active="False"/>
|
||||
<menuitem id="menu_product_category_config_sale"
|
||||
action="product.product_category_action_form"
|
||||
parent="sale.prod_config_main"
|
||||
sequence="1"
|
||||
groups="base.group_no_one"/>
|
||||
<menuitem action="product.variants_action" id="menu_variants_action" parent="sale.prod_config_main" sequence="2" groups="base.group_no_one" active="False"/>
|
||||
<menuitem id="menu_product_category_config_sale"
|
||||
action="product.product_category_action_form"
|
||||
parent="sale.prod_config_main"
|
||||
sequence="1"
|
||||
groups="base.group_no_one"/>
|
||||
<menuitem id="menu_products" action="product.product_normal_action_sell" parent="product_menu_catalog" groups="product.group_product_variant" sequence="2" active="False"/>
|
||||
<menuitem id="next_id_16" name="Units of Measure" parent="sale.prod_config_main" sequence="6" groups="uom.group_uom" active="False"/>
|
||||
<menuitem action="uom.product_uom_form_action" id="menu_product_uom_form_action" parent="sale.prod_config_main" sequence="7" groups="uom.group_uom" active="False"/>
|
||||
@@ -290,7 +278,6 @@
|
||||
<field name="validity_date" attrs="{'invisible': [('state', 'in', ['sale', 'done'])]}"/>
|
||||
<field name="confirmation_date" attrs="{'invisible': [('state', 'in', ['draft', 'sent', 'cancel'])]}"/>
|
||||
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
|
||||
<field name="pricelist_id_id" invisible="1" class="js_sale_order_pricelist_id" />
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="payment_term_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
|
||||
@@ -10,4 +10,5 @@ class SaleProductConfigurator(models.TransientModel):
|
||||
|
||||
product_template_id = fields.Many2one(
|
||||
'product.template', string="Product",
|
||||
required=True, domain=['|', ('attribute_line_ids', '!=', False), ('optional_product_ids', '!=', False)])
|
||||
required=True, domain=[('sale_ok', '=', True), '|', ('attribute_line_ids', '!=', False), ('optional_product_ids', '!=', False)])
|
||||
pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', readonly=True)
|
||||
|
||||
@@ -21,12 +21,6 @@
|
||||
<record id="sale.prod_config_main" model="ir.ui.menu">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
<record id="sale.menu_attribute_action" model="ir.ui.menu">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
<record id="sale.menu_variants_action" model="ir.ui.menu">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
<record id="sale.menu_products" model="ir.ui.menu">
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
@@ -394,11 +394,15 @@ class WebsiteSale(ProductConfiguratorController):
|
||||
|
||||
@http.route(['/shop/cart/update'], type='http', auth="public", methods=['POST'], website=True, csrf=False)
|
||||
def cart_update(self, product_id, add_qty=1, set_qty=0, **kw):
|
||||
product_custom_attribute_values = None
|
||||
if kw.get('product_custom_attribute_values'):
|
||||
product_custom_attribute_values = json.loads(kw.get('product_custom_attribute_values'))
|
||||
|
||||
request.website.sale_get_order(force_create=1)._cart_update(
|
||||
product_id=int(product_id),
|
||||
add_qty=add_qty,
|
||||
set_qty=set_qty,
|
||||
product_custom_attribute_values=json.loads(kw.get('product_custom_attribute_values', '{}'))
|
||||
product_custom_attribute_values=product_custom_attribute_values
|
||||
)
|
||||
return request.redirect("/shop/cart")
|
||||
|
||||
@@ -1078,7 +1082,9 @@ class WebsiteSale(ProductConfiguratorController):
|
||||
if "optional-product-" in k and int(kw.get(k.replace("product", "add"))):
|
||||
optional_product_ids.append(int(v))
|
||||
|
||||
custom_values = json.loads(kw.get('custom_values'))
|
||||
custom_values = []
|
||||
if kw.get('custom_values'):
|
||||
custom_values = json.loads(kw.get('custom_values'))
|
||||
|
||||
value = {}
|
||||
if add_qty or set_qty:
|
||||
|
||||
@@ -62,21 +62,12 @@ sAnimations.registry.WebsiteSaleOptions = sAnimations.Class.extend(ProductConfig
|
||||
'input[type="radio"][name="product_id"]:checked'
|
||||
];
|
||||
|
||||
var productId = parseInt($form.find(productSelector.join(', ')).first().val(), 10);
|
||||
|
||||
var productReady = $.Deferred();
|
||||
if (productId){
|
||||
productReady.resolve(productId);
|
||||
} else {
|
||||
productReady = self._rpc({
|
||||
model: 'product.template',
|
||||
method: 'create_product_variant',
|
||||
args: [
|
||||
$form.find('.product_template_id').val(),
|
||||
JSON.stringify(self.getSelectedVariantValues($form))
|
||||
],
|
||||
});
|
||||
}
|
||||
var productReady = this.selectOrCreateProduct(
|
||||
$form,
|
||||
parseInt($form.find(productSelector.join(', ')).first().val(), 10),
|
||||
$form.find('.product_template_id').val(),
|
||||
false
|
||||
);
|
||||
|
||||
productReady.done(function (productId){
|
||||
$form.find(productSelector.join(', ')).val(productId);
|
||||
|
||||
@@ -138,3 +138,8 @@ $o-wsale-wizard-label-completed: $success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table.js_cart_lines .td-img img {
|
||||
// allows sizing the placeholder image to the "image_small" size of 64px
|
||||
max-height: 64px;
|
||||
}
|
||||
@@ -121,7 +121,7 @@
|
||||
</h6>
|
||||
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price" t-if="product.product_variant_ids">
|
||||
<b>
|
||||
<t t-if="product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
|
||||
<t t-if="product.product_variant_id and product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
|
||||
<del class="text-danger mr8" style="white-space: nowrap;" t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" />
|
||||
</t>
|
||||
|
||||
@@ -484,7 +484,7 @@
|
||||
<span itemprop="url" style="display:none;" t-esc="'%sshop/product/%s' % (request.httprequest.url_root, slug(product))"/>
|
||||
<form action="/shop/cart/update" method="POST">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()" />
|
||||
<div class="js_product js_main_product" t-if="product.product_variant_ids">
|
||||
<div class="js_product js_main_product">
|
||||
<t t-placeholder="select">
|
||||
<input type="hidden" class="product_id" name="product_id" t-att-value="product.product_variant_id.id if len(product.product_variant_ids) == 1 else '0'" />
|
||||
<input type="hidden" class="product_template_id" name="product_template_id" t-att-value="product.id" />
|
||||
@@ -604,7 +604,7 @@
|
||||
<div itemprop="offers" itemscope="itemscope" itemtype="http://schema.org/Offer" class="product_price mt16">
|
||||
<h4 class="oe_price_h4 css_editable_mode_hidden">
|
||||
<span class="text-danger oe_default_price" style="text-decoration: line-through; white-space: nowrap;"
|
||||
t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" t-att-style="'text-decoration: line-through; white-space: nowrap; ' + '' if product.website_price_difference and website.get_current_pricelist().discount_policy == 'without_discount' else 'display: none;'"
|
||||
t-esc="compute_currency(product.website_public_price)" t-options="{'widget': 'monetary', 'display_currency': website.get_current_pricelist().currency_id, 'from_currency': website.currency_id}" t-att-style="'text-decoration: line-through; white-space: nowrap; ' + '' if product.product_variant_id and product.website_price_difference and website.get_current_pricelist().discount_policy == 'without_discount' else 'display: none;'"
|
||||
/>
|
||||
<b class="oe_price" style="white-space: nowrap;" t-esc="product.website_price" t-options="{'widget': 'monetary', 'display_currency': website.currency_id}"/>
|
||||
<span itemprop="price" style="display:none;" t-esc="product.website_price"/>
|
||||
@@ -951,7 +951,7 @@
|
||||
<div class="text-muted d-none d-md-block" t-field="product.description_sale" />
|
||||
</td>
|
||||
<td class='td-price'>
|
||||
<t t-if="product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
|
||||
<t t-if="product.product_variant_id and product.website_price_difference and website.get_current_pricelist().discount_policy=='without_discount'">
|
||||
<del class="text-danger mr8" style="white-space: nowrap;" t-field="product.lst_price" t-options="{'widget': 'monetary','from_currency': product.currency_id, 'display_currency': website.currency_id}" />
|
||||
</t>
|
||||
<span t-field="product.website_price" style="white-space: nowrap;" t-options="{'widget': 'monetary','display_currency': website.currency_id}" />
|
||||
|
||||
@@ -4,13 +4,17 @@ odoo.define('website_sale_comparison.comparison', function (require) {
|
||||
var core = require('web.core');
|
||||
var utils = require('web.utils');
|
||||
var Widget = require('web.Widget');
|
||||
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
|
||||
var sAnimations = require('website.content.snippets.animation');
|
||||
var website_sale_utils = require('website_sale.utils');
|
||||
|
||||
var qweb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
var ProductComparison = Widget.extend({
|
||||
// ProductConfiguratorMixin events are overridden on purpose here
|
||||
// to avoid registering them more than once since they are already registered
|
||||
// in website_sale.js
|
||||
var ProductComparison = Widget.extend(ProductConfiguratorMixin, {
|
||||
xmlDependencies: ['/website_sale_comparison/static/src/xml/comparison.xml'],
|
||||
|
||||
template: 'product_comparison_template',
|
||||
@@ -82,20 +86,38 @@ var ProductComparison = Widget.extend({
|
||||
* @param {jQuery} $elem
|
||||
*/
|
||||
handleCompareAddition: function ($elem) {
|
||||
var self = this;
|
||||
if (this.comparelist_product_ids.length < this.product_compare_limit) {
|
||||
var prod = $elem.data('product-product-id');
|
||||
var productId = $elem.data('product-product-id');
|
||||
if ($elem.hasClass('o_add_compare_dyn')) {
|
||||
prod = $elem.parent().find('.product_id').val();
|
||||
if (!prod) { // case List View Variants
|
||||
prod = $elem.parent().find('input:checked').first().val();
|
||||
productId = $elem.parent().find('.product_id').val();
|
||||
if (!productId) { // case List View Variants
|
||||
productId = $elem.parent().find('input:checked').first().val();
|
||||
}
|
||||
prod = parseInt(prod, 10);
|
||||
}
|
||||
if (!prod) {
|
||||
return;
|
||||
}
|
||||
this._addNewProducts(prod);
|
||||
website_sale_utils.animateClone($('#comparelist .o_product_panel_header'), $elem.closest('form'), -50, 10);
|
||||
|
||||
var productReady = this.selectOrCreateProduct(
|
||||
$elem.closest('form'),
|
||||
productId,
|
||||
$elem.closest('form').find('.product_template_id').val(),
|
||||
false
|
||||
);
|
||||
|
||||
productReady.done(function (productId) {
|
||||
productId = parseInt(productId, 10);
|
||||
|
||||
if (!productId) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._addNewProducts(productId);
|
||||
website_sale_utils.animateClone(
|
||||
$('#comparelist .o_product_panel_header'),
|
||||
$elem.closest('form'),
|
||||
-50,
|
||||
10
|
||||
);
|
||||
});
|
||||
} else {
|
||||
this.$('.o_comparelist_limit_warning').show();
|
||||
$('#comparelist .o_product_panel_header').popover('show');
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
|
||||
<template id="add_to_compare" inherit_id="website_sale.products_item" active="True" customize_show="True" name="Comparison List" priority="22">
|
||||
<xpath expr="//div[hasclass('product_price')]" position="inside">
|
||||
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-sm o_add_compare" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
|
||||
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-sm o_add_compare" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="product_add_to_compare" name='Add to comparison in product page' inherit_id="website_sale.product" priority="8">
|
||||
<xpath expr="//a[@id='add_to_cart']" position="after">
|
||||
<t t-if="product.env.ref('website_sale_comparison.add_to_compare').active">
|
||||
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-lg mt8 o_add_compare_dyn" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
|
||||
<button type="button" role="button" class="d-none d-md-inline-block btn btn-secondary btn-lg mt8 o_add_compare_dyn" title="Compare" aria-label="Compare" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_comparelist"><span class="fa fa-exchange"></span></button>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -3,17 +3,22 @@ odoo.define('website_sale_wishlist.wishlist', function (require) {
|
||||
|
||||
var sAnimations = require('website.content.snippets.animation');
|
||||
var wSaleUtils = require('website_sale.utils');
|
||||
var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
|
||||
|
||||
sAnimations.registry.ProductWishlist = sAnimations.Class.extend({
|
||||
// ProductConfiguratorMixin events are overridden on purpose here
|
||||
// to avoid registering them more than once since they are already registered
|
||||
// in website_sale.js
|
||||
sAnimations.registry.ProductWishlist = sAnimations.Class.extend(ProductConfiguratorMixin, {
|
||||
selector: '.oe_website_sale',
|
||||
read_events: {
|
||||
'click #my_wish': '_onClickMyWish',
|
||||
'click .o_add_wishlist, .o_add_wishlist_dyn': '_onClickAddWish',
|
||||
'change input.js_variant_change, select.js_variant_change, ul[data-attribute_exclusions]': '_onChangeVariant',
|
||||
'change input.product_id': '_onChangeVariant',
|
||||
'change input.js_product_change': '_onChangeProduct',
|
||||
'click .wishlist-section .o_wish_rm': '_onClickWishRemove',
|
||||
'click .wishlist-section .o_wish_add': '_onClickWishAdd',
|
||||
},
|
||||
events: sAnimations.Class.events,
|
||||
|
||||
start: function () {
|
||||
var self = this;
|
||||
@@ -48,23 +53,39 @@ sAnimations.registry.ProductWishlist = sAnimations.Class.extend({
|
||||
*/
|
||||
_addNewProducts: function ($el) {
|
||||
var self = this;
|
||||
var productID = parseInt($el.data('product-product-id'), 10);
|
||||
if (!productID && $el.hasClass('o_add_wishlist_dyn')) {
|
||||
productID = parseInt($el.parent().find('.product_id').val());
|
||||
}
|
||||
if (productID && !_.contains(self.wishlistProductIDs, productID)) {
|
||||
return this._rpc({
|
||||
route: '/shop/wishlist/add',
|
||||
params: {
|
||||
product_id: productID,
|
||||
},
|
||||
}).then(function () {
|
||||
self.wishlistProductIDs.push(productID);
|
||||
self._updateWishlistView();
|
||||
wSaleUtils.animateClone($('#my_wish'), $el.closest('form'), 25, 40);
|
||||
$el.prop("disabled", true).addClass('disabled');
|
||||
});
|
||||
var productID = $el.data('product-product-id');
|
||||
if ($el.hasClass('o_add_wishlist_dyn')) {
|
||||
productID = $el.parent().find('.product_id').val();
|
||||
if (!productID) { // case List View Variants
|
||||
productID = $el.parent().find('input:checked').first().val();
|
||||
}
|
||||
productID = parseInt(productID, 10);
|
||||
}
|
||||
|
||||
var productReady = this.selectOrCreateProduct(
|
||||
$el.closest('form'),
|
||||
productID,
|
||||
$el.closest('form').find('.product_template_id').val(),
|
||||
false
|
||||
);
|
||||
|
||||
productReady.done(function (productId) {
|
||||
productId = parseInt(productId, 10);
|
||||
|
||||
if (productId && !_.contains(self.wishlistProductIDs, productId)) {
|
||||
return self._rpc({
|
||||
route: '/shop/wishlist/add',
|
||||
params: {
|
||||
product_id: productId,
|
||||
},
|
||||
}).then(function () {
|
||||
self.wishlistProductIDs.push(productId);
|
||||
self._updateWishlistView();
|
||||
wSaleUtils.animateClone($('#my_wish'), $el.closest('form'), 25, 40);
|
||||
$el.prop("disabled", true).addClass('disabled');
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -175,16 +196,15 @@ sAnimations.registry.ProductWishlist = sAnimations.Class.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onChangeVariant: function (ev) {
|
||||
var $ul = $(ev.target).closest('.js_add_cart_variants');
|
||||
var $parent = $ul.closest('.js_product');
|
||||
var $productID = $parent.find('.product_id').first();
|
||||
var $input = $(ev.target);
|
||||
var $parent = $input.closest('.js_product');
|
||||
var $el = $parent.find("[data-action='o_wishlist']");
|
||||
if (!_.contains(this.wishlistProductIDs, parseInt($productID.val(), 10))) {
|
||||
if (!_.contains(this.wishlistProductIDs, parseInt($input.val(), 10))) {
|
||||
$el.prop("disabled", false).removeClass('disabled').removeAttr('disabled');
|
||||
} else {
|
||||
$el.prop("disabled", true).addClass('disabled').attr('disabled', 'disabled');
|
||||
}
|
||||
$el.data('product-product-id', parseInt($productID.val(), 10));
|
||||
$el.data('product-product-id', parseInt($input.val(), 10));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -15,4 +15,9 @@
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table.table-comparator .td-img img {
|
||||
// allows sizing the placeholder image to the "image" size of 100px
|
||||
max-height: 100px;
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
<template id="add_to_wishlist" inherit_id="website_sale.products_item" active="True" customize_show="True" name="Wishlist" priority="20">
|
||||
<xpath expr="//div[hasclass('product_price')]" position="inside">
|
||||
<t t-set="in_wish" t-value="product.product_variant_ids & request.env['product.wishlist'].current().mapped('product_id')"/>
|
||||
<button type="button" role="button" class="btn btn-secondary btn-sm o_add_wishlist" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
|
||||
<button type="button" role="button" class="btn btn-secondary btn-sm o_add_wishlist" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<xpath expr="//a[@id='add_to_cart']" position="after">
|
||||
<t t-if="product.env.ref('website_sale_wishlist.add_to_wishlist').active">
|
||||
<t t-set="in_wish" t-value="product.product_variant_ids & request.env['product.wishlist'].current().mapped('product_id')"/>
|
||||
<button type="button" role="button" class="btn btn-secondary btn-lg mt8 o_add_wishlist_dyn" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
|
||||
<button type="button" role="button" class="btn btn-secondary btn-lg mt8 o_add_wishlist_dyn" t-att-disabled='in_wish or None' title="Add to Wishlist" t-att-data-product-product-id="product.product_variant_ids[0].id if product.product_variant_ids else '0'" data-action="o_wishlist"><span class="fa fa-heart" role="img" aria-label="Add to wishlist"></span></button>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user