@@ -189,14 +175,17 @@
- -
+
-
diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index 3dc714bb33c..54c14e61797 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -423,6 +423,7 @@
'default_description_sale': name
}"
/>
+
+
+
+
+
diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py
index 3f57e1d85b4..140603c1afe 100644
--- a/addons/website_sale/controllers/main.py
+++ b/addons/website_sale/controllers/main.py
@@ -399,6 +399,7 @@ class WebsiteSale(ProductConfiguratorController):
add_qty=add_qty,
set_qty=set_qty,
attributes=self._filter_attributes(**kw),
+ product_custom_variant_values=json.loads(kw.get('product_custom_variant_values'))
)
return request.redirect("/shop/cart")
@@ -1083,6 +1084,8 @@ class WebsiteSale(ProductConfiguratorController):
attributes = self._filter_attributes(**kw)
+ product_custom_variant_values = json.loads(kw.get('product_custom_variant_values'))
+
value = {}
if add_qty or set_qty:
value = order._cart_update(
@@ -1090,7 +1093,8 @@ class WebsiteSale(ProductConfiguratorController):
add_qty=add_qty,
set_qty=set_qty,
attributes=attributes,
- optional_product_ids=optional_product_ids
+ optional_product_ids=optional_product_ids,
+ product_custom_variant_values=self._get_custom_variant_value(int(product_id), product_custom_variant_values)
)
# options have all time the same quantity
@@ -1099,11 +1103,20 @@ class WebsiteSale(ProductConfiguratorController):
product_id=option_id,
set_qty=value.get('quantity'),
attributes=attributes,
- linked_line_id=value.get('line_id')
+ linked_line_id=value.get('line_id'),
+ product_custom_variant_values=self._get_custom_variant_value(option_id, product_custom_variant_values)
)
return str(order.cart_quantity)
+ def _get_custom_variant_value(self, product_id, product_custom_variant_values):
+ if product_custom_variant_values:
+ for product_custom_variant_value in product_custom_variant_values:
+ if product_custom_variant_value['product_id'] == product_id:
+ return product_custom_variant_value['product_custom_variant_values']
+
+ return None
+
@http.route(['/product_configurator/show_optional_products_website'], type='json', auth="public", methods=['POST'], website=True)
def show_optional_products_website(self, product_id, **kw):
return self._show_optional_products(product_id, request.website.get_current_pricelist(), True, **kw)
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index 3d7397a7079..9170f7e3161 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -72,6 +72,8 @@ class SaleOrder(models.Model):
domain = [('order_id', '=', self.id), ('product_id', '=', product_id)]
if line_id:
domain += [('id', '=', line_id)]
+ else:
+ domain += [('product_custom_variant_values', '=', False)]
lines = self.env['sale.order.line'].sudo().search(domain)
@@ -116,7 +118,7 @@ class SaleOrder(models.Model):
}
@api.multi
- def _get_line_description(self, order_id, product_id, attributes=None):
+ def _get_line_description(self, order_id, product_id, attributes=None, custom_values=None):
if not attributes:
attributes = {}
@@ -140,6 +142,9 @@ class SaleOrder(models.Model):
if product.description_sale:
name += '\n%s' % (product.description_sale)
+ if custom_values:
+ name += ''.join(['\n%s: %s' % (custom_value['attribute_value_name'], custom_value['custom_value']) for custom_value in custom_values])
+
return name
@api.multi
@@ -170,7 +175,15 @@ class SaleOrder(models.Model):
# Create line if no line with product_id can be located
if not order_line:
values = self._website_product_id_change(self.id, product_id, qty=1)
- values['name'] = self._get_line_description(self.id, product_id, attributes=attributes)
+
+ custom_values = kwargs.get('product_custom_variant_values')
+ if custom_values:
+ values['product_custom_variant_values'] = [(0, 0, {
+ 'attribute_value_id': custom_value['attribute_value_id'],
+ 'custom_value': custom_value['custom_value']
+ }) for custom_value in custom_values]
+
+ values['name'] = self._get_line_description(self.id, product_id, attributes=attributes, custom_values=custom_values)
order_line = SaleOrderLineSudo.create(values)
try:
diff --git a/addons/website_sale/security/ir.model.access.csv b/addons/website_sale/security/ir.model.access.csv
index 8c0f6569c8e..00480b5e449 100644
--- a/addons/website_sale/security/ir.model.access.csv
+++ b/addons/website_sale/security/ir.model.access.csv
@@ -11,6 +11,7 @@ access_product_supplierinfo,product.supplierinfo.public,product.model_product_su
access_product_attribute_public,product.attribute public,product.model_product_attribute,,1,0,0,0
access_product_attribute_value_public,product.attribute value public,product.model_product_attribute_value,,1,0,0,0
access_product_product_attribute,product.product.attribute value public,product.model_product_product_attribute_value,,1,0,0,0
+access_product_product_attribute_custom_value,product.attribute.custom value,sale.model_product_attribute_custom_value,,1,0,0,0
access_product_attribute_filter_line,product.attribute filter line public,product.model_product_attribute_filter_line,,1,0,0,0
access_product_attribute_line_public,product.attribute line public,product.model_product_attribute_line,,1,0,0,0
access_fiscal_position_public,fiscal position public,account.model_account_fiscal_position,base.group_portal,1,0,0,0
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 64a353de923..856efe6dc2a 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -124,7 +124,7 @@ odoo.define('website_sale.website_sale', function (require) {
'use strict';
var utils = require('web.utils');
-var ProductConfiguratorUtils = require('sale.product_configurator_utils');
+var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
var core = require('web.core');
var config = require('web.config');
var sAnimations = require('website.content.snippets.animation');
@@ -132,7 +132,7 @@ require("website.content.zoomodoo");
var _t = core._t;
-sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
+sAnimations.registry.WebsiteSale = sAnimations.Class.extend(ProductConfiguratorMixin, {
selector: '.oe_website_sale',
read_events: {
'change form .js_product:first input[name="add_qty"]': '_onChangeAddQuantity',
@@ -140,7 +140,7 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
'touchend .js_publish': '_onMouseupPublish',
'change .oe_cart input.js_quantity[data-product-id]': '_onChangeCartQuantity',
'click .oe_cart a.js_add_suggested_products': '_onClickSuggestedProduct',
- 'click a.js_add_cart_json, button.js_add_cart_json': '_onClickAddCartJSON',
+ 'click a.js_add_cart_json': '_onClickAddCartJSON',
'click .a-submit': '_onClickSubmit',
'change form.js_attributes input, form.js_attributes select': '_onChangeAttribute',
'mouseup form.js_add_cart_json label': '_onMouseupAddCartLabel',
@@ -162,6 +162,8 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
this._changeCartQuantity = _.debounce(this._changeCartQuantity.bind(this), 500);
this._changeCountry = _.debounce(this._changeCountry.bind(this), 500);
+
+ this.isWebsite = true;
},
/**
* @override
@@ -176,9 +178,7 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
$('input.js_product_change', product).first().trigger('change');
});
- _.each(this.$('.js_add_cart_variants'), function (add_variant) {
- $('input.js_variant_change, select.js_variant_change', add_variant).first().trigger('change');
- });
+ this.triggerVariantChange(this.$el);
this.$('select[name="country_id"]').change();
@@ -334,12 +334,19 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
// Handlers
//--------------------------------------------------------------------------
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickAddCartJSON: function (ev){
+ this.onClickAddCartJSON(ev);
+ },
/**
* @private
* @param {Event} ev
*/
- _onChangeAddQuantity: function (ev, isWebsite, pricelist, no_stock_check) {
- ProductConfiguratorUtils.onChangeAddQuantity(ev, true, pricelist, no_stock_check);
+ _onChangeAddQuantity: function (ev, no_stock_check) {
+ this.onChangeAddQuantity(ev, no_stock_check);
},
/**
* @private
@@ -375,13 +382,6 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
_onClickSuggestedProduct: function (ev) {
$(ev.currentTarget).prev('input').val(1).trigger('change');
},
- /**
- * @private
- * @param {Event} ev
- */
- _onClickAddCartJSON: function (ev) {
- ProductConfiguratorUtils.onClickAddCartJSON(ev);
- },
/**
* @private
* @param {Event} ev
@@ -447,7 +447,7 @@ sAnimations.registry.WebsiteSale = sAnimations.Class.extend({
* @param {Event} ev
*/
_onChangeVariant: function (ev) {
- ProductConfiguratorUtils.onChangeVariant(ev, false);
+ this.onChangeVariant(ev, false);
},
/**
* @private
diff --git a/addons/website_sale/static/src/js/website_sale_options.js b/addons/website_sale/static/src/js/website_sale_options.js
index cb9e93c204b..4ce19531af5 100644
--- a/addons/website_sale/static/src/js/website_sale_options.js
+++ b/addons/website_sale/static/src/js/website_sale_options.js
@@ -1,88 +1,146 @@
odoo.define('website_sale_options.website_sale', function (require) {
- 'use strict';
-
- var OptionalProductsModal = require('sale.OptionalProductsModal');
- var weContext = require('web_editor.context');
- var sAnimations = require('website.content.snippets.animation');
- require('website_sale.website_sale');
-
- var product_name_map = {};
- var optional_products_map = {};
-
- sAnimations.registry.WebsiteSaleOptions = sAnimations.Class.extend({
- selector: '.oe_website_sale',
- read_events: {
- 'click #add_to_cart, #products_grid .product_price .a-submit': '_onClickAdd',
- },
-
- /**
- * @constructor
- */
- init: function () {
- this._super.apply(this, arguments);
-
- this._handleAdd = _.debounce(this._handleAdd.bind(this), 200, true);
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @private
- */
- _handleAdd: function ($form) {
- var self = this;
- var quantity = parseFloat($form.find('input[name="add_qty"]').val() || 1);
- var product_id = parseInt($form.find('input[type="hidden"][name="product_id"], input[type="radio"][name="product_id"]:checked').first().val(),10);
+'use strict';
- var modal = new OptionalProductsModal(quantity, product_id, $form, null, true);
+var core = require('web.core');
+var _t = core._t;
+var OptionalProductsModal = require('sale.OptionalProductsModal');
+var weContext = require('web_editor.context');
+var sAnimations = require('website.content.snippets.animation');
+var ProductConfiguratorMixin = require('sale.ProductConfiguratorMixin');
+require('website_sale.website_sale');
- modal.on('options_empty', null, function () {
- $form.trigger('submit', [true]);
- });
+sAnimations.registry.WebsiteSaleOptions = sAnimations.Class.extend(ProductConfiguratorMixin, {
+ selector: '.oe_website_sale',
+ read_events: {
+ 'click #add_to_cart, #products_grid .product_price .a-submit': '_onClickAdd',
+ },
- modal.on('back', null, function () {
- self._onModalSubmit($form);
- });
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super.apply(this, arguments);
- modal.on('confirm', null, function () {
- self._onModalSubmit($form, true);
- });
+ this._handleAdd = _.debounce(this._handleAdd.bind(this), 200, true);
+ },
- modal.on('modal_ready', null, function ($modal) {
- $modal.on('hidden.bs.modal', function () {
- $form.removeClass('css_options'); // possibly reactivate opacity (see above)
- $(this).remove();
- });
- });
-
- modal.appendTo($form);
- },
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
- _onModalSubmit: function($form, go_to_shop){
- $form.ajaxSubmit({
- url: '/shop/cart/update_option',
- data: {lang: weContext.get().lang},
- success: function (quantity) {
- if (go_to_shop) {
- window.location.pathname = window.location.pathname.replace(/shop([\/?].*)?$/, "shop/cart");
- }
- var $q = $(".my_cart_quantity");
- $q.parent().parent().removeClass("d-none", !quantity);
- $q.html(quantity).hide().fadeIn(600);
+ /**
+ * Prevents the default submit to allow showing product options
+ * in a modal (if any)
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickAdd: function (ev) {
+ ev.preventDefault();
+ this._handleAdd($(ev.currentTarget).closest('form'));
+ },
+
+ /**
+ * Initializes the optional products modal
+ * and add handlers to the modal events (confirm, back, ...)
+ *
+ * @private
+ * @param {$.Element} $form the related webshop form
+ */
+ _handleAdd: function ($form) {
+ this.$form = $form;
+
+ var productSelector = [
+ 'input[type="hidden"][name="product_id"]',
+ 'input[type="radio"][name="product_id"]:checked'
+ ];
+
+ this.rootProduct = {
+ product_id: parseInt($form.find(productSelector.join(', ')).first().val(), 10),
+ quantity: parseFloat($form.find('input[name="add_qty"]').val() || 1),
+ product_custom_variant_values: this.getCustomVariantValues($form.find('.js_product'))
+ };
+
+ this.isWebsite = true;
+ this.optionalProductsModal = new OptionalProductsModal($form, {
+ rootProduct: this.rootProduct,
+ isWebsite: true,
+ okButtonText: _t('Proceed to Checkout'),
+ cancelButtonText: _t('Continue Shopping'),
+ title: _t('Add to cart')
+ }).open();
+
+ this.optionalProductsModal.on('options_empty', null, this._onModalOptionsEmpty.bind(this));
+ this.optionalProductsModal.on('confirm', null, this._onModalConfirm.bind(this));
+ this.optionalProductsModal.on('back', null, this._onModalBack.bind(this));
+ },
+
+ /**
+ * No optional products found for this product
+ * Add custom variant values in the form data and trigger submit
+ *
+ * @private
+ */
+ _onModalOptionsEmpty: function () {
+ var $productCustomVariantValues = $('', {
+ name: 'product_custom_variant_values',
+ type: "hidden",
+ value: JSON.stringify(this.rootProduct.product_custom_variant_values)
+ });
+ this.$form.append($productCustomVariantValues);
+ this.$form.trigger('submit', [true]);
+ },
+
+ /**
+ * Submit form and stay on the same page
+ *
+ * @private
+ */
+ _onModalBack: function () {
+ this._onModalSubmit(false);
+ },
+
+ /**
+ * Submit form and go to shop
+ *
+ * @private
+ */
+ _onModalConfirm: function () {
+ this._onModalSubmit(true);
+ },
+
+ /**
+ * Submits the form with additional parameters
+ * - lang
+ * - product_custom_variant_values: The products custom variant values
+ *
+ * @private
+ * @param {Boolean} goToShop Triggers a page refresh to the url "shop/cart"
+ */
+ _onModalSubmit: function (goToShop){
+ var productCustomVariantValues = JSON.stringify(
+ this.optionalProductsModal.getSelectedProducts()
+ );
+
+ this.$form.ajaxSubmit({
+ url: '/shop/cart/update_option',
+ data: {
+ lang: weContext.get().lang,
+ product_custom_variant_values: productCustomVariantValues
+ },
+ success: function (quantity) {
+ if (goToShop) {
+ var path = window.location.pathname.replace(/shop([\/?].*)?$/, "shop/cart");
+ window.location.pathname = path;
}
- });
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- _onClickAdd: function (ev) {
- ev.preventDefault();
- this._handleAdd($(ev.currentTarget).closest('form'));
- },
- });
- });
-
\ No newline at end of file
+ var $quantity = $(".my_cart_quantity");
+ $quantity.parent().parent().removeClass("d-none", !quantity);
+ $quantity.html(quantity).hide().fadeIn(600);
+ }
+ });
+ },
+});
+
+return sAnimations.registry.WebsiteSaleOptions;
+
+});
diff --git a/addons/website_sale/static/src/js/website_sale_tour_buy.js b/addons/website_sale/static/src/js/website_sale_tour_buy.js
index bf674dc7a47..74a307a480b 100644
--- a/addons/website_sale/static/src/js/website_sale_tour_buy.js
+++ b/addons/website_sale/static/src/js/website_sale_tour_buy.js
@@ -40,7 +40,7 @@ tour.register('shop_buy_product', {
},
{
content: "click in modal on 'Proceed to checkout' button",
- trigger: 'a:contains("Proceed to Checkout")',
+ trigger: 'button:contains("Proceed to Checkout")',
},
{
content: "add suggested",
diff --git a/addons/website_sale/static/src/js/website_sale_tour_shop_custom_attribute_value.js b/addons/website_sale/static/src/js/website_sale_tour_shop_custom_attribute_value.js
new file mode 100644
index 00000000000..372b64968a5
--- /dev/null
+++ b/addons/website_sale/static/src/js/website_sale_tour_shop_custom_attribute_value.js
@@ -0,0 +1,63 @@
+odoo.define("website_sale.tour_shop", function (require) {
+ "use strict";
+
+ var tour = require("web_tour.tour");
+ var base = require("web_editor.base");
+
+ tour.register("shop_custom_attribute_value", {
+ url: "/shop",
+ test: true,
+ wait_for: base.ready()
+ }, [{
+ trigger: 'img[src*="/product.template/9"]',
+ run: 'click'
+ }, {
+ trigger: 'li.js_attribute_value span:contains(Custom)',
+ extra_trigger: 'li.js_attribute_value',
+ run: 'click',
+ }, {
+ trigger: 'input.variant_custom_value',
+ run: 'text Wood',
+ }, {
+ trigger: 'a:contains(Add to Cart)',
+ run: 'click',
+ }, {
+ trigger: 'div:contains(Custom: Wood)',
+ extra_trigger: '.js_product.in_cart.main_product',
+ run: function (){} // checks that Yep, it's wood!
+ }, {
+ trigger: 'button.js_add_cart_json:has(i.fa-plus)',
+ run: 'click',
+ }, {
+ trigger: 'div.oe_striked_price span:contains(750)',
+ run: function (){}, // check
+ }, {
+ trigger: 'span.oe_price span:contains(600)',
+ run: function (){}, // check
+ }, {
+ trigger: 'li.js_attribute_value span:contains(Aluminium)',
+ extra_trigger: '.oe_optional_products_modal',
+ run: 'click'
+ }, {
+ trigger: '.oe_price span:contains(22.9)',
+ run: function (){}, // check
+ }, {
+ trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Conference Chair)) .js_add',
+ extra_trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Conference Chair))',
+ run: 'click'
+ }, {
+ trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Chair floor protection)) .js_add',
+ extra_trigger: '.oe_optional_products_modal .js_product:has(strong:contains(Chair floor protection))',
+ run: 'click'
+ }, {
+ trigger: 'span:contains(1,269.80)',
+ run: function (){}, // check
+ }, {
+ trigger: 'button:has(span:contains(Proceed to Checkout))',
+ run: 'click',
+ }, {
+ trigger: 'span:contains(Custom: Wood)',
+ extra_trigger: '#cart_products',
+ run: function (){}, // check
+ }]);
+});
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 68052b253d6..9000e28d845 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -19,7 +19,7 @@
-
+
@@ -28,6 +28,7 @@
+
|