diff --git a/addons/website_sale/data/data.xml b/addons/website_sale/data/data.xml index ccc5ef78c98..3ad98c695c7 100644 --- a/addons/website_sale/data/data.xml +++ b/addons/website_sale/data/data.xml @@ -110,6 +110,16 @@ model.env.context['product_template_id'] = request.params.get('productTemplateId response = DynamicFilter._get_products('recently_sold_with', model.env.context) + + Alternative Products + + code + +DynamicFilter = model.env['website.snippet.filter'] +model.env.context['product_template_id'] = request.params.get('productTemplateId') +response = DynamicFilter._get_products('alternative_products', model.env.context) + + @@ -143,6 +153,13 @@ response = DynamicFilter._get_products('recently_sold_with', model.env.context) Products Recently Sold With Product True + + + display_name,description_sale,image_512 + + Alternative Products + True + sale.order diff --git a/addons/website_sale/models/product_template.py b/addons/website_sale/models/product_template.py index c6597abe5db..92b0b050ba0 100644 --- a/addons/website_sale/models/product_template.py +++ b/addons/website_sale/models/product_template.py @@ -382,6 +382,10 @@ class ProductTemplate(models.Model): return self.website_ribbon_id return self.product_tag_ids.ribbon_id[:1] or self.product_variant_ids.additional_product_tag_ids.ribbon_id[:1] + @api.model + def _get_alternative_product_filter(self): + return self.env.ref('website_sale.dynamic_filter_cross_selling_alternative_products').id + # --------------------------------------------------------- # Rating Mixin API # --------------------------------------------------------- diff --git a/addons/website_sale/models/website_snippet_filter.py b/addons/website_sale/models/website_snippet_filter.py index 5d1d7f3d448..ce6ec975893 100644 --- a/addons/website_sale/models/website_snippet_filter.py +++ b/addons/website_sale/models/website_snippet_filter.py @@ -157,3 +157,22 @@ class WebsiteSnippetFilter(models.Model): ]) products = self.env['product.product'].with_context(display_default_code=False).search(domain, limit=limit) return products + + def _get_products_alternative_products(self, website, limit, domain, context): + products = self.env['product.product'] + current_id = context.get('product_template_id') + if not current_id: + return products + current_template = self.env['product.template'].browse(int(current_id)) + if current_template.exists(): + excluded_products = website.sale_get_order().order_line.product_id + excluded_products |= current_template.product_variant_ids + included_products = current_template.alternative_product_ids.product_variant_ids + products = included_products - excluded_products + if products: + domain = expression.AND([ + domain, + [('id', 'in', products.ids)], + ]) + products = self.env['product.product'].with_context(display_default_code=False).search(domain, limit=limit) + return products diff --git a/addons/website_sale/static/src/js/website_sale.editor.js b/addons/website_sale/static/src/js/website_sale.editor.js index e9af1eef78b..82970911746 100644 --- a/addons/website_sale/static/src/js/website_sale.editor.js +++ b/addons/website_sale/static/src/js/website_sale.editor.js @@ -766,4 +766,31 @@ options.registry.WebsiteSaleProductAttribute = options.Class.extend({ return this._super(methodName, params); }, }); + +// Disable "Shown on Mobile" option if for dynamic product snippets +options.registry.MobileVisibility.include({ + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + async _computeVisibility() { + return await this._super(...arguments) + && !this.$target.hasClass('s_dynamic_snippet_products'); + }, +}); + +// Disable save for alternative products snippet +options.registry.SnippetSave.include({ + /** + * @override + */ + async _computeVisibility() { + return await this._super(...arguments) + && !this.$target.hasClass('o_wsale_alternative_products'); + } +}) }); diff --git a/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/000.js b/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/000.js index 7de3b122c73..f0286f6e3ed 100644 --- a/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/000.js +++ b/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/000.js @@ -81,10 +81,19 @@ const DynamicSnippetProducts = DynamicSnippetCarousel.extend({ if (productNames) { const nameDomain = []; for (const productName of productNames.split(',')) { + // Ignore empty names + if (!productName.length) { + continue; + } + // Search on name, internal reference and barcode. if (nameDomain.length) { nameDomain.unshift('|'); } - nameDomain.push(['name', 'ilike', productName]); + nameDomain.push(...[ + '|', '|', ['name', 'ilike', productName], + ['default_code', '=', productName], + ['barcode', '=', productName], + ]); } searchDomain.push(...nameDomain); } diff --git a/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/options.js b/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/options.js index b6f3ab370f5..1d203be31de 100644 --- a/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/options.js +++ b/addons/website_sale/static/src/snippets/s_dynamic_snippet_products/options.js @@ -6,6 +6,10 @@ const s_dynamic_snippet_carousel_options = require('website.s_dynamic_snippet_ca var wUtils = require('website.utils'); +const alternativeSnippetRemovedOptions = [ + 'filter_opt', 'product_category_opt', 'product_tag_opt', 'product_names_opt', +] + const dynamicSnippetProductsOptions = s_dynamic_snippet_carousel_options.extend({ /** @@ -23,11 +27,22 @@ const dynamicSnippetProductsOptions = s_dynamic_snippet_carousel_options.extend( this.contextualFilterDomain.push(['product_cross_selling', '=', false]); } this.productCategories = {}; + this.isAlternativeProductSnippet = this.$target.hasClass('o_wsale_alternative_products'); }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + /** + * @private + * @override + */ + _computeWidgetVisibility(widgetName, params) { + if (this.isAlternativeProductSnippet && alternativeSnippetRemovedOptions.includes(widgetName)) { + return false; + } + return this._super(...arguments); + }, /** * Fetches product categories. * @private diff --git a/addons/website_sale/views/snippets/s_dynamic_snippet_products.xml b/addons/website_sale/views/snippets/s_dynamic_snippet_products.xml index aaaa19cdeff..1f38be1641a 100644 --- a/addons/website_sale/views/snippets/s_dynamic_snippet_products.xml +++ b/addons/website_sale/views/snippets/s_dynamic_snippet_products.xml @@ -32,7 +32,7 @@ data-select-data-attribute=""/> + placeholder="e.g. lamp,bin" title="Comma-separated list of parts of product names, barcodes or internal reference"/> diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 162730b9635..197cf580455 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -724,6 +724,28 @@ + + - - - -