From 52ef83e0067e916956676b799cc12c7fd20876b9 Mon Sep 17 00:00:00 2001 From: Samuel Degueldre Date: Mon, 29 Aug 2022 11:22:07 +0000 Subject: [PATCH] [REF] sale_product_configurator, *: remove product configurator wizard *: sale, sale_product_matrix, test_sale_product_configurators, website_sale_product_configurator Previously, the product configurator used a transient model and a corresponding wizard to configure products. In subsequent refactorings, the product configurator was merged with the optional product modal, and because this feature also needs to work in the eCommerce, all the features are available in the modal which doesn't directly depend on the form view. Since the modal is capable of doing everything on its own, the form view and its corresponding transient model are no longer necessary, the m2o widget can just open the modal directly instead. The only thing that was missing from the modal was the ability to selectively display the optional products depending on the configurator mode (we don't want to show optional products when we're configuring an existing product). This commit refactors the product_configurator_widget so that it can open the product_configurator_modal directly without the need for an intermediate form view, and removes said form view and the corresponding transient model. Tours and tests have been adapted to have their selectors always match the modal. closes odoo/odoo#99219 Related: odoo/upgrade#3844 Related: odoo/enterprise#30860 Signed-off-by: Victor Feyens (vfe) --- addons/sale/static/src/js/variant_mixin.js | 6 +- addons/sale_product_configurator/__init__.py | 1 - .../sale_product_configurator/__manifest__.py | 7 - .../controllers/main.py | 3 +- .../security/ir.model.access.csv | 2 - .../src/js/product_configurator_controller.js | 330 ------------------ .../src/js/product_configurator_modal.js | 27 +- .../src/js/product_configurator_renderer.js | 136 -------- .../src/js/product_configurator_view.js | 20 -- .../src/js/product_configurator_widget.js | 304 ++++++++++------ .../static/tests/product_configurator.test.js | 62 +--- .../views/templates.xml | 8 +- .../wizard/__init__.py | 4 - .../wizard/sale_product_configurator.py | 21 -- .../sale_product_configurator_views.xml | 40 --- .../src/js/product_matrix_configurator.js | 54 +-- .../tours/product_configurator_edition_ui.js | 22 +- ...configurator_single_custom_attribute_ui.js | 10 +- .../src/js/product_configurator_modal.js | 2 +- .../static/src/js/website_sale_options.js | 2 +- 20 files changed, 264 insertions(+), 797 deletions(-) delete mode 100644 addons/sale_product_configurator/security/ir.model.access.csv delete mode 100644 addons/sale_product_configurator/static/src/js/product_configurator_controller.js delete mode 100644 addons/sale_product_configurator/static/src/js/product_configurator_renderer.js delete mode 100644 addons/sale_product_configurator/static/src/js/product_configurator_view.js delete mode 100644 addons/sale_product_configurator/wizard/__init__.py delete mode 100644 addons/sale_product_configurator/wizard/sale_product_configurator.py delete mode 100644 addons/sale_product_configurator/wizard/sale_product_configurator_views.xml diff --git a/addons/sale/static/src/js/variant_mixin.js b/addons/sale/static/src/js/variant_mixin.js index a7b20cfea4a..5afcb79a37e 100644 --- a/addons/sale/static/src/js/variant_mixin.js +++ b/addons/sale/static/src/js/variant_mixin.js @@ -206,10 +206,9 @@ var VariantMixin = { * @param {$.Element} $container */ triggerVariantChange: function ($container) { - var self = this; $container.find('ul[data-attribute_exclusions]').trigger('change'); $container.find('input.js_variant_change:checked, select.js_variant_change').each(function () { - self.handleCustomValues($(this)); + VariantMixin.handleCustomValues($(this)); }); }, @@ -312,7 +311,6 @@ var VariantMixin = { * @returns {Promise} the promise that will be resolved with a {integer} productId */ selectOrCreateProduct: function ($container, productId, productTemplateId, useAjax) { - var self = this; productId = parseInt(productId); productTemplateId = parseInt(productTemplateId); var productReady = Promise.resolve(); @@ -322,7 +320,7 @@ var VariantMixin = { var params = { product_template_id: productTemplateId, product_template_attribute_value_ids: - JSON.stringify(self.getSelectedVariantValues($container)), + JSON.stringify(VariantMixin.getSelectedVariantValues($container)), }; var route = '/sale/create_product_variant'; diff --git a/addons/sale_product_configurator/__init__.py b/addons/sale_product_configurator/__init__.py index bbc55806ace..aa4d0fd63a9 100644 --- a/addons/sale_product_configurator/__init__.py +++ b/addons/sale_product_configurator/__init__.py @@ -2,4 +2,3 @@ from . import controllers from . import models -from . import wizard diff --git a/addons/sale_product_configurator/__manifest__.py b/addons/sale_product_configurator/__manifest__.py index 95cb28d45f3..750545113a8 100644 --- a/addons/sale_product_configurator/__manifest__.py +++ b/addons/sale_product_configurator/__manifest__.py @@ -15,13 +15,9 @@ It also enables the "optional products" feature. 'depends': ['sale'], 'data': [ - 'security/ir.model.access.csv', - 'views/product_template_views.xml', 'views/sale_order_views.xml', 'views/templates.xml', - - 'wizard/sale_product_configurator_views.xml', ], 'demo': [ 'data/sale_demo.xml', @@ -29,9 +25,6 @@ It also enables the "optional products" feature. 'assets': { 'web.assets_backend': [ 'sale/static/src/js/variant_mixin.js', - 'sale_product_configurator/static/src/js/product_configurator_renderer.js', - 'sale_product_configurator/static/src/js/product_configurator_controller.js', - 'sale_product_configurator/static/src/js/product_configurator_view.js', 'sale_product_configurator/static/src/js/product_configurator_widget.js', 'sale_product_configurator/static/src/js/product_configurator_modal.js', ], diff --git a/addons/sale_product_configurator/controllers/main.py b/addons/sale_product_configurator/controllers/main.py index edb0c2be7f5..847d4adfbd2 100644 --- a/addons/sale_product_configurator/controllers/main.py +++ b/addons/sale_product_configurator/controllers/main.py @@ -75,7 +75,8 @@ class ProductConfiguratorController(http.Controller): 'variant_values': variant_values, 'pricelist': pricelist, 'handle_stock': handle_stock, - 'already_configured': kw.get("already_configured", False) + 'already_configured': kw.get("already_configured", False), + 'mode': kw.get('mode', 'add') }) def _get_pricelist(self, pricelist_id, pricelist_fallback=False): diff --git a/addons/sale_product_configurator/security/ir.model.access.csv b/addons/sale_product_configurator/security/ir.model.access.csv deleted file mode 100644 index 1c8e002cafd..00000000000 --- a/addons/sale_product_configurator/security/ir.model.access.csv +++ /dev/null @@ -1,2 +0,0 @@ -"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink" -"access_sale_product_configurator","access.sale.product.configurator","model_sale_product_configurator","sales_team.group_sale_salesman",1,1,1,0 diff --git a/addons/sale_product_configurator/static/src/js/product_configurator_controller.js b/addons/sale_product_configurator/static/src/js/product_configurator_controller.js deleted file mode 100644 index 6d7448f0e6b..00000000000 --- a/addons/sale_product_configurator/static/src/js/product_configurator_controller.js +++ /dev/null @@ -1,330 +0,0 @@ -odoo.define('sale_product_configurator.ProductConfiguratorFormController', function (require) { -"use strict"; - -var core = require('web.core'); -var _t = core._t; -var FormController = require('web.FormController'); -var OptionalProductsModal = require('sale_product_configurator.OptionalProductsModal'); - -var ProductConfiguratorFormController = FormController.extend({ - custom_events: _.extend({}, FormController.prototype.custom_events, { - field_changed: '_onFieldChanged', - handle_add: '_handleAdd' - }), - /** - * @override - */ - start: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - self.$el.addClass('o_product_configurator'); - }); - }, - /** - * We need to first load the template of the selected product and then render the content - * to avoid a flicker when the modal is opened. - * - * @override - */ - willStart: function () { - var def = this._super.apply(this, arguments); - if (this.initialState.data.product_template_id) { - return this._configureProduct( - this.initialState.data.product_template_id.data.id - ).then(function () { - return def; - }); - } - - return def; - }, - /** - * Showing this window is useless for configuratorMode 'options' as this form view - * is used as a bridge between SO lines and optional products. - * - * Placed here because it's the only method that is called after the modal is rendered. - * - * @override - */ - renderButtons: function () { - this._super.apply(this, arguments); - - if (this.renderer.state.context.configuratorMode === 'options') { - this.$el.closest('.modal').addClass('d-none'); - } - - const renderSecondOnly = this.renderer.state.context.configuratorMode !== 'edit'; - this.$el.closest('.o_dialog_container').toggleClass('d-none', renderSecondOnly); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - - /** - * We need to override the default click behavior for our "Add" button - * because there is a possibility that this product has optional products. - * If so, we need to display an extra modal to choose the options. - * - * @override - */ - _onButtonClicked: function (event) { - if (event.stopPropagation) { - event.stopPropagation(); - } - var attrs = event.data.attrs; - if (attrs.special === 'cancel') { - this._super.apply(this, arguments); - } else { - if (!this.$el - .parents('.modal') - .find('.o_sale_product_configurator_add') - .hasClass('disabled')) { - this._handleAdd(); - } - } - }, - /** - * This is overridden to allow catching the "select" event on our product template select field. - * - * @override - * @private - */ - _onFieldChanged: function (event) { - this._super.apply(this, arguments); - - var self = this; - var productId = event.data.changes.product_template_id.id; - - // check to prevent traceback when emptying the field - if (!productId) { - return; - } - - this._configureProduct(event.data.changes.product_template_id.id) - .then(function () { - self.renderer.renderConfigurator(self.renderer.configuratorHtml); - }); - }, - - /** - * Renders the "variants" part of the wizard - * - * @param {integer} productTemplateId - */ - _configureProduct: function (productTemplateId) { - var self = this; - var initialProduct = this.initialState.data.product_template_id; - var changed = initialProduct && initialProduct.data.id !== productTemplateId; - var data = this.renderer.state.data; - var quantity = initialProduct.context && initialProduct.context.default_quantity ? initialProduct.context.default_quantity : data.quantity; - return this._rpc({ - route: '/sale_product_configurator/configure', - params: { - product_template_id: productTemplateId, - pricelist_id: this.renderer.pricelistId, - add_qty: quantity, - product_template_attribute_value_ids: changed ? [] : this._getAttributeValueIds( - data.product_template_attribute_value_ids - ), - product_no_variant_attribute_value_ids: changed ? [] : this._getAttributeValueIds( - data.product_no_variant_attribute_value_ids - ), - context: this.getSession().user_context, - } - }).then(function (configurator) { - self.renderer.configuratorHtml = configurator; - }); - }, - /** - * When the user adds a product that has optional products, we need to display - * a window to allow the user to choose these extra options. - * - * This will also create the product if it's in "dynamic" mode - * (see product_attribute.create_variant) - * - * If "self.renderer.state.context.configuratorMode" is 'edit', this will only send - * the main product with its changes. - * - * As opposed to the 'add' mode that will add the main product AND all the configured optional products. - * - * A third mode, 'options', is available for products that don't have a configuration but have - * optional products to select. This will bypass the configuration step and open the - * options modal directly. - * - * @private - */ - _handleAdd: function () { - var self = this; - var $modal = this.$el; - var productSelector = [ - 'input[type="hidden"][name="product_id"]', - 'input[type="radio"][name="product_id"]:checked' - ]; - - var productId = parseInt($modal.find(productSelector.join(', ')).first().val(), 10); - var productTemplateId = $modal.find('.product_template_id').val(); - this.renderer.selectOrCreateProduct( - $modal, - productId, - productTemplateId, - false - ).then(function (productId) { - $modal.find(productSelector.join(', ')).val(productId); - - var variantValues = self - .renderer - .getSelectedVariantValues($modal.find('.js_product')); - - var productCustomVariantValues = self - .renderer - .getCustomVariantValues($modal.find('.js_product')); - - var noVariantAttributeValues = self - .renderer - .getNoVariantAttributeValues($modal.find('.js_product')); - - self.rootProduct = { - product_id: productId, - product_template_id: parseInt(productTemplateId), - quantity: parseFloat($modal.find('input[name="add_qty"]').val() || 1), - variant_values: variantValues, - product_custom_attribute_values: productCustomVariantValues, - no_variant_attribute_values: noVariantAttributeValues - }; - - if (self.renderer.state.context.configuratorMode === 'edit') { - // edit mode only takes care of main product - self._onAddRootProductOnly(); - return; - } - - self.optionalProductsModal = new OptionalProductsModal($('body'), { - rootProduct: self.rootProduct, - pricelistId: self.renderer.pricelistId, - okButtonText: _t('Confirm'), - cancelButtonText: _t('Back'), - title: _t('Configure'), - context: self.initialState.context, - previousModalHeight: self.$el.closest('.modal-content').height() - }).open(); - - self.optionalProductsModal.on('options_empty', null, - // no optional products found for this product, only add the root product - self._onAddRootProductOnly.bind(self)); - - self.optionalProductsModal.on('update_quantity', null, - self._onOptionsUpdateQuantity.bind(self)); - - self.optionalProductsModal.on('confirm', null, - self._onModalConfirm.bind(self)); - - self.optionalProductsModal.on('closed', null, - self._onModalClose.bind(self)); - }); - }, - - /** - * Add root product only and forget optional products. - * Used when product has no optional products and in 'edit' mode. - * - * @private - */ - _onAddRootProductOnly: function () { - this._addProducts([this.rootProduct]); - }, - - /** - * Add all selected products - * - * @private - */ - _onModalConfirm: function () { - this._wasConfirmed = true; - this.optionalProductsModal.getAndCreateSelectedProducts().then((products) => { - this._addProducts(products); - }); - }, - - /** - * When the optional products modal is closed (and not confirmed) on 'options' mode, - * this window should also be closed immediately. - * - * @private - */ - _onModalClose: function () { - if (['options', 'add'].includes(this.renderer.state.context.configuratorMode) - && this._wasConfirmed !== true) { - this.do_action({type: 'ir.actions.act_window_close'}); - } - }, - - /** - * Remove "d-none" to allow other modals to display - * - * @override - */ - destroy: function () { - $('.o_dialog_container').removeClass('d-none'); - this._super.apply(this, arguments); - }, - - /** - * Update product configurator form - * when quantity is updated in the optional products window - * - * @private - * @param {integer} quantity - */ - _onOptionsUpdateQuantity: function (quantity) { - this.$el - .find('input[name="add_qty"]') - .val(quantity) - .trigger('change'); - }, - - /** - * This triggers the close action for the window and - * adds the product as the "infos" parameter. - * It will allow the caller (typically the product_configurator widget) of this window - * to handle the added products. - * - * @private - * @param {Array} products the list of added products - * {integer} products.product_id: the id of the product - * {integer} products.quantity: the added quantity for this product - * {Array} products.product_custom_attribute_values: - * see variant_mixin.getCustomVariantValues - * {Array} products.no_variant_attribute_values: - * see variant_mixin.getNoVariantAttributeValues - */ - _addProducts: function (products) { - this.do_action({type: 'ir.actions.act_window_close', infos: { - mainProduct: products[0], - options: products.slice(1) - }}); - }, - /** - * Extracts the ids from the passed attributeValueIds and returns them - * as a plain array. - * - * @param {Array} attributeValueIds - */ - _getAttributeValueIds: function (attributeValueIds) { - if (!attributeValueIds || attributeValueIds.length === 0) { - return false; - } - - var result = []; - _.each(attributeValueIds.data, function (attributeValue) { - result.push(attributeValue.data.id); - }); - - return result; - } -}); - -return ProductConfiguratorFormController; - -}); diff --git a/addons/sale_product_configurator/static/src/js/product_configurator_modal.js b/addons/sale_product_configurator/static/src/js/product_configurator_modal.js index b6dbb6037be..03141503491 100644 --- a/addons/sale_product_configurator/static/src/js/product_configurator_modal.js +++ b/addons/sale_product_configurator/static/src/js/product_configurator_modal.js @@ -1,13 +1,12 @@ -odoo.define('sale_product_configurator.OptionalProductsModal', function (require) { - "use strict"; +/** @odoo-module */ -var ajax = require('web.ajax'); -var Dialog = require('web.Dialog'); -const OwlDialog = require('web.OwlDialog'); -var ServicesMixin = require('web.ServicesMixin'); -var VariantMixin = require('sale.VariantMixin'); +import ajax from 'web.ajax'; +import Dialog from 'web.Dialog'; +import OwlDialog from 'web.OwlDialog'; +import ServicesMixin from 'web.ServicesMixin'; +import VariantMixin from 'sale.VariantMixin'; -var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, { +export const OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, { events: _.extend({}, Dialog.prototype.events, VariantMixin.events, { 'click a.js_add, a.js_remove': '_onAddOrRemoveOption', 'click button.js_add_cart_json': 'onClickAddCartJSON', @@ -43,7 +42,8 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, { buttons: [{ text: params.okButtonText, click: this._onConfirmButtonClick, - classes: 'btn-primary' + // the o_sale_product_configurator_edit class is used for tours. + classes: 'btn-primary o_sale_product_configurator_edit' }, { text: params.cancelButtonText, click: this._onCancelButtonClick @@ -58,6 +58,7 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, { this.container = parent; this.pricelistId = params.pricelistId; this.previousModalHeight = params.previousModalHeight; + this.mode = params.mode; this.dialogClass = 'oe_advanced_configurator_modal'; this._productImageField = 'image_128'; @@ -75,6 +76,7 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, { var uri = this._getUri("/sale_product_configurator/show_advanced_configurator"); var getModalContent = ajax.jsonRpc(uri, 'call', { + mode: self.mode, product_id: self.rootProduct.product_id, variant_values: self.rootProduct.variant_values, pricelist_id: self.pricelistId || false, @@ -236,12 +238,11 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, { $updatedDescription.append($('

', { text: $productDescription.text() })); - $.each(this.rootProduct.product_custom_attribute_values, function () { if (this.custom_value) { const $customInput = $modalContent .find(".main_product [data-is_custom='True']") - .closest(`[data-value_id='${this.custom_product_template_attribute_value_id}']`); + .closest(`[data-value_id='${this.custom_product_template_attribute_value_id.res_id}']`); $customInput.attr('previous_custom_value', this.custom_value); VariantMixin.handleCustomValues($customInput); } @@ -520,7 +521,3 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, VariantMixin, { return el.dataset.uniqueId; }, }); - -return OptionalProductsModal; - -}); diff --git a/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js b/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js deleted file mode 100644 index 4319e9bcdd4..00000000000 --- a/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js +++ /dev/null @@ -1,136 +0,0 @@ -odoo.define('sale_product_configurator.ProductConfiguratorFormRenderer', function (require) { -"use strict"; - -var FormRenderer = require('web.FormRenderer'); -var VariantMixin = require('sale.VariantMixin'); - -var ProductConfiguratorFormRenderer = FormRenderer.extend(VariantMixin, { - - events: _.extend({}, FormRenderer.prototype.events, VariantMixin.events, { - 'click button.js_add_cart_json': 'onClickAddCartJSON', - }), - - /** - * @override - */ - init: function () { - this._super.apply(this, arguments); - this.pricelistId = this.state.context.default_pricelist_id || 0; - }, - /** - * @override - */ - start: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - self.$el.append($('

', {class: 'configurator_container'})); - self.renderConfigurator(self.configuratorHtml); - self._checkMode(); - }); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Renders the product configurator within the form - * - * Will also: - * - * - add events handling for variant changes - * - trigger variant change to compute the price and other - * variant specific changes - * - * @param {string} configuratorHtml the evaluated template of - * the product configurator - */ - renderConfigurator: function (configuratorHtml) { - var $configuratorContainer = this.$('.configurator_container'); - $configuratorContainer.empty(); - - var $configuratorHtml = $(configuratorHtml); - $configuratorHtml.appendTo($configuratorContainer); - - this.triggerVariantChange($configuratorContainer); - this._applyCustomValues(); - if (this.state.context.configuratorMode !== 'options') { - this.trigger_up('handle_add'); - } - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * If the configuratorMode in the given context is 'edit', we need to - * hide the regular 'Add' button to replace it with an 'EDIT' button. - * - * If the configuratorMode is set to 'options', we will directly open the - * options modal. - * - * @private - */ - _checkMode: function () { - if (this.state.context.configuratorMode === 'edit') { - this.$('.o_sale_product_configurator_add').hide(); - this.$('.o_sale_product_configurator_edit').css('display', 'inline-block'); - } else if (this.state.context.configuratorMode === 'options') { - this.trigger_up('handle_add'); - } - }, - - /** - * Toggles the add button depending on the possibility of the current - * combination. - * - * @override - */ - _toggleDisable: function ($parent, isCombinationPossible) { - VariantMixin._toggleDisable.apply(this, arguments); - $parent.parents('.modal').find('.o_sale_product_configurator_add').toggleClass('disabled', !isCombinationPossible); - }, - - /** - * Will fill the custom values input based on the provided initial configuration. - * - * @private - */ - _applyCustomValues: function () { - var self = this; - var customValueIds = this.state.data.product_custom_attribute_value_ids; - if (customValueIds) { - _.each(customValueIds.data, function (customValue) { - if (customValue.data.custom_value) { - var attributeValueId = customValue.data.custom_product_template_attribute_value_id.data.id; - var $input = self._findRelatedAttributeValueInput(attributeValueId); - $input - .closest('li[data-attribute_id]') - .find('.variant_custom_value') - .val(customValue.data.custom_value); - } - }); - } - }, - - /** - * Find the $.Element input/select related to that product.attribute.value - * - * @param {integer} attributeValueId - * - * @private - */ - _findRelatedAttributeValueInput: function (attributeValueId) { - var selectors = [ - 'ul.js_add_cart_variants input[data-value_id="' + attributeValueId + '"]', - 'ul.js_add_cart_variants option[data-value_id="' + attributeValueId + '"]' - ]; - - return this.$(selectors.join(', ')); - } -}); - -return ProductConfiguratorFormRenderer; - -}); diff --git a/addons/sale_product_configurator/static/src/js/product_configurator_view.js b/addons/sale_product_configurator/static/src/js/product_configurator_view.js deleted file mode 100644 index aa16a0bb81d..00000000000 --- a/addons/sale_product_configurator/static/src/js/product_configurator_view.js +++ /dev/null @@ -1,20 +0,0 @@ -odoo.define('sale_product_configurator.ProductConfiguratorFormView', function (require) { -"use strict"; - -var ProductConfiguratorFormController = require('sale_product_configurator.ProductConfiguratorFormController'); -var ProductConfiguratorFormRenderer = require('sale_product_configurator.ProductConfiguratorFormRenderer'); -var FormView = require('web.FormView'); -var viewRegistry = require('web.view_registry'); - -var ProductConfiguratorFormView = FormView.extend({ - config: _.extend({}, FormView.prototype.config, { - Controller: ProductConfiguratorFormController, - Renderer: ProductConfiguratorFormRenderer, - }), -}); - -viewRegistry.add('product_configurator_form', ProductConfiguratorFormView); - -return ProductConfiguratorFormView; - -}); diff --git a/addons/sale_product_configurator/static/src/js/product_configurator_widget.js b/addons/sale_product_configurator/static/src/js/product_configurator_widget.js index 64da75d8a9f..0586722c4fd 100644 --- a/addons/sale_product_configurator/static/src/js/product_configurator_widget.js +++ b/addons/sale_product_configurator/static/src/js/product_configurator_widget.js @@ -1,5 +1,12 @@ -odoo.define('sale_product_configurator.product_configurator', function (require) { -var ProductConfiguratorWidget = require('sale.product_configurator'); +/** @odoo-module */ +import ProductConfiguratorWidget from "sale.product_configurator"; +import { OptionalProductsModal } from "@sale_product_configurator/js/product_configurator_modal"; +import { + selectOrCreateProduct, + getSelectedVariantValues, + getNoVariantAttributeValues, +} from "sale.VariantMixin"; +import { _t } from "web.core"; /** * Extension of the ProductConfiguratorWidget to support product configuration. @@ -16,7 +23,7 @@ ProductConfiguratorWidget.include({ * Override of sale.product_configurator Hook * * @override - */ + */ _isConfigurableProduct: function () { return this.recordData.is_configurable_product || this._super.apply(this, arguments); }, @@ -43,20 +50,20 @@ ProductConfiguratorWidget.include({ }, /** - * This method is overridden to check if the product_template_id - * needs configuration or not: - * - * - The product_template has only one "product.product" and is not dynamic - * -> Set the product_id on the SO line - * -> If the product has optional products, open the configurator in 'options' mode - * - * - The product_template is configurable - * -> Open the product configurator wizard and initialize it with - * the provided product_template_id and its current attribute values - * - * @override - * @private - */ + * This method is overridden to check if the product_template_id + * needs configuration or not: + * + * - The product_template has only one "product.product" and is not dynamic + * -> Set the product_id on the SO line + * -> If the product has optional products, open the configurator in 'options' mode + * + * - The product_template is configurable + * -> Open the product configurator wizard and initialize it with + * the provided product_template_id and its current attribute values + * + * @override + * @private + */ _onTemplateChange: function (productTemplateId, dataPointId) { var self = this; var ctx = {}; @@ -65,19 +72,19 @@ ProductConfiguratorWidget.include({ } return this._rpc({ - model: 'product.template', - method: 'get_single_product_variant', + model: "product.template", + method: "get_single_product_variant", args: [productTemplateId], context: ctx, }).then(function (result) { if (result.product_id && !result.has_optional_products) { - self.trigger_up('field_changed', { + self.trigger_up("field_changed", { dataPointID: dataPointId, changes: { - product_id: {id: result.product_id}, + product_id: { id: result.product_id }, product_custom_attribute_value_ids: { - operation: 'DELETE_ALL' - } + operation: "DELETE_ALL", + }, }, }); } else { @@ -96,16 +103,16 @@ ProductConfiguratorWidget.include({ var self = this; this._super.apply(this, arguments); var parentList = self.getParent(); - var unselectRow = (parentList.unselectRow || function() {}).bind(parentList); // form view on mobile + var unselectRow = (parentList.unselectRow || function () {}).bind(parentList); // form view on mobile if (self.optionalProducts && self.optionalProducts.length !== 0) { - self.trigger_up('add_record', { + self.trigger_up("add_record", { context: self._productsToRecords(self.optionalProducts), - forceEditable: 'bottom', + forceEditable: "bottom", allowWarning: true, onSuccess: function () { // Leave edit mode of one2many list. unselectRow(); - } + }, }); } else if (!self._isConfigurableLine() && self._isConfigurableProduct()) { // Leave edit mode of current line if line was configured @@ -115,11 +122,12 @@ ProductConfiguratorWidget.include({ }, _openConfigurator: function (result, productTemplateId, dataPointId) { - if (!result.mode || result.mode === 'configurator') { - this._openProductConfigurator({ - configuratorMode: result && result.has_optional_products ? 'options' : 'add', - default_pricelist_id: this._getPricelistId(), - default_product_template_id: productTemplateId + if (!result.mode || result.mode === "configurator") { + this._openProductConfigurator( + { + configuratorMode: result && result.has_optional_products ? "options" : "add", + pricelist_id: this._getPricelistId(), + product_template_id: productTemplateId, }, dataPointId ); @@ -149,40 +157,108 @@ ProductConfiguratorWidget.include({ * * @private */ - _openProductConfigurator: function (data, dataPointId) { + async _openProductConfigurator(data, dataPointId) { this.optionalProducts = undefined; - var self = this; - this.do_action('sale_product_configurator.sale_product_configurator_action', { - additional_context: data, - on_close: function (result) { - if (result && !result.special) { - self._addProducts(result, dataPointId); - } else { - if (self.restoreProductTemplateId) { - // if configurator opened in edit mode. - self.trigger_up('field_changed', { - dataPointID: dataPointId, - preventProductIdCheck: true, - changes: { - product_template_id: self.restoreProductTemplateId.data - } - }); - } else { - // if configurator opened to create line: - // destroy line if configurator closed during configuration process. - self.trigger_up('field_changed', { - dataPointID: dataPointId, - changes: { - product_template_id: false, - product_id: false, - }, - }); - } - } + const optionalProductsModal = await this._openModal(data); + // no optional products found for this product, only add the root product + optionalProductsModal.on("options_empty", null, () => + this._addProducts({ mainProduct: this.rootProduct }, dataPointId) + ); + + let confirmed = false; + optionalProductsModal.on("confirm", null, async () => { + confirmed = true; + const [ + mainProduct, + ...options + ] = await optionalProductsModal.getAndCreateSelectedProducts(); + this._addProducts({ mainProduct, options }, dataPointId); + }); + optionalProductsModal.on("closed", null, () => { + if (confirmed) { + return; + } + if (this.restoreProductTemplateId) { + // if configurator opened in edit mode. + this.trigger_up("field_changed", { + dataPointID: dataPointId, + preventProductIdCheck: true, + changes: { + product_template_id: this.restoreProductTemplateId.data, + }, + }); + } else { + // if configurator opened to create line: + // destroy line if configurator closed during configuration process. + this.trigger_up("field_changed", { + dataPointID: dataPointId, + changes: { + product_template_id: false, + product_id: false, + }, + }); } }); }, + async _openModal(data) { + const { + product_template_id, + product_template_attribute_value_ids, + product_no_variant_attribute_value_ids, + pricelist_id, + product_custom_attribute_value_ids, + quantity, + configuratorMode, + } = data; + // FIXME replace this with an RPC instead of rendering a template and querying it for data + const $modal = $( + await this._rpc({ + route: "/sale_product_configurator/configure", + params: { + product_template_id, + pricelist_id, + add_qty: quantity, + product_template_attribute_value_ids, + product_no_variant_attribute_value_ids, + context: this.getSession().user_context, + }, + }) + ); + const productSelector = `input[type="hidden"][name="product_id"], input[type="radio"][name="product_id"]:checked`; + const productTemplateId = $modal.find(".product_template_id").val(); + const productId = await selectOrCreateProduct.call( + this, + $modal, + parseInt($modal.find(productSelector).first().val(), 10), + productTemplateId, + false + ); + $modal.find(productSelector).val(productId); + const variantValues = getSelectedVariantValues($modal); + const noVariantAttributeValues = getNoVariantAttributeValues($modal); + this.rootProduct = { + product_id: productId, + product_template_id: parseInt(productTemplateId), + quantity: parseFloat($modal.find('input[name="add_qty"]').val() || 1), + variant_values: variantValues, + product_custom_attribute_values: product_custom_attribute_value_ids, + no_variant_attribute_values: noVariantAttributeValues, + }; + return new OptionalProductsModal(null, { + rootProduct: this.rootProduct, + pricelistId: pricelist_id, + okButtonText: _t("Confirm"), + cancelButtonText: _t("Back"), + title: _t("Configure"), + context: + (this.record && this.recordParams && this.record.getContext(this.recordParams)) || + {}, + previousModalHeight: this.$el.closest(".modal-content").height(), + mode: configuratorMode, + }).open(); + }, + /** * Opens the product configurator in "edit" mode. * (see '_openProductConfigurator' for more info on the "edit" mode). @@ -200,25 +276,30 @@ ProductConfiguratorWidget.include({ } this.restoreProductTemplateId = this.recordData.product_template_id; // If line has been set up through the product_configurator: - this._openProductConfigurator({ - configuratorMode: 'edit', - default_product_template_id: this.recordData.product_template_id.data.id, - default_pricelist_id: this._getPricelistId(), - default_product_template_attribute_value_ids: this._convertFromMany2Many( + this._openProductConfigurator( + { + configuratorMode: "edit", + product_template_id: this.recordData.product_template_id.data.id, + pricelist_id: this._getPricelistId(), + product_template_attribute_value_ids: this._toIdArray( this.recordData.product_template_attribute_value_ids ), - default_product_no_variant_attribute_value_ids: this._convertFromMany2Many( + product_no_variant_attribute_value_ids: this._toIdArray( this.recordData.product_no_variant_attribute_value_ids ), - default_product_custom_attribute_value_ids: this._convertFromOne2Many( - this.recordData.product_custom_attribute_value_ids + product_custom_attribute_value_ids: this.recordData.product_custom_attribute_value_ids.data.map( + (d) => d.data ), - default_quantity: this.recordData.product_uom_qty + quantity: this.recordData.product_uom_qty, }, this.dataPointID ); }, + _toIdArray(recordData) { + return recordData ? [...recordData.res_ids] : null; + }, + /** * This will first modify the SO line to update all the information coming from * the product configurator using the 'field_changed' event. @@ -238,11 +319,11 @@ ProductConfiguratorWidget.include({ * @private */ _addProducts: function (result, dataPointId) { - this.trigger_up('field_changed', { + this.trigger_up("field_changed", { dataPointID: dataPointId, preventProductIdCheck: true, optionalProducts: result.options, - changes: this._getMainProductChanges(result.mainProduct) + changes: this._getMainProductChanges(result.mainProduct), }); }, @@ -264,13 +345,13 @@ ProductConfiguratorWidget.include({ */ _getMainProductChanges: function (mainProduct) { var result = { - product_id: {id: mainProduct.product_id}, - product_template_id: {id: mainProduct.product_template_id}, - product_uom_qty: mainProduct.quantity + product_id: { id: mainProduct.product_id }, + product_template_id: { id: mainProduct.product_template_id }, + product_uom_qty: mainProduct.quantity, }; var customAttributeValues = mainProduct.product_custom_attribute_values; - var customValuesCommands = [{operation: 'DELETE_ALL'}]; + var customValuesCommands = [{ operation: "DELETE_ALL" }]; if (customAttributeValues && customAttributeValues.length !== 0) { _.each(customAttributeValues, function (customValue) { // FIXME awa: This could be optimized by adding a "disableDefaultGet" to avoid @@ -280,36 +361,39 @@ ProductConfiguratorWidget.include({ // those "default_" values and set them on the various fields (text,o2m,m2m,...). // -> This is not considered as worth it right now. customValuesCommands.push({ - operation: 'CREATE', - context: [{ - default_custom_product_template_attribute_value_id: customValue.custom_product_template_attribute_value_id, - default_custom_value: customValue.custom_value - }] + operation: "CREATE", + context: [ + { + default_custom_product_template_attribute_value_id: + customValue.custom_product_template_attribute_value_id, + default_custom_value: customValue.custom_value, + }, + ], }); }); } - result['product_custom_attribute_value_ids'] = { - operation: 'MULTI', - commands: customValuesCommands + result["product_custom_attribute_value_ids"] = { + operation: "MULTI", + commands: customValuesCommands, }; var noVariantAttributeValues = mainProduct.no_variant_attribute_values; - var noVariantCommands = [{operation: 'DELETE_ALL'}]; + var noVariantCommands = [{ operation: "DELETE_ALL" }]; if (noVariantAttributeValues && noVariantAttributeValues.length !== 0) { var resIds = _.map(noVariantAttributeValues, function (noVariantValue) { - return {id: parseInt(noVariantValue.value)}; + return { id: parseInt(noVariantValue.value) }; }); noVariantCommands.push({ - operation: 'ADD_M2M', - ids: resIds + operation: "ADD_M2M", + ids: resIds, }); } - result['product_no_variant_attribute_value_ids'] = { - operation: 'MULTI', - commands: noVariantCommands + result["product_no_variant_attribute_value_ids"] = { + operation: "MULTI", + commands: noVariantCommands, }; return result; @@ -339,41 +423,43 @@ ProductConfiguratorWidget.include({ var record = { default_product_id: product.product_id, default_product_template_id: product.product_template_id, - default_product_uom_qty: product.quantity + default_product_uom_qty: product.quantity, }; if (product.no_variant_attribute_values) { var defaultProductNoVariantAttributeValues = []; _.each(product.no_variant_attribute_values, function (attributeValue) { - defaultProductNoVariantAttributeValues.push( - [4, parseInt(attributeValue.value)] - ); + defaultProductNoVariantAttributeValues.push([ + 4, + parseInt(attributeValue.value), + ]); }); - record['default_product_no_variant_attribute_value_ids'] - = defaultProductNoVariantAttributeValues; + record[ + "default_product_no_variant_attribute_value_ids" + ] = defaultProductNoVariantAttributeValues; } if (product.product_custom_attribute_values) { var defaultCustomAttributeValues = []; _.each(product.product_custom_attribute_values, function (attributeValue) { - defaultCustomAttributeValues.push( - [0, 0, { - custom_product_template_attribute_value_id: attributeValue.custom_product_template_attribute_value_id, - custom_value: attributeValue.custom_value - }] - ); + defaultCustomAttributeValues.push([ + 0, + 0, + { + custom_product_template_attribute_value_id: + attributeValue.custom_product_template_attribute_value_id, + custom_value: attributeValue.custom_value, + }, + ]); }); - record['default_product_custom_attribute_value_ids'] - = defaultCustomAttributeValues; + record["default_product_custom_attribute_value_ids"] = defaultCustomAttributeValues; } records.push(record); }); return records; - } + }, }); -return ProductConfiguratorWidget; - -}); +export { ProductConfiguratorWidget }; diff --git a/addons/sale_product_configurator/static/tests/product_configurator.test.js b/addons/sale_product_configurator/static/tests/product_configurator.test.js index cad2beaa9bf..3db2b281df3 100644 --- a/addons/sale_product_configurator/static/tests/product_configurator.test.js +++ b/addons/sale_product_configurator/static/tests/product_configurator.test.js @@ -1,9 +1,8 @@ -odoo.define('sale.product.configurator.tests', function (require) { -"use strict"; - -const FormView = require('web.FormView'); -const ProductConfiguratorFormView = require('sale_product_configurator.ProductConfiguratorFormView'); -const testUtils = require('web.test_utils'); +/** @odoo-module */ +import FormView from 'web.FormView'; +import testUtils from 'web.test_utils'; +import { patchWithCleanup } from "@web/../tests/helpers/utils"; +import { ProductConfiguratorWidget } from "@sale_product_configurator/js/product_configurator_widget"; const createView = testUtils.createView; const getArch = function (){ @@ -175,8 +174,11 @@ QUnit.module('Product Configurator', { }); QUnit.test('Select a configurable product template and verify that the product configurator is opened', async function (assert) { - assert.expect(2); - + patchWithCleanup(ProductConfiguratorWidget.prototype, { + _openProductConfigurator() { + assert.step("open configurator"); + } + }); const form = await createView({ View: FormView, model: 'sale_order', @@ -189,17 +191,12 @@ QUnit.module('Product Configurator', { } return this._super.apply(this, arguments); }, - intercepts: { - do_action: function (ev) { - if (ev.data.action === 'sale_product_configurator.sale_product_configurator_action') { - assert.ok(true); - } - }, - } }); await testUtils.dom.click(form.$("a:contains('Add a product')")); + assert.verifySteps([]); await testUtils.fields.many2one.searchAndClickItem("product_template_id", {item: 'Customizable Desk'}); + assert.verifySteps(["open configurator"]); form.destroy(); }); @@ -226,39 +223,6 @@ QUnit.module('Product Configurator', { form.destroy(); }); - QUnit.test('Select a product in the list and check for template loading', async function (assert) { - assert.expect(1); - - const product_configurator_form = await createView({ - View: ProductConfiguratorFormView, - model: 'sale_product_configurator', - data: this.data, - arch: - '
' + - '' + - '' + - '' + - '' + - '' + - '' + - '
' + - '
' + - '
', - mockRPC: function (route) { - if (route === '/sale_product_configurator/configure') { - assert.ok(true); - return Promise.resolve('
plop
'); - } - return this._super.apply(this, arguments); - } - }); - await testUtils.dom.click(product_configurator_form.$('.o_input')); - await testUtils.dom.click($("ul.ui-autocomplete li a:contains('Customizable Desk')").mouseenter()); - product_configurator_form.destroy(); - }); - QUnit.test('drag and drop rows containing product_configurator many2one', async function (assert) { assert.expect(4); @@ -305,5 +269,3 @@ QUnit.module('Product Configurator', { form.destroy(); }); }); - -}); diff --git a/addons/sale_product_configurator/views/templates.xml b/addons/sale_product_configurator/views/templates.xml index 4ae24d35d17..f15584272bc 100644 --- a/addons/sale_product_configurator/views/templates.xml +++ b/addons/sale_product_configurator/views/templates.xml @@ -147,9 +147,11 @@ }'/> -

Available Options:

- - + +

Available Options:

+ + + diff --git a/addons/sale_product_configurator/wizard/__init__.py b/addons/sale_product_configurator/wizard/__init__.py deleted file mode 100644 index a0eaada809c..00000000000 --- a/addons/sale_product_configurator/wizard/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from . import sale_product_configurator diff --git a/addons/sale_product_configurator/wizard/sale_product_configurator.py b/addons/sale_product_configurator/wizard/sale_product_configurator.py deleted file mode 100644 index 4d636383471..00000000000 --- a/addons/sale_product_configurator/wizard/sale_product_configurator.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import models, fields - - -class SaleProductConfigurator(models.TransientModel): - _name = 'sale.product.configurator' - _description = 'Sale Product Configurator' - - product_template_id = fields.Many2one( - 'product.template', string="Product", - required=True, domain=[('sale_ok', '=', True), ('attribute_line_ids.value_ids', '!=', False)]) - quantity = fields.Integer('Quantity') - pricelist_id = fields.Many2one('product.pricelist', 'Pricelist', readonly=True) - product_template_attribute_value_ids = fields.Many2many( - 'product.template.attribute.value', 'product_configurator_template_attribute_value_rel', string='Attribute Values', readonly=True) - product_custom_attribute_value_ids = fields.Many2many( - 'product.attribute.custom.value', 'product_configurator_custom_attribute_value_rel', string="Custom Values") - product_no_variant_attribute_value_ids = fields.Many2many( - 'product.template.attribute.value', 'product_configurator_no_variant_attribute_value_rel', string="Extra Values") diff --git a/addons/sale_product_configurator/wizard/sale_product_configurator_views.xml b/addons/sale_product_configurator/wizard/sale_product_configurator_views.xml deleted file mode 100644 index 7c6a4ac275e..00000000000 --- a/addons/sale_product_configurator/wizard/sale_product_configurator_views.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - sale_product_configurator.product.configurator.view.form - sale.product.configurator - -
- - - - - - - - - - - - - - - - -
-
-
-
-
- - - Configure a product - sale.product.configurator - form - new - - -
diff --git a/addons/sale_product_matrix/static/src/js/product_matrix_configurator.js b/addons/sale_product_matrix/static/src/js/product_matrix_configurator.js index eb43f416e22..e9ff37cb564 100644 --- a/addons/sale_product_matrix/static/src/js/product_matrix_configurator.js +++ b/addons/sale_product_matrix/static/src/js/product_matrix_configurator.js @@ -1,5 +1,5 @@ -odoo.define('sale_product_matrix.product_configurator', function (require) { -var ProductConfiguratorWidget = require('sale_product_configurator.product_configurator'); +/** @odoo-module */ +import { ProductConfiguratorWidget } from "@sale_product_configurator/js/product_configurator_widget"; /** * Extension of the ProductConfiguratorWidget to support product configuration @@ -10,15 +10,14 @@ var ProductConfiguratorWidget = require('sale_product_configurator.product_confi * */ ProductConfiguratorWidget.include({ - /** * @override */ - _openConfigurator: function (result, productTemplateId, dataPointId) { + _openConfigurator: function (result, productTemplateId, dataPointId) { var self = this; var mode = result.mode; this._super.apply(this, arguments).then(function (configuratorOpened) { - if (!configuratorOpened && mode === 'matrix') { + if (!configuratorOpened && mode === "matrix") { self._openGridConfigurator(productTemplateId, dataPointId); return Promise.resolve(true); } @@ -28,9 +27,9 @@ ProductConfiguratorWidget.include({ _openGridConfigurator: function (productTemplateId, dataPointId, edit) { var attribs = edit ? this._getPTAVS() : []; - this.trigger_up('open_matrix', { + this.trigger_up("open_matrix", { product_template_id: productTemplateId, - model: 'sale.order', + model: "sale.order", dataPointId: dataPointId, edit: edit, editedCellAttributes: attribs, @@ -38,42 +37,26 @@ ProductConfiguratorWidget.include({ }, _onEditProductConfiguration: function () { + const { _super } = this; if (!this.recordData.is_configurable_product) { // if line should be edited by another configurator // or simply inline. - this._super.apply(this, arguments); + _super.apply(this, arguments); return; } var self = this; var productTemplateId = this.recordData.product_template_id.data.id; this._rpc({ - model: 'product.template', - method: 'read', - args: [productTemplateId, ['product_add_mode']], + model: "product.template", + method: "read", + args: [productTemplateId, ["product_add_mode"]], }).then(function (result) { - if (result && result[0].product_add_mode === 'matrix') { + if (result && result[0].product_add_mode === "matrix") { self._openGridConfigurator(productTemplateId, self.dataPointID, true); } else { - self.restoreProductTemplateId = self.recordData.product_template_id; // Call super only if product_add_mode different than matrix // to avoid product configurator opening (which is the default case). - self._openProductConfigurator({ - configuratorMode: 'edit', - default_product_template_id: self.recordData.product_template_id.data.id, - default_pricelist_id: self._getPricelistId(), - default_product_template_attribute_value_ids: self._convertFromMany2Many( - self.recordData.product_template_attribute_value_ids - ), - default_product_no_variant_attribute_value_ids: self._convertFromMany2Many( - self.recordData.product_no_variant_attribute_value_ids - ), - default_product_custom_attribute_value_ids: self._convertFromOne2Many( - self.recordData.product_custom_attribute_value_ids - ), - default_quantity: self.recordData.product_uom_qty - }, - self.dataPointID - ); + return _super.apply(self, arguments); } }); }, @@ -81,7 +64,7 @@ ProductConfiguratorWidget.include({ /** * Returns the list of attribute ids (product.template.attribute.value) * from the current SOLine. - */ + */ _getPTAVS: function () { var PTAVSIDS = []; _.each(this.recordData.product_no_variant_attribute_value_ids.res_ids, function (id) { @@ -90,9 +73,8 @@ ProductConfiguratorWidget.include({ _.each(this.recordData.product_template_attribute_value_ids.res_ids, function (id) { PTAVSIDS.push(id); }); - return PTAVSIDS.sort(function (a, b) {return a - b;}); - } - -}); - + return PTAVSIDS.sort(function (a, b) { + return a - b; + }); + }, }); diff --git a/addons/test_sale_product_configurators/static/tests/tours/product_configurator_edition_ui.js b/addons/test_sale_product_configurators/static/tests/tours/product_configurator_edition_ui.js index d4b2435307b..efbe75440c7 100644 --- a/addons/test_sale_product_configurators/static/tests/tours/product_configurator_edition_ui.js +++ b/addons/test_sale_product_configurators/static/tests/tours/product_configurator_edition_ui.js @@ -45,13 +45,13 @@ tour.register('sale_product_configurator_edition_tour', { }, { trigger: '.o_edit_product_configuration', }, { - trigger: '.configurator_container li.js_attribute_value:has(span:contains("Aluminium")) input:checked', + trigger: '.main_product li.js_attribute_value:has(span:contains("Aluminium")) input:checked', run: function (){} // check updated legs }, { trigger: 'span.oe_currency_value:contains("800")', run: function (){} // check updated price }, { - trigger: '.configurator_container span:contains("Steel")', + trigger: '.main_product span:contains("Steel")', run: function () { $('input.product_id').change(function () { if ($('.o_sale_product_configurator_edit').attr('request_count')) { @@ -63,20 +63,20 @@ tour.register('sale_product_configurator_edition_tour', { }); } }, { - trigger: '.configurator_container span:contains("Custom")', + trigger: '.main_product span:contains("Custom")', run: function () { // FIXME awa: since jquery3 update it doesn't "click" // on the element without this run (and 'run: "click"' // doesn't work either) - $('.configurator_container span:contains("Custom")').click(); + $('.main_product span:contains("Custom")').click(); } }, { - trigger: '.configurator_container .variant_custom_value', + trigger: '.main_product .variant_custom_value', run: 'text nice custom value' }, { trigger: 'input[data-value_name="Black"]', }, { - trigger: '.o_sale_product_configurator_edit[request_count="2"]', + trigger: '.product_display_name:contains("Customizable Desk (TEST) (Custom, Black)")', run: function (){} // used to sync with "get_combination_info" completion }, { trigger: '.o_sale_product_configurator_edit', @@ -93,7 +93,7 @@ tour.register('sale_product_configurator_edition_tour', { }, { trigger: '.o_edit_product_configuration', }, { - trigger: '.configurator_container .variant_custom_value', + trigger: '.main_product .variant_custom_value', run: 'text another nice custom value' }, { trigger: '.o_sale_product_configurator_edit', @@ -106,25 +106,25 @@ tour.register('sale_product_configurator_edition_tour', { }, { trigger: '.o_edit_product_configuration', }, { - trigger: '.configurator_container span:contains("Steel")', + trigger: '.main_product span:contains("Steel")', run: function () { $('input.product_id').change(function () { $('.o_sale_product_configurator_edit').attr('request_count', 1); }); } }, { - trigger: '.configurator_container span:contains("Steel")', + trigger: '.main_product span:contains("Steel")', run: function () { // FIXME awa: since jquery3 update it doesn't "click" // on the element without this run (and 'run: "click"' // doesn't work either) - $('.configurator_container span:contains("Steel")').click(); + $('.main_product span:contains("Steel")').click(); } }, { trigger: '.o_sale_product_configurator_edit[request_count="1"]', run: function (){} // used to sync with "get_combination_info" completion }, { - trigger: '.configurator_container button.js_add_cart_json:has(.fa-plus)', + trigger: '.main_product button.js_add_cart_json:has(.fa-plus)', }, { trigger: '.o_sale_product_configurator_edit', }, { diff --git a/addons/test_sale_product_configurators/static/tests/tours/product_configurator_single_custom_attribute_ui.js b/addons/test_sale_product_configurators/static/tests/tours/product_configurator_single_custom_attribute_ui.js index d1dd1aaaa0d..fcb6b9ccab8 100644 --- a/addons/test_sale_product_configurators/static/tests/tours/product_configurator_single_custom_attribute_ui.js +++ b/addons/test_sale_product_configurators/static/tests/tours/product_configurator_single_custom_attribute_ui.js @@ -45,20 +45,20 @@ tour.register('sale_product_configurator_single_custom_attribute_tour', { }, { trigger: '.o_edit_product_configuration', }, { - trigger: '.configurator_container .variant_custom_value', + trigger: '.main_product .variant_custom_value', run: function () { // check custom value initialized - if ($('.configurator_container .variant_custom_value').val() === "great single custom value") { - $('.configurator_container').addClass('tour_success_2'); + if ($('.main_product .variant_custom_value').val() === "great single custom value") { + $('.main_product').addClass('tour_success_2'); } } }, { - trigger: '.configurator_container.tour_success_2', + trigger: '.main_product.tour_success_2', run: function () { //check } }, { - trigger: '.configurator_container', + trigger: '.main_product', run: function () { window.location.href = window.location.origin + '/web'; } diff --git a/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js b/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js index 339dde30b23..274b07f83a3 100644 --- a/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js +++ b/addons/website_sale_product_configurator/static/src/js/product_configurator_modal.js @@ -1,7 +1,7 @@ odoo.define('website_sale_product_configurator.OptionalProductsModal', function (require) { "use strict"; -var OptionalProductsModal = require('sale_product_configurator.OptionalProductsModal'); +const { OptionalProductsModal } = require('@sale_product_configurator/js/product_configurator_modal'); OptionalProductsModal.include({ /** diff --git a/addons/website_sale_product_configurator/static/src/js/website_sale_options.js b/addons/website_sale_product_configurator/static/src/js/website_sale_options.js index 0c43812b4b5..a34bfcf2a3e 100644 --- a/addons/website_sale_product_configurator/static/src/js/website_sale_options.js +++ b/addons/website_sale_product_configurator/static/src/js/website_sale_options.js @@ -4,7 +4,7 @@ odoo.define('website_sale_options.website_sale', function (require) { var ajax = require('web.ajax'); var core = require('web.core'); var publicWidget = require('web.public.widget'); -var OptionalProductsModal = require('sale_product_configurator.OptionalProductsModal'); +var { OptionalProductsModal } = require('@sale_product_configurator/js/product_configurator_modal'); require('website_sale.website_sale'); var _t = core._t;