From 3cb46ec74ca8e8d96b358ccce3afc32ff4986bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Warnon?= Date: Tue, 19 Mar 2019 13:40:48 +0000 Subject: [PATCH] [ADD/MOV] sale_product_configurator: new module and files move Purpose ======= - Moved the product_configurator into its own module now that we have a specific option for it. The option only enables the p. configurator in the SO form. The e-commerce should always allow customer to configure their products, even if the option is not checked. This allows moving some of the code specific to the p. configurator into its own module. However, a lot of the code has to remain in "sale" module since the e-commerce does not require the option checked and needs the "configure product" feature in the shop by default. (This is why the fields "product_custom_attribute_value_ids" and "product_no_variant_attribute_value_ids" are still in the "sale" module) --- addons/product/models/res_config_settings.py | 14 ++ addons/sale/__manifest__.py | 3 +- addons/sale/controllers/__init__.py | 2 +- addons/sale/controllers/variant.py | 28 +++ addons/sale/data/sale_demo.xml | 17 -- addons/sale/models/product_product.py | 10 -- .../src/js/product_configurator_mixin.js | 4 +- addons/sale/views/assets.xml | 6 - .../sale/views/res_config_settings_views.xml | 13 +- addons/sale/views/sale_views.xml | 7 - addons/sale/views/variant_templates.xml | 108 ++++++++++++ addons/sale/wizard/__init__.py | 1 - addons/sale_management/__manifest__.py | 1 - addons/sale_management/tests/test_sale_ui.py | 116 ------------- addons/sale_management/views/assets.xml | 10 -- addons/sale_product_configurator/__init__.py | 5 + .../sale_product_configurator/__manifest__.py | 26 +++ .../controllers/__init__.py | 4 + .../controllers/main.py} | 47 +----- .../data/sale_demo.xml | 25 +++ .../models/product.py | 14 ++ .../src/js/product_configurator_controller.js | 7 +- .../src/js/product_configurator_modal.js | 6 +- .../src/js/product_configurator_renderer.js | 2 +- .../src/js/product_configurator_view.js | 6 +- .../static/tests/product_configurator.test.js | 2 +- .../product_configurator_advanced_test_ui.js | 0 .../product_configurator_pricelist_test_ui.js | 0 .../tests/product_configurator_test_ui.js | 0 .../tests/__init__.py | 4 + .../test_sale_product_configurator_ui.py | 159 ++++++++++++++++++ .../views/assets.xml | 27 +++ .../views/sale_views.xml | 17 ++ .../views/templates.xml} | 116 +------------ .../wizard/sale_product_configurator.py | 0 .../sale_product_configurator_views.xml | 2 +- .../views/res_config_settings_views.xml | 11 ++ 37 files changed, 480 insertions(+), 340 deletions(-) create mode 100644 addons/sale/controllers/variant.py create mode 100644 addons/sale/views/variant_templates.xml delete mode 100644 addons/sale_management/views/assets.xml create mode 100644 addons/sale_product_configurator/__init__.py create mode 100644 addons/sale_product_configurator/__manifest__.py create mode 100644 addons/sale_product_configurator/controllers/__init__.py rename addons/{sale/controllers/product_configurator.py => sale_product_configurator/controllers/main.py} (61%) create mode 100644 addons/sale_product_configurator/data/sale_demo.xml create mode 100644 addons/sale_product_configurator/models/product.py rename addons/{sale => sale_product_configurator}/static/src/js/product_configurator_controller.js (96%) rename addons/{sale => sale_product_configurator}/static/src/js/product_configurator_modal.js (98%) rename addons/{sale => sale_product_configurator}/static/src/js/product_configurator_renderer.js (94%) rename addons/{sale => sale_product_configurator}/static/src/js/product_configurator_view.js (58%) rename addons/{sale => sale_product_configurator}/static/tests/product_configurator.test.js (98%) rename addons/{sale_management => sale_product_configurator}/static/tests/product_configurator_advanced_test_ui.js (100%) rename addons/{sale_management => sale_product_configurator}/static/tests/product_configurator_pricelist_test_ui.js (100%) rename addons/{sale_management => sale_product_configurator}/static/tests/product_configurator_test_ui.js (100%) create mode 100644 addons/sale_product_configurator/tests/__init__.py create mode 100644 addons/sale_product_configurator/tests/test_sale_product_configurator_ui.py create mode 100644 addons/sale_product_configurator/views/assets.xml create mode 100644 addons/sale_product_configurator/views/sale_views.xml rename addons/{sale/views/sale_product_configurator_templates.xml => sale_product_configurator/views/templates.xml} (59%) rename addons/{sale => sale_product_configurator}/wizard/sale_product_configurator.py (100%) rename addons/{sale => sale_product_configurator}/wizard/sale_product_configurator_views.xml (99%) diff --git a/addons/product/models/res_config_settings.py b/addons/product/models/res_config_settings.py index 7e73f4df5e6..6272b1f37ba 100644 --- a/addons/product/models/res_config_settings.py +++ b/addons/product/models/res_config_settings.py @@ -14,6 +14,7 @@ class ResConfigSettings(models.TransientModel): " * Unchecked : Each company can see only its product (product where company is defined). Product not related to a company are visible for all companies.") group_uom = fields.Boolean("Units of Measure", implied_group='uom.group_uom') group_product_variant = fields.Boolean("Variants and Options", implied_group='product.group_product_variant') + module_sale_product_configurator = fields.Boolean("Product Configurator") group_stock_packaging = fields.Boolean('Product Packagings', implied_group='product.group_stock_packaging') group_sale_pricelist = fields.Boolean("Use pricelists to adapt your price per customers", @@ -33,6 +34,19 @@ class ResConfigSettings(models.TransientModel): ('1', 'Cubic Feet'), ], 'Volume unit of measure', config_parameter='product.volume_in_cubic_feet', default='0') + @api.onchange('group_product_variant') + def _onchange_group_product_variant(self): + """The product Configurator requires the product variants activated. + If the user disables the product variants -> disable the product configurator as well""" + if self.module_sale_product_configurator and not self.group_product_variant: + self.module_sale_product_configurator = False + + @api.onchange('module_sale_product_configurator') + def _onchange_module_sale_product_configurator(self): + """The product Configurator requires the product variants activated + If the user enables the product configurator -> enable the product variants as well""" + if self.module_sale_product_configurator and not self.group_product_variant: + self.group_product_variant = True @api.model def get_values(self): diff --git a/addons/sale/__manifest__.py b/addons/sale/__manifest__.py index 741d357c900..c275df0ac9b 100644 --- a/addons/sale/__manifest__.py +++ b/addons/sale/__manifest__.py @@ -27,13 +27,12 @@ This module contains all the common features of Sales Management and eCommerce. 'views/res_partner_views.xml', 'views/mail_activity_views.xml', 'views/assets.xml', + 'views/variant_templates.xml', 'views/sale_portal_templates.xml', - 'views/sale_product_configurator_templates.xml', 'views/sale_onboarding_views.xml', 'views/res_config_settings_views.xml', 'views/payment_views.xml', 'views/product_attribute_views.xml', - 'wizard/sale_product_configurator_views.xml', ], 'demo': [ 'data/sale_demo.xml', diff --git a/addons/sale/controllers/__init__.py b/addons/sale/controllers/__init__.py index ac5f1269f82..a35a880227c 100644 --- a/addons/sale/controllers/__init__.py +++ b/addons/sale/controllers/__init__.py @@ -3,4 +3,4 @@ from . import onboarding from . import portal -from . import product_configurator +from . import variant diff --git a/addons/sale/controllers/variant.py b/addons/sale/controllers/variant.py new file mode 100644 index 00000000000..3ca2c7c32b0 --- /dev/null +++ b/addons/sale/controllers/variant.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import http +from odoo.http import request + + +class VariantController(http.Controller): + @http.route(['/sale/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(['/sale/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 _get_pricelist(self, pricelist_id, pricelist_fallback=False): + return request.env['product.pricelist'].browse(int(pricelist_id or 0)) + + 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) diff --git a/addons/sale/data/sale_demo.xml b/addons/sale/data/sale_demo.xml index d0ccf291eff..d83063b9158 100644 --- a/addons/sale/data/sale_demo.xml +++ b/addons/sale/data/sale_demo.xml @@ -730,23 +730,6 @@ Thanks! - - Chair floor protection - - 12.0 - 0.01 - - - Office chairs can harm your floor: protect it. - - - - - - - - - Custom diff --git a/addons/sale/models/product_product.py b/addons/sale/models/product_product.py index a489b03b952..8d919e39ac3 100644 --- a/addons/sale/models/product_product.py +++ b/addons/sale/models/product_product.py @@ -5,16 +5,6 @@ from odoo import api, fields, models from odoo.tools.float_utils import float_round -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.).") - - class ProductProduct(models.Model): _inherit = 'product.product' diff --git a/addons/sale/static/src/js/product_configurator_mixin.js b/addons/sale/static/src/js/product_configurator_mixin.js index 80471814207..7e7847215a7 100644 --- a/addons/sale/static/src/js/product_configurator_mixin.js +++ b/addons/sale/static/src/js/product_configurator_mixin.js @@ -68,7 +68,7 @@ var ProductConfiguratorMixin = { self._checkExclusions($parent, combination); - ajax.jsonRpc(this._getUri('/product_configurator/get_combination_info'), 'call', { + ajax.jsonRpc(this._getUri('/sale/get_combination_info'), 'call', { product_template_id: parseInt($parent.find('.product_template_id').val()), product_id: this._getProductId($parent), combination: combination, @@ -311,7 +311,7 @@ var ProductConfiguratorMixin = { // We don't handle that compatibility because the previous code was // not working either: it was making an RPC that failed with any // non-admin user anyway. To use this feature, restart the server. - var route = '/product_configurator/create_product_variant'; + var route = '/sale/create_product_variant'; if (useAjax) { productReady = ajax.jsonRpc(route, 'call', params); } else { diff --git a/addons/sale/views/assets.xml b/addons/sale/views/assets.xml index ea5c82b1c86..9e7e42eff80 100644 --- a/addons/sale/views/assets.xml +++ b/addons/sale/views/assets.xml @@ -8,11 +8,6 @@ - - - - - @@ -28,7 +23,6 @@ diff --git a/addons/sale/views/res_config_settings_views.xml b/addons/sale/views/res_config_settings_views.xml index bc1366e5d5c..3f2bb403453 100644 --- a/addons/sale/views/res_config_settings_views.xml +++ b/addons/sale/views/res_config_settings_views.xml @@ -18,7 +18,7 @@
+
+
+ +
+
+
+
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/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 @@ -