diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index bcf728e356b..e793650ecb6 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -81,13 +81,6 @@
0
-
-
-
-
-
-
-
diff --git a/addons/sale/views/variant_templates.xml b/addons/sale/views/variant_templates.xml
new file mode 100644
index 00000000000..8d52d860be4
--- /dev/null
+++ b/addons/sale/views/variant_templates.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sale/wizard/__init__.py b/addons/sale/wizard/__init__.py
index 145cce89c1e..09f069f85ed 100644
--- a/addons/sale/wizard/__init__.py
+++ b/addons/sale/wizard/__init__.py
@@ -3,4 +3,3 @@
from . import payment_acquirer_onboarding_wizard
from . import sale_make_invoice_advance
-from . import sale_product_configurator
\ No newline at end of file
diff --git a/addons/sale_management/__manifest__.py b/addons/sale_management/__manifest__.py
index 3bf801a604b..98f77d20956 100644
--- a/addons/sale_management/__manifest__.py
+++ b/addons/sale_management/__manifest__.py
@@ -47,7 +47,6 @@ The Dashboard for the Sales Manager will include
'views/sale_management_views.xml',
'views/digest_views.xml',
'views/sale_order_views.xml',
- 'views/assets.xml',
'report/sale_report_templates.xml',
],
'demo': [
diff --git a/addons/sale_management/tests/test_sale_ui.py b/addons/sale_management/tests/test_sale_ui.py
index 6ec2f4167c1..cf107b02ebc 100644
--- a/addons/sale_management/tests/test_sale_ui.py
+++ b/addons/sale_management/tests/test_sale_ui.py
@@ -7,119 +7,3 @@ class TestUi(odoo.tests.HttpCase):
def test_01_sale_tour(self):
self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_tour.ready", login="admin")
-
- def test_02_product_configurator(self):
- # group_product_variant: use the product configurator
- # group_sale_pricelist: display the pricelist to determine when it is changed after choosing
- # the partner
- self.env.ref('base.user_admin').write({
- 'groups_id': [
- (4, self.env.ref('product.group_product_variant').id),
- (4, self.env.ref('product.group_sale_pricelist').id),
- ],
- })
-
- self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_tour.ready", login="admin")
-
- def test_03_product_configurator_advanced(self):
- # group_product_variant: use the product configurator
- # group_sale_pricelist: display the pricelist to determine when it is changed after choosing
- # the partner
- self.env.ref('base.user_admin').write({
- 'groups_id': [
- (4, self.env.ref('product.group_product_variant').id),
- (4, self.env.ref('product.group_sale_pricelist').id),
- ],
- })
-
- # Prepare relevant test data
- # This is not included in demo data to avoid useless noise
- product_attributes = self.env['product.attribute'].create([{
- 'name': 'PA1',
- 'type': 'radio',
- 'create_variant': 'dynamic'
- }, {
- 'name': 'PA2',
- 'type': 'radio',
- 'create_variant': 'always'
- }, {
- 'name': 'PA3',
- 'type': 'radio',
- 'create_variant': 'dynamic'
- }, {
- 'name': 'PA4',
- 'type': 'select',
- 'create_variant': 'no_variant'
- }, {
- 'name': 'PA5',
- 'type': 'select',
- 'create_variant': 'no_variant'
- }, {
- 'name': 'PA7',
- 'type': 'color',
- 'create_variant': 'no_variant'
- }, {
- 'name': 'PA8',
- 'type': 'radio',
- 'create_variant': 'no_variant'
- }])
-
- product_attribute_values = self.env['product.attribute.value'].create([{
- 'name': 'PAV' + str(i),
- 'is_custom': i == 9,
- 'attribute_id': product_attribute.id
- } for i in range(1, 11) for product_attribute in product_attributes])
-
- product_template_attribute_lines = self.env['product.template.attribute.line'].create([{
- 'attribute_id': product_attribute.id,
- 'product_tmpl_id': self.env.ref("product.product_product_4").id,
- 'value_ids': [(6, 0, product_attribute_values.filtered(
- lambda product_attribute_value: product_attribute_value.attribute_id == product_attribute
- ).ids)]
- } for product_attribute in product_attributes])
-
- self.env.ref("product.product_product_4").update({
- 'attribute_line_ids': [(4, product_template_attribute_line.id) for product_template_attribute_line in product_template_attribute_lines]
- })
-
- self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_advanced_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_advanced_tour.ready", login="admin")
-
- def test_04_product_configurator_pricelist(self):
- """The goal of this test is to make sure pricelist rules are correctly
- applied on the backend product configurator.
- Also testing B2C setting: no impact on the backend configurator.
- """
-
- admin = self.env.ref('base.user_admin')
-
- # Activate B2C
- self.env.ref('account.group_show_line_subtotals_tax_included').users |= admin
- self.env.ref('account.group_show_line_subtotals_tax_excluded').users -= admin
-
- # Active pricelist on SO
- self.env.ref('product.group_sale_pricelist').users |= admin
-
- # Add a 15% tax on desk
- tax = self.env['account.tax'].create({'name': "Test tax", 'amount': 15})
- self.env.ref('product.product_product_4_product_template').taxes_id = tax
-
- # Remove tax from Conference Chair and Chair floor protection
- self.env.ref('sale.product_product_1_product_template').taxes_id = None
- self.env.ref('product.product_product_11_product_template').taxes_id = None
-
- # Make sure pricelist rule exist
- product_template = self.env.ref('product.product_product_4_product_template')
- pricelist = self.env.ref('product.list0')
-
- if not pricelist.item_ids.filtered(lambda i: i.product_tmpl_id == product_template and i.price_discount == 20):
- self.env['product.pricelist.item'].create({
- 'base': 'list_price',
- 'applied_on': '1_product',
- 'pricelist_id': pricelist.id,
- 'product_tmpl_id': product_template.id,
- 'price_discount': 20,
- 'min_quantity': 2,
- 'compute_price': 'formula',
- })
-
- self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_pricelist_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_pricelist_tour.ready", login="admin")
diff --git a/addons/sale_management/views/assets.xml b/addons/sale_management/views/assets.xml
deleted file mode 100644
index a5a3134949d..00000000000
--- a/addons/sale_management/views/assets.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/sale_product_configurator/__init__.py b/addons/sale_product_configurator/__init__.py
new file mode 100644
index 00000000000..1f4530082b3
--- /dev/null
+++ b/addons/sale_product_configurator/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import controllers
+from . import wizard
diff --git a/addons/sale_product_configurator/__manifest__.py b/addons/sale_product_configurator/__manifest__.py
new file mode 100644
index 00000000000..fd72a2fec6f
--- /dev/null
+++ b/addons/sale_product_configurator/__manifest__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+ 'name': "Sale Product Configurator",
+ 'version': '1.0',
+ 'category': 'Hidden',
+ 'summary': "Configure your products",
+
+ 'description': """
+ Technical module installed when the user checks the "module_sale_product_configurator" setting.
+ The main purpose is to override the sale_order view to allow configuring products in the SO form.
+
+ It also enables the optional products feature.
+ """,
+
+ 'depends': ['sale'],
+ 'data': [
+ 'views/assets.xml',
+ 'views/templates.xml',
+ 'views/sale_views.xml',
+ 'wizard/sale_product_configurator_views.xml',
+ ],
+ 'demo': [
+ 'data/sale_demo.xml',
+ ],
+}
diff --git a/addons/sale_product_configurator/controllers/__init__.py b/addons/sale_product_configurator/controllers/__init__.py
new file mode 100644
index 00000000000..5d4b25db9c0
--- /dev/null
+++ b/addons/sale_product_configurator/controllers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import main
diff --git a/addons/sale/controllers/product_configurator.py b/addons/sale_product_configurator/controllers/main.py
similarity index 61%
rename from addons/sale/controllers/product_configurator.py
rename to addons/sale_product_configurator/controllers/main.py
index 8ebcf879ba8..d991c4ff277 100644
--- a/addons/sale/controllers/product_configurator.py
+++ b/addons/sale_product_configurator/controllers/main.py
@@ -6,7 +6,7 @@ from odoo.http import request
class ProductConfiguratorController(http.Controller):
- @http.route(['/product_configurator/configure'], type='json', auth="user", methods=['POST'])
+ @http.route(['/sale_product_configurator/configure'], type='json', auth="user", methods=['POST'])
def configure(self, product_id, pricelist_id, **kw):
add_qty = int(kw.get('add_qty', 1))
product_template = request.env['product.template'].browse(int(product_id))
@@ -17,39 +17,24 @@ class ProductConfiguratorController(http.Controller):
product_template = product_template.with_context(pricelist=pricelist.id, partner=request.env.user.partner_id)
to_currency = pricelist.currency_id
- return request.env['ir.ui.view'].render_template("sale.product_configurator_configure", {
+ return request.env['ir.ui.view'].render_template("sale_product_configurator.configure", {
'product': product_template,
# to_currency deprecated, get it from the pricelist or product directly
'to_currency': to_currency,
'pricelist': pricelist,
'add_qty': add_qty,
- # get_attribute_exclusions deprecated, use product method
- 'get_attribute_exclusions': self._get_attribute_exclusions
})
- @http.route(['/product_configurator/show_optional_products'], type='json', auth="user", methods=['POST'])
+ @http.route(['/sale_product_configurator/show_optional_products'], type='json', auth="user", methods=['POST'])
def show_optional_products(self, product_id, variant_values, pricelist_id, **kw):
pricelist = self._get_pricelist(pricelist_id)
return self._show_optional_products(product_id, variant_values, pricelist, False, **kw)
- @http.route(['/product_configurator/optional_product_items'], type='json', auth="user", methods=['POST'])
+ @http.route(['/sale_product_configurator/optional_product_items'], type='json', auth="user", methods=['POST'])
def optional_product_items(self, product_id, pricelist_id, **kw):
pricelist = self._get_pricelist(pricelist_id)
return self._optional_product_items(product_id, pricelist, **kw)
- @http.route(['/product_configurator/get_combination_info'], type='json', auth="user", methods=['POST'])
- def get_combination_info(self, product_template_id, product_id, combination, add_qty, pricelist_id, **kw):
- combination = request.env['product.template.attribute.value'].browse(combination)
- pricelist = self._get_pricelist(pricelist_id)
- ProductTemplate = request.env['product.template']
- if 'context' in kw:
- ProductTemplate = ProductTemplate.with_context(**kw.get('context'))
- return ProductTemplate.browse(int(product_template_id))._get_combination_info(combination, int(product_id or 0), int(add_qty or 1), pricelist)
-
- @http.route(['/product_configurator/create_product_variant'], type='json', auth="user", methods=['POST'])
- def create_product_variant(self, product_template_id, product_template_attribute_value_ids, **kwargs):
- return request.env['product.template'].browse(int(product_template_id)).create_product_variant(product_template_attribute_value_ids)
-
def _optional_product_items(self, product_id, pricelist, **kw):
add_qty = int(kw.get('add_qty', 1))
product = request.env['product.product'].with_context(self._get_product_context(pricelist, **kw)).browse(int(product_id))
@@ -64,7 +49,7 @@ class ProductConfiguratorController(http.Controller):
# They are kept in the context since they are not linked to this product variant
parent_combination |= product.env.context.get('no_variant_attribute_values')
- return request.env['ir.ui.view'].render_template("sale.optional_product_items", {
+ return request.env['ir.ui.view'].render_template("sale_product_configurator.optional_product_items", {
# product deprecated, it's not used in the view
'product': product,
# reference_product deprecated, use parent_combination instead
@@ -74,8 +59,6 @@ class ProductConfiguratorController(http.Controller):
'pricelist': pricelist,
# to_currency deprecated, get from pricelist or product
'to_currency': to_currency,
- # get_attribute_exclusions deprecated, use product method
- 'get_attribute_exclusions': self._get_attribute_exclusions,
'add_qty': add_qty,
})
@@ -101,7 +84,7 @@ class ProductConfiguratorController(http.Controller):
if no_variant_attribute_values:
product = product.with_context(no_variant_attribute_values=no_variant_attribute_values)
- return request.env['ir.ui.view'].render_template("sale.optional_products_modal", {
+ return request.env['ir.ui.view'].render_template("sale_product_configurator.optional_products_modal", {
'product': product,
'combination': combination,
'add_qty': add_qty,
@@ -115,21 +98,8 @@ class ProductConfiguratorController(http.Controller):
# to_currency deprecated, get from pricelist or product
'to_currency': to_currency,
'handle_stock': handle_stock,
- # get_attribute_exclusions deprecated, use product method
- 'get_attribute_exclusions': self._get_attribute_exclusions,
})
- def _get_attribute_exclusions(self, product, reference_product=None):
- """deprecated, use product method"""
- parent_combination = request.env['product.template.attribute.value']
- if reference_product:
- parent_combination |= reference_product.product_template_attribute_value_ids
- if reference_product.env.context.get('no_variant_attribute_values'):
- # Add "no_variant" attribute values' exclusions
- # They are kept in the context since they are not linked to this product variant
- parent_combination |= reference_product.env.context.get('no_variant_attribute_values')
- return product._get_attribute_exclusions(parent_combination, reference_product.name if reference_product else None)
-
def _get_product_context(self, pricelist=None, **kw):
"""deprecated, can be removed in master"""
product_context = dict(request.context)
@@ -140,10 +110,5 @@ class ProductConfiguratorController(http.Controller):
return product_context
- def _get_combination_info(self, product_template_id, product_id, combination, add_qty, pricelist, **kw):
- """deprecated, use product method"""
- combination = request.env['product.template.attribute.value'].browse(combination)
- return request.env['product.template'].browse(product_template_id)._get_combination_info(combination, product_id, add_qty, pricelist)
-
def _get_pricelist(self, pricelist_id, pricelist_fallback=False):
return request.env['product.pricelist'].browse(int(pricelist_id or 0))
diff --git a/addons/sale_product_configurator/data/sale_demo.xml b/addons/sale_product_configurator/data/sale_demo.xml
new file mode 100644
index 00000000000..00683af8855
--- /dev/null
+++ b/addons/sale_product_configurator/data/sale_demo.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ Chair floor protection
+
+ 12.0
+ 0.01
+
+
+ Office chairs can harm your floor: protect it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sale_product_configurator/models/product.py b/addons/sale_product_configurator/models/product.py
new file mode 100644
index 00000000000..a47d6d4421a
--- /dev/null
+++ b/addons/sale_product_configurator/models/product.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ optional_product_ids = fields.Many2many(
+ 'product.template', 'product_optional_rel', 'src_id', 'dest_id',
+ string='Optional Products', help="Optional Products are suggested "
+ "whenever the customer hits *Add to Cart* (cross-sell strategy, "
+ "e.g. for computers: warranty, software, etc.).")
diff --git a/addons/sale/static/src/js/product_configurator_controller.js b/addons/sale_product_configurator/static/src/js/product_configurator_controller.js
similarity index 96%
rename from addons/sale/static/src/js/product_configurator_controller.js
rename to addons/sale_product_configurator/static/src/js/product_configurator_controller.js
index 7ca3d32c292..a7411a77370 100644
--- a/addons/sale/static/src/js/product_configurator_controller.js
+++ b/addons/sale_product_configurator/static/src/js/product_configurator_controller.js
@@ -1,10 +1,10 @@
-odoo.define('sale.ProductConfiguratorFormController', function (require) {
+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.OptionalProductsModal');
+var OptionalProductsModal = require('sale_product_configurator.OptionalProductsModal');
var ProductConfiguratorFormController = FormController.extend({
custom_events: _.extend({}, FormController.prototype.custom_events, {
@@ -127,8 +127,7 @@ var ProductConfiguratorFormController = FormController.extend({
pricelistId: self.renderer.pricelistId,
okButtonText: _t('Confirm'),
cancelButtonText: _t('Back'),
- title: _t('Configure'),
- context: self.initialState.context,
+ title: _t('Configure')
}).open();
self.optionalProductsModal.on('options_empty', null,
diff --git a/addons/sale/static/src/js/product_configurator_modal.js b/addons/sale_product_configurator/static/src/js/product_configurator_modal.js
similarity index 98%
rename from addons/sale/static/src/js/product_configurator_modal.js
rename to addons/sale_product_configurator/static/src/js/product_configurator_modal.js
index 3bcebf88725..9fc78b9528f 100644
--- a/addons/sale/static/src/js/product_configurator_modal.js
+++ b/addons/sale_product_configurator/static/src/js/product_configurator_modal.js
@@ -1,4 +1,4 @@
-odoo.define('sale.OptionalProductsModal', function (require) {
+odoo.define('sale_product_configurator.OptionalProductsModal', function (require) {
"use strict";
var ajax = require('web.ajax');
@@ -79,7 +79,7 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, ProductConfiguratorMixi
willStart: function () {
var self = this;
- var uri = this._getUri("/product_configurator/show_optional_products");
+ var uri = this._getUri("/sale_product_configurator/show_optional_products");
var getModalContent = ajax.jsonRpc(uri, 'call', {
product_id: self.rootProduct.product_id,
variant_values: self.rootProduct.variant_values,
@@ -361,7 +361,7 @@ var OptionalProductsModal = Dialog.extend(ServicesMixin, ProductConfiguratorMixi
).then(function (productId) {
$parent.find('.product_id').val(productId);
- ajax.jsonRpc(self._getUri("/product_configurator/optional_product_items"), 'call', {
+ ajax.jsonRpc(self._getUri("/sale_product_configurator/optional_product_items"), 'call', {
'product_id': productId,
'pricelist_id': self.pricelistId || false,
}).then(function (addedItem) {
diff --git a/addons/sale/static/src/js/product_configurator_renderer.js b/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js
similarity index 94%
rename from addons/sale/static/src/js/product_configurator_renderer.js
rename to addons/sale_product_configurator/static/src/js/product_configurator_renderer.js
index f3f437461ea..62a7d7ea06b 100644
--- a/addons/sale/static/src/js/product_configurator_renderer.js
+++ b/addons/sale_product_configurator/static/src/js/product_configurator_renderer.js
@@ -1,4 +1,4 @@
-odoo.define('sale.ProductConfiguratorFormRenderer', function (require) {
+odoo.define('sale_product_configurator.ProductConfiguratorFormRenderer', function (require) {
"use strict";
var FormRenderer = require('web.FormRenderer');
diff --git a/addons/sale/static/src/js/product_configurator_view.js b/addons/sale_product_configurator/static/src/js/product_configurator_view.js
similarity index 58%
rename from addons/sale/static/src/js/product_configurator_view.js
rename to addons/sale_product_configurator/static/src/js/product_configurator_view.js
index 5a3bb4401be..d3294deb42d 100644
--- a/addons/sale/static/src/js/product_configurator_view.js
+++ b/addons/sale_product_configurator/static/src/js/product_configurator_view.js
@@ -1,8 +1,8 @@
-odoo.define('sale.ProductConfiguratorFormView', function (require) {
+odoo.define('sale_product_configurator.ProductConfiguratorFormView', function (require) {
"use strict";
-var ProductConfiguratorFormController = require('sale.ProductConfiguratorFormController');
-var ProductConfiguratorFormRenderer = require('sale.ProductConfiguratorFormRenderer');
+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');
diff --git a/addons/sale/static/tests/product_configurator.test.js b/addons/sale_product_configurator/static/tests/product_configurator.test.js
similarity index 98%
rename from addons/sale/static/tests/product_configurator.test.js
rename to addons/sale_product_configurator/static/tests/product_configurator.test.js
index c751eb6c111..e39fa64644d 100644
--- a/addons/sale/static/tests/product_configurator.test.js
+++ b/addons/sale_product_configurator/static/tests/product_configurator.test.js
@@ -2,7 +2,7 @@ odoo.define('sale.product.configurator.tests', function (require) {
"use strict";
var FormView = require('web.FormView');
- var ProductConfiguratorFormView = require('sale.ProductConfiguratorFormView');
+ var ProductConfiguratorFormView = require('sale_product_configurator.ProductConfiguratorFormView');
var testUtils = require('web.test_utils');
var createView = testUtils.createView;
diff --git a/addons/sale_management/static/tests/product_configurator_advanced_test_ui.js b/addons/sale_product_configurator/static/tests/product_configurator_advanced_test_ui.js
similarity index 100%
rename from addons/sale_management/static/tests/product_configurator_advanced_test_ui.js
rename to addons/sale_product_configurator/static/tests/product_configurator_advanced_test_ui.js
diff --git a/addons/sale_management/static/tests/product_configurator_pricelist_test_ui.js b/addons/sale_product_configurator/static/tests/product_configurator_pricelist_test_ui.js
similarity index 100%
rename from addons/sale_management/static/tests/product_configurator_pricelist_test_ui.js
rename to addons/sale_product_configurator/static/tests/product_configurator_pricelist_test_ui.js
diff --git a/addons/sale_management/static/tests/product_configurator_test_ui.js b/addons/sale_product_configurator/static/tests/product_configurator_test_ui.js
similarity index 100%
rename from addons/sale_management/static/tests/product_configurator_test_ui.js
rename to addons/sale_product_configurator/static/tests/product_configurator_test_ui.js
diff --git a/addons/sale_product_configurator/tests/__init__.py b/addons/sale_product_configurator/tests/__init__.py
new file mode 100644
index 00000000000..c1cb786c919
--- /dev/null
+++ b/addons/sale_product_configurator/tests/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import test_sale_product_configurator_ui
diff --git a/addons/sale_product_configurator/tests/test_sale_product_configurator_ui.py b/addons/sale_product_configurator/tests/test_sale_product_configurator_ui.py
new file mode 100644
index 00000000000..a37e5da7f8a
--- /dev/null
+++ b/addons/sale_product_configurator/tests/test_sale_product_configurator_ui.py
@@ -0,0 +1,159 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import odoo.tests
+
+
+@odoo.tests.tagged('post_install', '-at_install')
+class TestUi(odoo.tests.HttpCase):
+ def test_01_product_configurator(self):
+ # To be able to test the product configurator, admin user must have access to "variants and options" feature, so we give him the right group for that
+ self.env.ref('base.user_admin').write({'groups_id': [(4, self.env.ref('product.group_product_variant').id)]})
+
+ self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_tour.ready", login="admin")
+
+ def test_02_product_configurator_advanced(self):
+ # group_product_variant: use the product configurator
+ # group_sale_pricelist: display the pricelist to determine when it is changed after choosing
+ # the partner
+ self.env.ref('base.user_admin').write({
+ 'groups_id': [
+ (4, self.env.ref('product.group_product_variant').id),
+ (4, self.env.ref('product.group_sale_pricelist').id),
+ ],
+ })
+
+ # Prepare relevant test data
+ # This is not included in demo data to avoid useless noise
+ product_attributes = self.env['product.attribute'].create([{
+ 'name': 'PA1',
+ 'type': 'radio',
+ 'create_variant': 'dynamic'
+ }, {
+ 'name': 'PA2',
+ 'type': 'radio',
+ 'create_variant': 'always'
+ }, {
+ 'name': 'PA3',
+ 'type': 'radio',
+ 'create_variant': 'dynamic'
+ }, {
+ 'name': 'PA4',
+ 'type': 'select',
+ 'create_variant': 'no_variant'
+ }, {
+ 'name': 'PA5',
+ 'type': 'select',
+ 'create_variant': 'no_variant'
+ }, {
+ 'name': 'PA7',
+ 'type': 'color',
+ 'create_variant': 'no_variant'
+ }, {
+ 'name': 'PA8',
+ 'type': 'radio',
+ 'create_variant': 'no_variant'
+ }])
+
+ product_attribute_values = self.env['product.attribute.value'].create([{
+ 'name': 'PAV' + str(i),
+ 'is_custom': i == 9,
+ 'attribute_id': product_attribute.id
+ } for i in range(1, 11) for product_attribute in product_attributes])
+
+ product_template_attribute_lines = self.env['product.template.attribute.line'].create([{
+ 'attribute_id': product_attribute.id,
+ 'product_tmpl_id': self.env.ref("product.product_product_4").id,
+ 'value_ids': [(6, 0, product_attribute_values.filtered(
+ lambda product_attribute_value: product_attribute_value.attribute_id == product_attribute
+ ).ids)]
+ } for product_attribute in product_attributes])
+
+ self.env.ref("product.product_product_4").update({
+ 'attribute_line_ids': [(4, product_template_attribute_line.id) for product_template_attribute_line in product_template_attribute_lines]
+ })
+
+ self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_advanced_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_advanced_tour.ready", login="admin")
+
+ def test_03_product_configurator_edition(self):
+ # To be able to test the product configurator, admin user must have access to "variants and options" feature, so we give him the right group for that
+ self.env.ref('base.user_admin').write({'groups_id': [(4, self.env.ref('product.group_product_variant').id)]})
+
+ self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_edition_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_edition_tour.ready", login="admin")
+
+ def test_04_product_configurator_single_custom_value(self):
+ # group_product_variant: use the product configurator
+ # group_sale_pricelist: display the pricelist to determine when it is changed after choosing
+ # the partner
+ self.env.ref('base.user_admin').write({
+ 'groups_id': [
+ (4, self.env.ref('product.group_product_variant').id),
+ (4, self.env.ref('product.group_sale_pricelist').id),
+ ],
+ })
+
+ # Prepare relevant test data
+ # This is not included in demo data to avoid useless noise
+ product_attributes = self.env['product.attribute'].create([{
+ 'name': 'product attribute',
+ 'type': 'radio',
+ 'create_variant': 'always'
+ }])
+
+ product_attribute_values = self.env['product.attribute.value'].create([{
+ 'name': 'single product attribute value',
+ 'is_custom': True,
+ 'attribute_id': product_attributes[0].id
+ }])
+
+ product_template_attribute_lines = self.env['product.template.attribute.line'].create([{
+ 'attribute_id': product_attributes[0].id,
+ 'product_tmpl_id': self.env.ref("product.product_product_4").id,
+ 'value_ids': [(6, 0, [product_attribute_values[0].id])]
+ }])
+
+ self.env.ref("product.product_product_4").update({
+ 'attribute_line_ids': [(4, product_template_attribute_lines[0].id)]
+ })
+
+ self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_single_custom_attribute_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_single_custom_attribute_tour.ready", login="admin")
+
+ def test_05_product_configurator_pricelist(self):
+ """The goal of this test is to make sure pricelist rules are correctly
+ applied on the backend product configurator.
+ Also testing B2C setting: no impact on the backend configurator.
+ """
+
+ admin = self.env.ref('base.user_admin')
+
+ # Activate B2C
+ self.env.ref('account.group_show_line_subtotals_tax_included').users |= admin
+ self.env.ref('account.group_show_line_subtotals_tax_excluded').users -= admin
+
+ # Active pricelist on SO
+ self.env.ref('product.group_sale_pricelist').users |= admin
+
+ # Add a 15% tax on desk
+ tax = self.env['account.tax'].create({'name': "Test tax", 'amount': 15})
+ self.env.ref('product.product_product_4_product_template').taxes_id = tax
+
+ # Remove tax from Conference Chair and Chair floor protection
+ self.env.ref('sale_product_configurator.product_product_1_product_template').taxes_id = None
+ self.env.ref('product.product_product_11_product_template').taxes_id = None
+
+ # Make sure pricelist rule exist
+ product_template = self.env.ref('product.product_product_4_product_template')
+ pricelist = self.env.ref('product.list0')
+
+ if not pricelist.item_ids.filtered(lambda i: i.product_tmpl_id == product_template and i.price_discount == 20):
+ self.env['product.pricelist.item'].create({
+ 'base': 'list_price',
+ 'applied_on': '1_product',
+ 'pricelist_id': pricelist.id,
+ 'product_tmpl_id': product_template.id,
+ 'price_discount': 20,
+ 'min_quantity': 2,
+ 'compute_price': 'formula',
+ })
+
+ self.phantom_js("/web", "odoo.__DEBUG__.services['web_tour.tour'].run('sale_product_configurator_pricelist_tour')", "odoo.__DEBUG__.services['web_tour.tour'].tours.sale_product_configurator_pricelist_tour.ready", login="admin")
\ No newline at end of file
diff --git a/addons/sale_product_configurator/views/assets.xml b/addons/sale_product_configurator/views/assets.xml
new file mode 100644
index 00000000000..3e68f30d810
--- /dev/null
+++ b/addons/sale_product_configurator/views/assets.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sale_product_configurator/views/sale_views.xml b/addons/sale_product_configurator/views/sale_views.xml
new file mode 100644
index 00000000000..499cb2c7eb0
--- /dev/null
+++ b/addons/sale_product_configurator/views/sale_views.xml
@@ -0,0 +1,17 @@
+
+
+
+ product.template.form.inherit.sale.product.configurator
+ product.template
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sale/views/sale_product_configurator_templates.xml b/addons/sale_product_configurator/views/templates.xml
similarity index 59%
rename from addons/sale/views/sale_product_configurator_templates.xml
rename to addons/sale_product_configurator/views/templates.xml
index 1f0ec29d534..534a199dc70 100644
--- a/addons/sale/views/sale_product_configurator_templates.xml
+++ b/addons/sale_product_configurator/views/templates.xml
@@ -2,12 +2,12 @@
-
+
-
+
@@ -64,7 +64,7 @@
-
+
@@ -141,7 +141,7 @@
Available Options: |
-
+
@@ -201,112 +201,4 @@
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/sale/wizard/sale_product_configurator.py b/addons/sale_product_configurator/wizard/sale_product_configurator.py
similarity index 100%
rename from addons/sale/wizard/sale_product_configurator.py
rename to addons/sale_product_configurator/wizard/sale_product_configurator.py
diff --git a/addons/sale/wizard/sale_product_configurator_views.xml b/addons/sale_product_configurator/wizard/sale_product_configurator_views.xml
similarity index 99%
rename from addons/sale/wizard/sale_product_configurator_views.xml
rename to addons/sale_product_configurator/wizard/sale_product_configurator_views.xml
index 9b29c3f9183..a3d3eddfee9 100644
--- a/addons/sale/wizard/sale_product_configurator_views.xml
+++ b/addons/sale_product_configurator/wizard/sale_product_configurator_views.xml
@@ -15,4 +15,4 @@
-
\ No newline at end of file
+
diff --git a/addons/website_sale/views/res_config_settings_views.xml b/addons/website_sale/views/res_config_settings_views.xml
index 6a2daeee7da..9890566f03b 100644
--- a/addons/website_sale/views/res_config_settings_views.xml
+++ b/addons/website_sale/views/res_config_settings_views.xml
@@ -25,6 +25,17 @@
+
+
+
+
+
+
+
+ Configure your attributes with exclusions and select optional products
+
+
+