From 154fc7d9dd550d35b7266af024e54c20e18938fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Warnon?= Date: Fri, 28 Sep 2018 09:09:51 +0200 Subject: [PATCH] [IMP] product,(website_)sale: Product configurator bug fix Targets commit d3530eb07e24278117ab396ecb86a59deecc312d 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 --- .../src/js/section_and_note_fields_backend.js | 20 +++++- addons/product/views/product_views.xml | 2 +- .../sale/controllers/product_configurator.py | 2 +- addons/sale/models/sale.py | 1 - .../src/js/product_configurator_controller.js | 23 +++---- .../src/js/product_configurator_mixin.js | 37 +++++++++++ .../src/js/product_configurator_modal.js | 29 ++++---- .../src/js/product_configurator_renderer.js | 3 +- .../static/tests/product_configurator.test.js | 36 ++++++---- .../sale_product_configurator_templates.xml | 2 +- addons/sale/views/sale_views.xml | 13 ---- .../sale/wizard/sale_product_configurator.py | 3 +- .../views/sale_management_views.xml | 6 -- addons/website_sale/controllers/main.py | 10 ++- .../static/src/js/website_sale_options.js | 21 ++---- .../src/scss/website_sale_frontend.scss | 5 ++ addons/website_sale/views/templates.xml | 8 +-- .../static/src/js/website_sale_comparison.js | 44 +++++++++---- .../website_sale_comparison_template.xml | 4 +- .../static/src/js/website_sale_wishlist.js | 66 ++++++++++++------- .../src/scss/website_sale_wishlist.scss | 5 ++ .../views/website_sale_wishlist_template.xml | 4 +- 22 files changed, 215 insertions(+), 129 deletions(-) diff --git a/addons/account/static/src/js/section_and_note_fields_backend.js b/addons/account/static/src/js/section_and_note_fields_backend.js index bd3cb3e563a..8880c0b137c 100644 --- a/addons/account/static/src/js/section_and_note_fields_backend.js +++ b/addons/account/static/src/js/section_and_note_fields_backend.js @@ -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 diff --git a/addons/product/views/product_views.xml b/addons/product/views/product_views.xml index 8dfb874d3a9..03c4f09096c 100644 --- a/addons/product/views/product_views.xml +++ b/addons/product/views/product_views.xml @@ -11,7 +11,7 @@
diff --git a/addons/sale/controllers/product_configurator.py b/addons/sale/controllers/product_configurator.py index 8bc58f09139..d008362316a 100644 --- a/addons/sale/controllers/product_configurator.py +++ b/addons/sale/controllers/product_configurator.py @@ -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 diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index 1208ea85c8a..6246fa32ca4 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -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') diff --git a/addons/sale/static/src/js/product_configurator_controller.js b/addons/sale/static/src/js/product_configurator_controller.js index 6f2306daa17..824bcb923a7 100644 --- a/addons/sale/static/src/js/product_configurator_controller.js +++ b/addons/sale/static/src/js/product_configurator_controller.js @@ -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') diff --git a/addons/sale/static/src/js/product_configurator_mixin.js b/addons/sale/static/src/js/product_configurator_mixin.js index f9191519b71..1dfd62b815b 100644 --- a/addons/sale/static/src/js/product_configurator_mixin.js +++ b/addons/sale/static/src/js/product_configurator_mixin.js @@ -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 //-------------------------------------------------------------------------- diff --git a/addons/sale/static/src/js/product_configurator_modal.js b/addons/sale/static/src/js/product_configurator_modal.js index b7a5627fed4..1cebc683162 100644 --- a/addons/sale/static/src/js/product_configurator_modal.js +++ b/addons/sale/static/src/js/product_configurator_modal.js @@ -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(); } diff --git a/addons/sale/static/src/js/product_configurator_renderer.js b/addons/sale/static/src/js/product_configurator_renderer.js index 7d28669dd15..9a613501a2f 100644 --- a/addons/sale/static/src/js/product_configurator_renderer.js +++ b/addons/sale/static/src/js/product_configurator_renderer.js @@ -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 diff --git a/addons/sale/static/tests/product_configurator.test.js b/addons/sale/static/tests/product_configurator.test.js index 0eef8a09088..0cec92b8d3a 100644 --- a/addons/sale/static/tests/product_configurator.test.js +++ b/addons/sale/static/tests/product_configurator.test.js @@ -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 '
' + '' + - '' + + '' + + '' + '' + '' + - '' + + '' + '' + '' + '' + - '' + + '' + '' + '' + '' + @@ -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 = '
'; - 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(); }); diff --git a/addons/sale/views/sale_product_configurator_templates.xml b/addons/sale/views/sale_product_configurator_templates.xml index cf6010ee5cb..ee67ef63536 100644 --- a/addons/sale/views/sale_product_configurator_templates.xml +++ b/addons/sale/views/sale_product_configurator_templates.xml @@ -122,7 +122,7 @@