From 560febd89304e69ebb5c8527a9e32e29a35d8bd9 Mon Sep 17 00:00:00 2001 From: Ruchir Shukla Date: Sun, 10 Nov 2024 18:42:57 +0530 Subject: [PATCH] [MIG][17.0] website_product_configurator:Migration to 17.0 --- website_product_configurator/__init__.py | 1 - website_product_configurator/__manifest__.py | 14 +- .../controllers/main.py | 93 ++- .../data/config_form_templates.xml | 121 ++- .../data/ir_config_parameter_data.xml | 19 +- .../demo/product_template_demo.xml | 10 +- .../models/product_config.py | 12 +- .../models/res_config_settings.py | 45 +- .../models/sale_order.py | 265 ++----- .../security/configurator_security.xml | 26 +- .../static/src/css/config_form.css | 151 ---- .../static/src/css/tooltip.css | 8 - .../static/src/js/config_form.esm.js | 629 ++++++++++++++++ .../static/src/js/config_form.js | 704 ------------------ .../static/src/js/website_config_tour.js | 180 ----- .../static/src/js/website_sale.esm.js | 23 + .../static/src/js/website_sale.js | 55 -- .../tests/tours/website_config_tour.esm.js | 170 +++++ .../tests/__init__.py | 3 +- .../tests/test_res_config_settings.py | 22 +- .../tests/test_sale_order.py | 37 +- .../tests/test_website_product_config.py | 14 +- ...est_website_product_configurator_values.py | 8 +- website_product_configurator/views/assets.xml | 35 - .../views/product_view.xml | 15 +- .../views/res_config_settings_view.xml | 72 +- 26 files changed, 1078 insertions(+), 1654 deletions(-) delete mode 100644 website_product_configurator/static/src/css/config_form.css delete mode 100644 website_product_configurator/static/src/css/tooltip.css create mode 100644 website_product_configurator/static/src/js/config_form.esm.js delete mode 100644 website_product_configurator/static/src/js/config_form.js delete mode 100644 website_product_configurator/static/src/js/website_config_tour.js create mode 100644 website_product_configurator/static/src/js/website_sale.esm.js delete mode 100644 website_product_configurator/static/src/js/website_sale.js create mode 100644 website_product_configurator/static/tests/tours/website_config_tour.esm.js delete mode 100644 website_product_configurator/views/assets.xml diff --git a/website_product_configurator/__init__.py b/website_product_configurator/__init__.py index 5a9543a4b8..f7209b1710 100644 --- a/website_product_configurator/__init__.py +++ b/website_product_configurator/__init__.py @@ -1,3 +1,2 @@ from . import models from . import controllers -from . import tests diff --git a/website_product_configurator/__manifest__.py b/website_product_configurator/__manifest__.py index 3e50eb258e..11082d7bd0 100644 --- a/website_product_configurator/__manifest__.py +++ b/website_product_configurator/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Website Product Configurator", - "version": "14.0.1.2.0", + "version": "17.0.1.0.0", "summary": """Configure products in e-shop""", "author": "Pledra, Odoo Community Association (OCA)", "license": "AGPL-3", @@ -16,11 +16,21 @@ "data/ir_config_parameter_data.xml", "data/config_form_templates.xml", "data/cron.xml", - "views/assets.xml", "views/product_view.xml", "views/templates.xml", "views/res_config_settings_view.xml", ], + "assets": { + "web.assets_frontend": [ + "website_product_configurator/static/src/js/config_form.esm.js", + "website_product_configurator/static/src/js/website_sale.esm.js", + "website_product_configurator/static/src/scss/config_form.scss", + "website_product_configurator/static/src/scss/tooltip.scss", + ], + "web.assets_tests": [ + "website_product_configurator/static/tests/tours/website_config_tour.esm.js", + ], + }, "demo": ["demo/product_template_demo.xml"], "images": ["static/description/cover.png"], "application": True, diff --git a/website_product_configurator/controllers/main.py b/website_product_configurator/controllers/main.py index 7886c35918..2accf9d7f4 100644 --- a/website_product_configurator/controllers/main.py +++ b/website_product_configurator/controllers/main.py @@ -1,12 +1,37 @@ -import json +import logging from odoo import http, models from odoo.exceptions import UserError, ValidationError -from odoo.http import request +from odoo.http import request, route from odoo.tools.safe_eval import safe_eval from odoo.addons.http_routing.models.ir_http import slug from odoo.addons.website_sale.controllers.main import WebsiteSale +from odoo.addons.website_sale_product_configurator.controllers.main import ( + WebsiteSaleProductConfiguratorController, +) + +_logger = logging.getLogger(__name__) + + +class CustomWebsiteSaleProductConfigurator(WebsiteSaleProductConfiguratorController): + @route() + def show_advanced_configurator( + self, + product_id, + variant_values, + add_qty=1, + force_dialog=False, + **kw, + ): + """Inherit: skips showing the advanced product configurator modal for + a product""" + product = request.env["product.product"].browse(int(product_id)) + if product.config_ok: + return False + return super().show_advanced_configurator( + product_id, variant_values, add_qty=add_qty, force_dialog=force_dialog, **kw + ) def get_pricelist(): @@ -30,9 +55,11 @@ def get_config_session(self, product_tmpl_id): is_public_user = request.env.user.has_group("base.group_public") cfg_session_id = product_config_sessions.get(product_tmpl_id.id) if cfg_session_id: - cfg_session = cfg_session_obj.browse(int(cfg_session_id)) + cfg_session = cfg_session_obj.search( + [("id", "=", int(cfg_session_id))], limit=1 + ) - # Retrieve an active configuration session or create a new one + # Retrieve an active configuration session or create a new one. if not cfg_session or not cfg_session.exists(): cfg_session = cfg_session_obj.sudo().create_get_session( product_tmpl_id.id, @@ -50,9 +77,7 @@ def get_config_session(self, product_tmpl_id): def product(self, product, category="", search="", **kwargs): # Use parent workflow for regular products if not product.config_ok or not product.attribute_line_ids: - return super(ProductConfigWebsiteSale, self).product( - product, category, search, **kwargs - ) + return super().product(product, category=category, search=search, **kwargs) try: cfg_session = self.get_config_session(product_tmpl_id=product) except Exception: @@ -129,7 +154,7 @@ def get_render_vals(self, cfg_session): request.env["decimal.precision"].precision_get("Stock Weight") or 2 ) website_tmpl_xml_id = cfg_session.get_config_form_website_template() - pricelist = request.website.get_current_pricelist() + pricelist = request.website._get_current_pricelist() product_tmpl = cfg_session.product_tmpl_id attr_value_ids = product_tmpl.attribute_line_ids.mapped("value_ids") av_obj = request.env["product.attribute.value"] @@ -213,7 +238,7 @@ def get_current_configuration(self, form_values, cfg_session): product_attribute_lines = product_tmpl_id.attribute_line_ids value_ids = [] for attr_line in product_attribute_lines: - field_name = "%s%s" % (field_prefix, attr_line.attribute_id.id) + field_name = f"{field_prefix}{attr_line.attribute_id.id}" attr_values = form_values.get(field_name, False) if attr_line.custom and attr_values == custom_val_id.id: pass @@ -272,8 +297,8 @@ def get_orm_form_vals(self, form_vals, config_session): config_vals = {} for attr_line in product_tmpl_id.attribute_line_ids.sorted(): attribute_id = attr_line.attribute_id.id - field_name = "%s%s" % (field_prefix, attribute_id) - custom_field = "%s%s" % (custom_field_prefix, attribute_id) + field_name = f"{field_prefix}{attribute_id}" + custom_field = f"{custom_field_prefix}{attribute_id}" field_value = values.get(field_name, []) field_value = [int(s) for s in field_value] @@ -344,7 +369,7 @@ def onchange(self, form_values, field_name, **post): updates = {} try: updates = product_configurator_obj.sudo().apply_onchange_values( - values=config_vals, field_name=field_name, field_onchange=specs + values=config_vals, field_names=[field_name], field_onchange=specs ) updates["value"] = self.remove_recursive_list(updates["value"]) except Exception as Ex: @@ -378,7 +403,7 @@ def onchange(self, form_values, field_name, **post): image_line_ids=config_image_ids, model_name=config_image_ids[:1]._name, ) - pricelist = request.website.get_current_pricelist() + pricelist = request.website._get_current_pricelist() updates["open_cfg_step_line_ids"] = open_cfg_step_line_ids updates["config_image_vals"] = image_vals decimal_prec_obj = request.env["decimal.precision"] @@ -468,7 +493,7 @@ def save_configuration( if valid: check_next_step = False except Exception: - pass + _logger.error("Error validating configuration.") if check_next_step: result = self.set_config_next_step( config_session_id=config_session_id, @@ -516,7 +541,7 @@ def cfg_session(self, cfg_session_id, **post): or cfg_session_id.state != "done" ): return request.render("website.page_404") - product_id = cfg_session_id.product_id + product_id = cfg_session_id.product_id.sudo() product_tmpl_id = product_id.product_tmpl_id custom_vals = sorted( @@ -531,10 +556,10 @@ def cfg_session(self, cfg_session_id, **post): ) pricelist = get_pricelist() product_config_session = request.session.get("product_config_session") + if product_config_session and product_config_session.get(product_tmpl_id.id): # Bizzappdev end code - del product_config_session[product_tmpl_id.id] - request.session["product_config_session"] = product_config_session + request.session.pop("product_config_session", None) reconfigure_product_url = "/product_configurator/reconfigure/%s" % slug( product_id @@ -590,37 +615,3 @@ def render_error(self, error=None, message="", **post): ) vals = {"message": message, "error": error} return request.render("website_product_configurator.error_page", vals) - - @http.route() - def cart_update(self, product_id, add_qty=1, set_qty=0, **kw): - """This route is called when adding a product to cart (no options).""" - sale_order = request.website.sale_get_order(force_create=True) - if sale_order.state != "draft": - request.session["sale_order_id"] = None - sale_order = request.website.sale_get_order(force_create=True) - - product_custom_attribute_values = None - if kw.get("product_custom_attribute_values"): - product_custom_attribute_values = json.loads( - kw.get("product_custom_attribute_values") - ) - - no_variant_attribute_values = None - if kw.get("no_variant_attribute_values"): - no_variant_attribute_values = json.loads( - kw.get("no_variant_attribute_values") - ) - - sale_order._cart_update( - product_id=int(product_id), - add_qty=add_qty, - set_qty=set_qty, - product_custom_attribute_values=product_custom_attribute_values, - no_variant_attribute_values=no_variant_attribute_values, - # BizzAppDev Customization - config_session_id=kw.get("config_session_id", False), - # BizzAppDev Customization End - ) - if kw.get("express"): - return request.redirect("/shop/checkout?express=1") - return request.redirect("/shop/cart") diff --git a/website_product_configurator/data/config_form_templates.xml b/website_product_configurator/data/config_form_templates.xml index 9fbdacf12a..6f0076ddb7 100644 --- a/website_product_configurator/data/config_form_templates.xml +++ b/website_product_configurator/data/config_form_templates.xml @@ -18,14 +18,13 @@ - - diff --git a/website_product_configurator/data/ir_config_parameter_data.xml b/website_product_configurator/data/ir_config_parameter_data.xml index 881454bae4..81c7e80753 100644 --- a/website_product_configurator/data/ir_config_parameter_data.xml +++ b/website_product_configurator/data/ir_config_parameter_data.xml @@ -1,14 +1,9 @@ - - - - product_configurator.default_configuration_step_website_view_id - website_product_configurator.config_form_select - - + + + product_configurator.default_configuration_step_website_view_id + website_product_configurator.config_form_select + diff --git a/website_product_configurator/demo/product_template_demo.xml b/website_product_configurator/demo/product_template_demo.xml index ef3f1ee4c0..a831a656de 100644 --- a/website_product_configurator/demo/product_template_demo.xml +++ b/website_product_configurator/demo/product_template_demo.xml @@ -1,10 +1,6 @@ - - - - True - - - + + True + diff --git a/website_product_configurator/models/product_config.py b/website_product_configurator/models/product_config.py index 68aa908b49..03fbdb919c 100644 --- a/website_product_configurator/models/product_config.py +++ b/website_product_configurator/models/product_config.py @@ -21,7 +21,7 @@ class ProductConfigStepLine(models.Model): def get_website_template(self): """Return the external id of the qweb template linked to this step""" if self.website_tmpl_id: - xml_id_dict = self.website_tmpl_id.get_xml_id() + xml_id_dict = self.website_tmpl_id.get_external_id() view_id = xml_id_dict.get(self.website_tmpl_id.id) else: view_id = self.env[ @@ -38,7 +38,10 @@ def remove_inactive_config_sessions(self): days=3 ) sessions_to_remove = self.search( - [("write_date", "<", fields.Datetime.to_string(check_date))] + [ + ("write_date", "<", fields.Datetime.to_string(check_date)), + ("state", "=", "draft"), + ] ) if sessions_to_remove: sessions_to_remove.unlink() @@ -50,9 +53,6 @@ def get_config_form_website_template(self): xml_id = ICPSudo.get_param( "product_configurator.default_configuration_step_website_view_id" ) - website_tmpl_id = self.env["res.config.settings"].xml_id_to_record_id( - xml_id=xml_id - ) - if not website_tmpl_id: + if not xml_id: return default_tmpl_xml_id return xml_id diff --git a/website_product_configurator/models/res_config_settings.py b/website_product_configurator/models/res_config_settings.py index 4b4c15f82e..7452fc046e 100644 --- a/website_product_configurator/models/res_config_settings.py +++ b/website_product_configurator/models/res_config_settings.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import fields, models class ResConfigSettings(models.TransientModel): @@ -14,46 +14,5 @@ class ResConfigSettings(models.TransientModel): s.env.ref("website_product_configurator.config_form_base").id, ) ], + config_parameter="product_configurator.default_configuration_step_website_view_id", ) - - def xml_id_to_record_id(self, xml_id): - if not xml_id or len(xml_id.split(".")) != 2: - return False - - website_tmpl_id = self.env.ref(xml_id) - if website_tmpl_id.exists() and website_tmpl_id.inherit_id != self.env.ref( - "website_product_configurator.config_form_base" - ): - return False - return website_tmpl_id - - def set_values(self): - super(ResConfigSettings, self).set_values() - ICPSudo = self.env["ir.config_parameter"].sudo() - website_tmpl_xml_id = "" - if self.website_tmpl_id: - website_tmpl_xml_id = self.website_tmpl_id.xml_id - ICPSudo.set_param( - "product_configurator.default_configuration_step_website_view_id", - website_tmpl_xml_id, - ) - - @api.model - def get_values(self): - res = super(ResConfigSettings, self).get_values() - ICPSudo = self.env["ir.config_parameter"].sudo() - xml_id = ICPSudo.get_param( - "product_configurator.default_configuration_step_website_view_id" - ) - - website_tmpl_xml_id = self.xml_id_to_record_id(xml_id=xml_id) - res.update( - { - "website_tmpl_id": ( - website_tmpl_xml_id - and website_tmpl_xml_id.id - or website_tmpl_xml_id - ) - } - ) - return res diff --git a/website_product_configurator/models/sale_order.py b/website_product_configurator/models/sale_order.py index 7808d86bd1..a3550decdf 100644 --- a/website_product_configurator/models/sale_order.py +++ b/website_product_configurator/models/sale_order.py @@ -1,8 +1,6 @@ import logging -from odoo import _, models -from odoo.exceptions import UserError, ValidationError -from odoo.http import request +from odoo import models _logger = logging.getLogger(__name__) @@ -10,234 +8,79 @@ class SaleOrder(models.Model): _inherit = "sale.order" - # flake8: noqa: C901 - def _cart_update( - self, product_id=None, line_id=None, add_qty=0, set_qty=0, **kwargs - ): - """Add or set product quantity, add_qty can be negative""" + def _cart_update_order_line(self, product_id, quantity, order_line, **kwargs): + """Inherit: To update the context of sale order line.""" self.ensure_one() - product_context = dict(self.env.context) - product_context.setdefault("lang", self.sudo().partner_id.lang) - SaleOrderLineSudo = ( - self.env["sale.order.line"].sudo().with_context(product_context) - ) - # change lang to get correct name of attributes/values - product_with_context = self.env["product.product"].with_context(product_context) - product = product_with_context.browse(int(product_id)) - - if not product.product_tmpl_id.config_ok: - return super(SaleOrder, self)._cart_update( - product_id=product_id, - line_id=line_id, - add_qty=add_qty, - set_qty=set_qty, - kwargs=kwargs, - ) - - # Config session map + product_variant = self.env["product.product"].browse(product_id) + # Retrieve the config session ID from the keyword arguments. config_session_id = kwargs.get("config_session_id", False) - if not config_session_id and line_id: - order_line = self._cart_find_product_line(product_id, line_id, **kwargs)[:1] + if not config_session_id and order_line and product_variant.config_ok: + # If the config session ID is not provided and line ID is given, + # find the corresponding order line and retrieve the config + # session ID. + order_line = self._cart_find_product_line( + product_id, order_line.id, **kwargs + )[:1] config_session_id = order_line.config_session_id.id - if config_session_id: + + ctx = {} + # Convert config session ID to integer if it exists. + if config_session_id and product_variant.config_ok: config_session_id = int(config_session_id) - if not product: - config_session = self.env["product.config.session"].browse( - config_session_id - ) - product = config_session.product_id + # Set the context with config session ID and current sale line. ctx = { - "current_sale_line": line_id, + "current_sale_line": order_line.id, "default_config_session_id": config_session_id, } - self = self.with_context(ctx) - SaleOrderLineSudo = SaleOrderLineSudo.with_context(ctx) - - # Add to cart functionality - try: - if add_qty: - add_qty = float(add_qty) - except ValueError: - add_qty = 1 - try: - if set_qty: - set_qty = float(set_qty) - except ValueError: - set_qty = 0 - quantity = 0 - order_line = False - if self.state != "draft": - request.session["sale_order_id"] = None - raise UserError( - _( - "It is forbidden to modify a sales order " - "which is not in draft status." - ) - ) - if line_id is not False: - order_line = self._cart_find_product_line(product_id, line_id, **kwargs)[:1] - - # Create line if no line with product_id can be located - if not order_line: - if not product: - raise UserError( - _( - "The given product does not exist therefore " - "it cannot be added to cart." - ) - ) - - product_id = product.id - values = self._website_product_id_change(self.id, product_id, qty=1) - - # create the line - order_line = SaleOrderLineSudo.create(values) - - try: - order_line._compute_tax_id() - except ValidationError as e: - # The validation may occur in backend - # eg: taxcloud) but should fail silently in frontend - _logger.debug("ValidationError occurs during tax compute. %s" % (e)) - if add_qty: - add_qty -= 1 - - # compute new quantity - if set_qty: - quantity = set_qty - elif add_qty is not None: - quantity = order_line.product_uom_qty + (add_qty or 0) - - # Remove zero of negative lines - if quantity <= 0: - linked_line = order_line.linked_line_id - order_line.unlink() - if linked_line: - # update description of the parent - linked_product = product_with_context.browse(linked_line.product_id.id) - linked_line.name = ( - linked_line.get_sale_order_line_multiline_description_sale( - linked_product - ) - ) - else: - # update line - no_variant_attributes_price_extra = [ - ptav.price_extra - for ptav in order_line.product_no_variant_attribute_value_ids - ] - values = self.with_context( - no_variant_attributes_price_extra=tuple( - no_variant_attributes_price_extra - ) - )._website_product_id_change(self.id, product_id, qty=quantity) - if ( - self.pricelist_id.discount_policy == "with_discount" - and not self.env.context.get("fixed_price") - ): - order = self.sudo().browse(self.id) - product_context.update( - { - "partner": order.partner_id, - "quantity": quantity, - "date": order.date_order, - "pricelist": order.pricelist_id.id, - "company_id": order.company_id.id, - } - ) - product_with_context = self.env["product.product"].with_context( - product_context - ) - product = product_with_context.browse(product_id) - values["price_unit"] = self.env[ - "account.tax" - ]._fix_tax_included_price_company( - order_line._get_display_price(product), - order_line.product_id.taxes_id, - order_line.tax_id, - self.company_id, - ) - - order_line.write(values) - # link a product to the sales order - if kwargs.get("linked_line_id"): - linked_line = SaleOrderLineSudo.browse(kwargs["linked_line_id"]) - order_line.write({"linked_line_id": linked_line.id}) - linked_product = product_with_context.browse(linked_line.product_id.id) - linked_line.name = ( - linked_line.get_sale_order_line_multiline_description_sale( - linked_product - ) - ) - # Generate the description with everything. This is done after - # creating because the following related fields have to be set: - # - product_no_variant_attribute_value_ids - # - product_custom_attribute_value_ids - # - linked_line_id - order_line.name = order_line.get_sale_order_line_multiline_description_sale( - product - ) - - option_lines = self.order_line.filtered( - lambda l: l.linked_line_id.id == order_line.id + return super(SaleOrder, self.with_context(**ctx))._cart_update_order_line( + product_id, quantity=quantity, order_line=order_line, **kwargs ) - return { - "line_id": order_line.id, - "quantity": quantity, - "option_ids": list(set(option_lines.ids)), - } - def _cart_find_product_line(self, product_id=None, line_id=None, **kwargs): """Include Config session in search.""" - order_line = super(SaleOrder, self)._cart_find_product_line( + order_line = super()._cart_find_product_line( product_id=product_id, line_id=line_id, **kwargs ) - # Onchange quantity in cart - if line_id: - return order_line - + # Check if config_session_id is provided. config_session_id = kwargs.get("config_session_id", False) - if not config_session_id: + + # If a line ID is provided, return the initial product line. + if line_id or not config_session_id: return order_line + # Filter the product line based on the config_session_id. order_line = order_line.filtered( lambda p: p.config_session_id.id == int(config_session_id) ) return order_line - -class SaleOrderLine(models.Model): - _inherit = "sale.order.line" - - def create(self, vals): - res = super(SaleOrderLine, self).create(vals) - return res - - def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id): - if not product.config_ok: - return super(SaleOrderLine, self)._get_real_price_currency( - product=product, - rule_id=rule_id, - qty=qty, - uom=uom, - pricelist_id=pricelist_id, + def _prepare_order_line_values( + self, + product_id, + quantity, + linked_line_id=False, + no_variant_attribute_values=None, + product_custom_attribute_values=None, + **kwargs, + ): + """Inherit: Skip the creating product_variant based on received_combination for + the configurable products.""" + self.ensure_one() + product = self.env["product.product"].browse(product_id) + if not product.product_tmpl_id.config_ok: + return super()._prepare_order_line_values( + product_id=product_id, + quantity=quantity, + linked_line_id=linked_line_id, + no_variant_attribute_values=no_variant_attribute_values, + product_custom_attribute_values=product_custom_attribute_values, + kwargs=kwargs, ) - currency_id = None - product_currency = None - if rule_id: - PricelistItem = self.env["product.pricelist.item"] - pricelist_item = PricelistItem.browse(rule_id) - currency_id = pricelist_item.pricelist_id.currency_id - if pricelist_item.base == "pricelist" and pricelist_item.base_pricelist_id: - product_currency = pricelist_item.base_pricelist_id.currency_id - product_currency = ( - product_currency - or (product.company_id and product.company_id.currency_id) - or self.env.user.company_id.currency_id - ) - - if not currency_id or currency_id.id == product_currency.id: - currency_id = product_currency - return product.price, currency_id + values = { + "product_id": product.id, + "product_uom_qty": quantity, + "order_id": self.id, + "linked_line_id": linked_line_id, + } + return values diff --git a/website_product_configurator/security/configurator_security.xml b/website_product_configurator/security/configurator_security.xml index bff5b12b92..aadf319b8d 100644 --- a/website_product_configurator/security/configurator_security.xml +++ b/website_product_configurator/security/configurator_security.xml @@ -1,17 +1,15 @@ - - - - - - - - + + + + + + diff --git a/website_product_configurator/static/src/css/config_form.css b/website_product_configurator/static/src/css/config_form.css deleted file mode 100644 index 1117490fb8..0000000000 --- a/website_product_configurator/static/src/css/config_form.css +++ /dev/null @@ -1,151 +0,0 @@ -#cfg_header { - max-width: 90%; - min-height: 320px; -} - -.cfg_image { - max-width: 90%; - max-height: 300px; - text-align: center; - text-align: -webkit-center; - position: absolute; - top: 0; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; -} - -.config_weight { - width: 25%; - float: left; - padding: 10px 10px 10px 0px; -} -.config_price { - width: 75%; - float: left; - padding: 10px 10px 10px 0px; -} -@media all and (orientation: portrait) { - .config_weight { - width: 50%; - float: left; - padding: 10px 10px 10px 0px; - } - .config_price { - width: 50%; - float: left; - padding: 10px 10px 10px 0px; - } -} - -.spinner_qty { - padding: 0; - min-width: 48px !important; - text-align: center; -} - -.custom_config_value.spinner_qty { - max-width: 50px !important; -} - -.config_attribute input[type="radio"], -.config_attribute input[type="checkbox"] { - position: absolute; - z-index: 999; - margin: 5px auto auto 5px; - cursor: pointer; -} - -.config_attribute input:active + .image_config_attr_value_radio { - opacity: 90%; -} -.config_attribute input[type="radio"]:checked + .image_config_attr_value_radio { - -webkit-filter: none; - -moz-filter: none; - filter: none; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} - -.config_attribute input[type="checkbox"]:checked + .image_config_attr_value_radio { - -webkit-filter: none; - -moz-filter: none; - filter: none; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} - -.config_attribute input[type="radio"][disabled] + .image_config_attr_value_radio { - -webkit-filter: brightness(90%) contrast(110%) grayscale(50%) opacity(50%); - -moz-filter: brightness(90%) contrast(110%) grayscale(50%) opacity(50%); - filter: brightness(90%) contrast(110%) grayscale(50%) opacity(50%); - border: none; - border-radius: none; - box-shadow: none; - pointer-events: none; - cursor: not-allowed; -} - -.config_attribute input[type="checkbox"][disabled] + .image_config_attr_value_radio { - -webkit-filter: brightness(90%) contrast(110%) grayscale(50%) opacity(50%); - -moz-filter: brightness(90%) contrast(110%) grayscale(50%) opacity(50%); - filter: brightness(90%) contrast(110%) grayscale(50%) opacity(50%); - border: none; - border-radius: none; - box-shadow: none; - pointer-events: none; - cursor: not-allowed; -} - -.image_config_attr_value_radio { - cursor: pointer; - background-size: contain; - background-repeat: no-repeat; - display: inline-block; - width: 160px; - height: 160px; - -webkit-transition: all 100ms ease-in; - -moz-transition: all 100ms ease-in; - transition: all 100ms ease-in; - -webkit-filter: brightness(90%) contrast(110%) grayscale(50%) opacity(90%); - -moz-filter: brightness(90%) contrast(110%) grayscale(50%) opacity(90%); - filter: brightness(90%) contrast(110%) grayscale(50%) opacity(90%); -} -.image_config_attr_value_radio:hover { - -webkit-filter: none; - -moz-filter: none; - filter: none; - border: solid silver 1px; - border-radius: 5px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); -} - -.label_config_attr_value_radio { - position: absolute; - z-index: 1; - width: 138px; - margin: 5px auto auto 25px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: default; -} - -.radio-card-container { - margin: 5px 10px 5px 10px; - float: left; -} -.control-label { - margin-top: 15px; - margin-bottom: auto; -} - -#cfg_footer { - padding-top: 15px; -} - -.label_config_price_extra_radio { - position: absolute; - z-index: 1; - width: 138px; - margin: 25px auto auto 5px; -} diff --git a/website_product_configurator/static/src/css/tooltip.css b/website_product_configurator/static/src/css/tooltip.css deleted file mode 100644 index 93d0b44c11..0000000000 --- a/website_product_configurator/static/src/css/tooltip.css +++ /dev/null @@ -1,8 +0,0 @@ -.tooltip-inner { - max-width: 250px; - padding: 8px 12px; -} - -.tooltip { - font-weight: bolder; -} diff --git a/website_product_configurator/static/src/js/config_form.esm.js b/website_product_configurator/static/src/js/config_form.esm.js new file mode 100644 index 0000000000..81517250f1 --- /dev/null +++ b/website_product_configurator/static/src/js/config_form.esm.js @@ -0,0 +1,629 @@ +/** @odoo-module **/ + +import Dialog from "@web/legacy/js/core/dialog"; +import {insertThousandsSep} from "@web/core/utils/numbers"; +import {jsonrpc} from "@web/core/network/rpc_service"; +import {localization} from "@web/core/l10n/localization"; +import publicWidget from "@web/legacy/js/public/public_widget"; + +publicWidget.registry.ProductConfigurator = publicWidget.Widget.extend({ + selector: ".product_configurator", + events: { + "change.datetimepicker #product_config_form .input-group.date": + "_onChangeDateTime", + "change #product_config_form .config_attribute": "_onChangeConfigAttribute", + "change #product_config_form .custom_config_value.config_attachment": + "_onChangeFile", + "change #product_config_form .custom_config_value": "_onChangeCustomField", + "click #product_config_form .config_step": "_onClickConfigStep", + "click #product_config_form .btnNextStep": "_onClickBtnNext", + "click #product_config_form .btnPreviousStep": "_onClickBtnPrevious", + "submit #product_config_form": "_onSubmitConfigForm", + "click #product_config_form .image_config_attr_value_radio": + "_onClickRadioImage", + "change #product_config_form .custom_config_value.spinner_qty": + "_onChangeQtySpinner", + "click #product_config_form .js_add_qty": "_onClickAddQty", + "click #product_config_form .js_remove_qty": "_onClickRemoveQty", + }, + + init: function () { + this._super.apply(this, arguments); + this.config_form = $("#product_config_form"); + // For file (custom field) + this.image_dict = {}; + }, + + _onChangeConfigAttribute: function (event) { + var self = this; + var attribute = [event.currentTarget]; + self._checkRequiredFields(attribute); + var flag = self._checkChange(self); + if (flag) { + var form_data = self.config_form.serializeArray(); + for (var field_name in self.image_dict) { + form_data.push({ + name: field_name, + value: self.image_dict[field_name], + }); + } + + self.call("ui", "block"); + jsonrpc("/website_product_configurator/onchange", { + form_values: form_data, + field_name: $(attribute[0]).getAttributes().name, + }).then(function (data) { + if (data.error) { + self.openWarningDialog(data.error); + } else { + var values = data.value; + var domains = data.domain; + + var open_cfg_step_line_ids = data.open_cfg_step_line_ids; + var config_image_vals = data.config_image_vals; + + self._applyDomainOnValues(domains); + self._setDataOldValId(); + self._handleOpenSteps(open_cfg_step_line_ids); + self._setImageUrl(config_image_vals); + self._setWeightPrice( + values.weight, + values.price, + data.decimal_precision + ); + } + self.call("ui", "unblock"); + }); + self._handleCustomAttribute(event); + } + }, + + _checkChange: function (attr_field) { + var flag = true; + if ($(attr_field).hasClass("cfg-radio")) { + flag = !( + $(attr_field).attr("data-old-val-id") === + $(attr_field).find("input:checked").val() + ); + } else if ($(attr_field).hasClass("cfg-select")) { + flag = !($(attr_field).attr("data-old-val-id") === $(attr_field).val()); + } + return flag; + }, + + openWarningDialog: function (message) { + var self = this; + new Dialog(self.config_form, { + title: "Warning!!!", + size: "medium", + $content: "
" + message + "
", + }).open(); + }, + + _applyDomainOnValues: function (domains) { + var self = this; + Object.entries(domains).forEach(function ([attr_id, domain]) { + var $selection = self.config_form.find("#" + attr_id); + var $options = $selection.find(".config_attr_value"); + $options.each(function (index, option) { + var condition = domain[0][1]; + if (condition === "in" || condition === "=") { + if ($.inArray(parseInt(option.value, 10), domain[0][2]) < 0) { + $(option).attr("disabled", true); + if ($(option).parent().parent().find("label")) { + $(option) + .parent() + .parent() + .find("label") + .attr("enabled", "False"); + } + if (option.selected) { + option.selected = false; + } else if (option.checked) { + option.checked = false; + } + } else { + $(option).attr("disabled", false); + if ($(option).parent().parent().find("label")) { + $(option) + .parent() + .parent() + .find("label") + .attr("enabled", "True"); + } + } + } else if (condition === "not in" || condition === "!=") { + if ($.inArray(parseInt(option.value, 10), domain[0][2]) < 0) { + $(option).attr("disabled", false); + if ($(option).parent().parent().find("label")) { + $(option) + .parent() + .parent() + .find("label") + .attr("enabled", "True"); + } + } else { + $(option).attr("disabled", true); + if ($(option).parent().parent().find("label")) { + $(option) + .parent() + .parent() + .find("label") + .attr("enabled", "False"); + } + if (option.selected) { + option.selected = false; + } else if (option.checked) { + option.checked = false; + } + } + } + }); + if ( + !domain[0][2].length && + $selection.attr("data-attr-required") && + $selection.hasClass("required_config_attrib") + ) { + $selection.removeClass("required_config_attrib"); + $selection.removeClass("textbox-border-color"); + } else if ( + domain[0][2].length && + !$selection.hasClass("required_config_attrib") && + $selection.attr("data-attr-required") + ) { + $selection.addClass("required_config_attrib"); + } + }); + }, + + _setDataOldValId: function () { + var selections = $(".cfg-select.config_attribute"); + Array.prototype.forEach.call(selections, function (select) { + $(select).attr("data-old-val-id", $(select).val()); + }); + var fieldsets = $(".cfg-radio.config_attribute"); + fieldsets.each(function () { + var $fieldset = $(this); + $fieldset.attr( + "data-old-val-id", + $fieldset.find("input:checked").val() || "" + ); + }); + }, + + _handleOpenSteps: function (open_cfg_step_line_ids) { + var self = this; + var $steps = self.config_form.find(".config_step"); + for (var i = 0; i < $steps.length; i++) { + var config_step = $($steps[i]); + var step_id = config_step.attr("data-step-id"); + if ($.inArray(step_id, open_cfg_step_line_ids) < 0) { + if (!config_step.hasClass("d-none")) { + config_step.addClass("d-none"); + } + } else if (config_step.hasClass("d-none")) { + config_step.removeClass("d-none"); + } + } + }, + + _setImageUrl: function (config_image_vals) { + var images = ""; + if (config_image_vals) { + var model = config_image_vals.name; + config_image_vals.config_image_ids.forEach(function (line) { + images += + ""; + }); + } + if (images) { + $("#product_config_image").html(images); + } + }, + + price_to_str: function (price, precision) { + var formatted = price.toFixed(precision).split("."); + const {thousandsSep, decimalPoint, grouping} = localization; + formatted[0] = insertThousandsSep(formatted[0], thousandsSep, grouping); + return formatted.join(decimalPoint); + }, + + weight_to_str: function (weight, precision) { + var formatted = weight.toFixed(precision).split("."); + const {thousandsSep, decimalPoint, grouping} = localization; + formatted[0] = insertThousandsSep(formatted[0], thousandsSep, grouping); + return formatted.join(decimalPoint); + }, + + _setWeightPrice: function (weight, price, decimal_precisions) { + var self = this; + var formatted_price = self.price_to_str(price, decimal_precisions.price); + var formatted_weight = self.weight_to_str(weight, decimal_precisions.weight); + $(".config_product_weight").text(formatted_weight); + $(".config_product_price").find(".oe_currency_value").text(formatted_price); + }, + + _handleCustomAttribute: function (event) { + var container = $(event.currentTarget).closest(".tab-pane.container"); + var attribute_id = $(event.currentTarget).attr("data-oe-id"); + var custom_value = container.find( + ".custom_config_value[data-oe-id=" + attribute_id + "]" + ); + var custom_value_container = custom_value.closest( + ".custom_field_container[data-oe-id=" + attribute_id + "]" + ); + var attr_field = container.find( + ".config_attribute[data-oe-id=" + attribute_id + "]" + ); + var custom_config_attr = attr_field.find(".custom_config_attr_value"); + var flag_custom = false; + if ( + custom_config_attr.length && + custom_config_attr[0].tagName === "OPTION" && + custom_config_attr[0].selected + ) { + flag_custom = true; + } else if ( + custom_config_attr.length && + custom_config_attr[0].tagName === "INPUT" && + custom_config_attr[0].checked + ) { + flag_custom = true; + } + if (flag_custom && custom_value_container.hasClass("d-none")) { + custom_value_container.removeClass("d-none"); + custom_value.addClass("required_config_attrib"); + } else if (!flag_custom && !custom_value_container.hasClass("d-none")) { + custom_value_container.addClass("d-none"); + if (custom_value.hasClass("required_config_attrib")) { + custom_value.removeClass("required_config_attrib"); + } + } + }, + + _onChangeDateTime: function (event) { + var self = this; + var attribute = $(event.currentTarget).find("input.required_config_attrib"); + self._checkRequiredFields(attribute); + }, + + _checkRequiredFields: function (config_attr) { + var self = this; + var flag_all = true; + for (var i = 0; i < config_attr.length; i++) { + var flag = true; + if (!$(config_attr[i]).hasClass("required_config_attrib")) { + flag = true; + } else if ($(config_attr[i]).hasClass("cfg-radio")) { + flag = self._checkRequiredFieldsRadio($(config_attr[i])); + } else if (!config_attr[i].value.trim() || config_attr[i].value === "0") { + flag = false; + } + if (!flag) { + $(config_attr[i]).addClass("textbox-border-color"); + } else if (flag && $(config_attr[i]).hasClass("textbox-border-color")) { + $(config_attr[i]).removeClass("textbox-border-color"); + } + flag_all &= flag; + } + return flag_all; + }, + + _checkRequiredFieldsRadio: function (parent_container) { + var radio_inputs = parent_container.find(".config_attr_value:checked"); + if (radio_inputs.length) { + return true; + } + return false; + }, + + _onChangeFile: function (ev) { + var self = this; + var result = $.Deferred(); + var file = ev.target.files[0]; + if (!file) { + return true; + } + var files_data = ""; + var BinaryReader = new FileReader(); + // File read as DataURL + BinaryReader.readAsDataURL(file); + BinaryReader.onloadend = function (upload) { + var buffer = upload.target.result; + buffer = buffer.split(",")[1]; + files_data = buffer; + self.image_dict[ev.target.name] = files_data; + result.resolve(); + }; + return result.promise(); + }, + + _onChangeCustomField: function (event) { + var self = this; + var attribute = [event.currentTarget]; + self._checkRequiredFields(attribute); + }, + + _onClickConfigStep: function (event) { + var self = this; + var next_step = event.currentTarget.getAttribute("data-step-id"); + var result = self._onChangeConfigStep(event, next_step); + if (result) { + self._handleFooterButtons($(event.currentTarget)); + } else { + event.preventDefault(); + event.stopPropagation(); + } + }, + + _onChangeConfigStep: function (event, next_step) { + var self = this; + var active_step = self.config_form + .find(".tab-content") + .find(".tab-pane.active.show"); + + var config_attr = active_step.find(".form-control.required_config_attrib"); + var flag = self._checkRequiredFields(config_attr); + var config_step_header = self.config_form.find(".nav.nav-tabs"); + var current_config_step = config_step_header + .find(".nav-item.config_step > a.active") + .parent() + .attr("data-step-id"); + var form_data = self.config_form.serializeArray(); + for (var field_name in self.image_dict) { + form_data.push({name: field_name, value: self.image_dict[field_name]}); + } + if (flag) { + self.call("ui", "block"); + return jsonrpc("/website_product_configurator/save_configuration", { + form_values: form_data, + next_step: next_step || false, + current_step: current_config_step || false, + submit_configuration: event.type === "submit", + }).then(function (data) { + if (data.error) { + self.openWarningDialog(data.error); + } + self.call("ui", "unblock"); + return data; + }); + } + return false; + }, + + _handleFooterButtons: function (step) { + var step_count = step.attr("data-step-count"); + var total_steps = $("#total_attributes").val(); + if (step_count === "1") { + $(".btnPreviousStep").addClass("d-none"); + $(".btnNextStep").removeClass("d-none"); + $(".configureProduct").addClass("d-none"); + } else if (step_count === total_steps) { + $(".btnPreviousStep").removeClass("d-none"); + $(".btnNextStep").addClass("d-none"); + $(".configureProduct").removeClass("d-none"); + } else { + $(".btnPreviousStep").removeClass("d-none"); + $(".btnNextStep").removeClass("d-none"); + $(".configureProduct").addClass("d-none"); + } + }, + + _onClickBtnNext: function () { + var active_step = this.config_form + .find(".tab-content") + .find(".tab-pane.active.show"); + + var config_attr = active_step.find(".form-control.required_config_attrib"); + var flag = this._checkRequiredFields(config_attr); + var nextTab = $(".nav-tabs > .config_step > .active") + .parent() + .nextAll("li:not(.d-none):first") + .find("a") + .trigger("click"); + if (flag) { + nextTab.tab("show"); + } + }, + + _onClickBtnPrevious: function () { + var active_step = this.config_form + .find(".tab-content") + .find(".tab-pane.active.show"); + + var config_attr = active_step.find(".form-control.required_config_attrib"); + var flag = this._checkRequiredFields(config_attr); + var previousTab = $(".nav-tabs > .config_step > .active") + .parent() + .prevAll("li:not(.d-none):first") + .find("a") + .trigger("click"); + if (flag) { + previousTab.tab("show"); + } + }, + + _onSubmitConfigForm: function (event) { + var self = this; + event.preventDefault(); + event.stopPropagation(); + + var result = self._onChangeConfigStep(event, false); + if (result) { + result.then(function (data) { + if (data) { + if (data.next_step) { + self._openNextStep(data.next_step); + } + if (data.redirect_url) { + window.location = data.redirect_url; + } + } + }); + } + }, + + _openNextStep: function (step) { + var self = this; + var config_step_header = self.config_form.find(".nav.nav-tabs"); + var config_step = config_step_header.find( + ".nav-item.config_step > .nav-link.active" + ); + if (config_step.length) { + config_step.removeClass("active"); + } + var active_step = self.config_form + .find(".tab-content") + .find(".tab-pane.active.show"); + active_step.removeClass("active"); + active_step.removeClass("show"); + + var next_step = config_step_header.find( + ".nav-item.config_step[data-step-id=" + step + "] > .nav-link" + ); + if (next_step.length) { + next_step.addClass("active"); + var selector = next_step.attr("href"); + var step_to_active = self.config_form.find(".tab-content").find(selector); + step_to_active.addClass("active"); + step_to_active.addClass("show"); + } + }, + + _onClickRadioImage: function (event) { + var val_id = $(event.currentTarget).data("val-id"); + var value_input = $(event.currentTarget) + .closest(".cfg-radio") + .find('.config_attr_value[data-oe-id="' + val_id + '"]'); + if (value_input.prop("disabled")) { + return; + } + if (value_input.length) { + if ( + value_input.attr("type") === "checkbox" && + value_input.prop("checked") + ) { + value_input.prop("checked", false); + } else { + value_input.prop("checked", "checked"); + } + value_input.change(); + } + }, + + _onChangeQtySpinner: function (ev) { + this._handleSppinerCustomValue(ev); + }, + + _onClickAddQty: function (ev) { + var custom_value = this._handleSppinerCustomValue(ev); + this._checkRequiredFields(custom_value); + }, + + _onClickRemoveQty: function (ev) { + var custom_value = this._handleSppinerCustomValue(ev); + this._checkRequiredFields(custom_value); + }, + + _handleSppinerCustomValue: function (ev) { + var self = this; + ev.preventDefault(); + ev.stopPropagation(); + + var current_target = $(ev.currentTarget); + var custom_value = current_target + .closest(".input-group") + .find("input.custom_config_value"); + var max_val = parseFloat(custom_value.attr("max") || Infinity); + var min_val = parseFloat(custom_value.attr("min") || 0); + var new_qty = min_val; + var ui_val = parseFloat(custom_value.val()); + var custom_type = custom_value.attr("data-type"); + var message = ""; + var attribute_name = custom_value; + if (isNaN(ui_val)) { + message = "Please enter a number."; + self._displayTooltip(custom_value, message); + } else if (custom_type === "int" && ui_val % 1 !== 0) { + message = "Please enter a Integer."; + self._displayTooltip(custom_value, message); + } else { + var quantity = ui_val || 0; + new_qty = quantity; + if (current_target.has(".fa-minus").length) { + new_qty = quantity - 1; + } else if (current_target.has(".fa-plus").length) { + new_qty = quantity + 1; + } + if (new_qty > max_val) { + attribute_name = custom_value + .closest(".tab-pane") + .find( + 'label[data-oe-id="' + custom_value.attr("data-oe-id") + '"]' + ); + message = + "Selected custom value " + + attribute_name.text() + + " must not be greater than " + + max_val; + self._displayTooltip(custom_value, message); + new_qty = max_val; + } else if (new_qty < min_val) { + attribute_name = custom_value + .closest(".tab-pane") + .find( + 'label[data-oe-id="' + custom_value.attr("data-oe-id") + '"]' + ); + message = + "Selected custom value " + + attribute_name.text() + + " must be at least " + + min_val; + self._displayTooltip(custom_value, message); + new_qty = min_val; + } + } + custom_value.val(new_qty); + self._disableEnableAddRemoveQtyButton( + current_target, + new_qty, + max_val, + min_val + ); + return custom_value; + }, + + _displayTooltip: function (config_attribute, message) { + $(config_attribute) + .tooltip({ + title: message, + placement: "bottom", + trigger: "manual", + }) + .tooltip("show"); + setTimeout(function () { + $(config_attribute).tooltip("dispose"); + }, 4000); + }, + + _disableEnableAddRemoveQtyButton: function ( + current_target, + quantity, + max_val, + min_val + ) { + var container = current_target.closest(".custom_field_container"); + if (quantity >= max_val) { + container.find(".js_add_qty").addClass("btn-disabled"); + } else if (quantity < max_val && $(".js_add_qty").hasClass("btn-disabled")) { + container.find(".js_add_qty").removeClass("btn-disabled"); + } + if (quantity <= min_val) { + container.find(".js_remove_qty").addClass("btn-disabled"); + } else if (quantity > min_val && $(".js_remove_qty").hasClass("btn-disabled")) { + container.find(".js_remove_qty").removeClass("btn-disabled"); + } + }, +}); +export default publicWidget.registry.ProductConfigurator; diff --git a/website_product_configurator/static/src/js/config_form.js b/website_product_configurator/static/src/js/config_form.js deleted file mode 100644 index cfc9768363..0000000000 --- a/website_product_configurator/static/src/js/config_form.js +++ /dev/null @@ -1,704 +0,0 @@ -odoo.define("website_product_configurator.config_form", function (require) { - "use strict"; - - var ajax = require("web.ajax"); - var time = require("web.time"); - var utils = require("web.utils"); - var core = require("web.core"); - var Dialog = require("web.Dialog"); - var publicWidget = require("web.public.widget"); - var _t = core._t; - - publicWidget.registry.ProductConfigurator = publicWidget.Widget.extend({ - selector: ".product_configurator", - events: { - "change.datetimepicker #product_config_form .input-group.date": - "_onChangeDateTime", - "change #product_config_form .config_attribute": "_onChangeConfigAttribute", - "change #product_config_form .custom_config_value.config_attachment": - "_onChangeFile", - "change #product_config_form .custom_config_value": "_onChangeCustomField", - "click #product_config_form .config_step": "_onClickConfigStep", - "click #product_config_form .btnNextStep": "_onClickBtnNext", - "click #product_config_form .btnPreviousStep": "_onClickBtnPrevious", - "submit #product_config_form": "_onSubmitConfigForm", - "click #product_config_form .image_config_attr_value_radio": - "_onClickRadioImage", - "change #product_config_form .custom_config_value.spinner_qty": - "_onChangeQtySpinner", - "click #product_config_form .js_add_qty": "_onClickAddQty", - "click #product_config_form .js_remove_qty": "_onClickRemoveQty", - }, - - init: function () { - this._super.apply(this, arguments); - // Datetime picker (for custom field) - if (!$.fn.datetimepicker) { - ajax.loadJS("/web/static/lib/tempusdominus/tempusdominus.js"); - } - this.config_form = $("#product_config_form"); - this.datetimepickers_options = { - calendarWeeks: true, - icons: { - time: "fa fa-clock-o", - date: "fa fa-calendar", - up: "fa fa-chevron-up", - down: "fa fa-chevron-down", - previous: "fa fa-chevron-left", - next: "fa fa-chevron-right", - today: "fa fa-calendar-check-o", - clear: "fa fa-delete", - close: "fa fa-times", - }, - locale: moment.locale(), - allowInputToggle: true, - buttons: { - showToday: true, - showClose: true, - }, - format: time.getLangDatetimeFormat(), - keyBinds: null, - }; - this.datepickers_options = $.extend({}, this.datetimepickers_options, { - format: time.getLangDateFormat(), - }); - - // For file (custom field) - this.image_dict = {}; - - // Block UI - this.blockui_opts = $.blockUI.defaults; - this.blockui_opts.baseZ = 2147483647; - this.blockui_opts.css.border = "0"; - this.blockui_opts.css["background-color"] = ""; - this.blockui_opts.overlayCSS.opacity = "0.5"; - this.blockui_opts.message = - '


'; - }, - - start: function () { - var def = this._super.apply(this, arguments); - this.$(".product_config_datetimepicker") - .parent() - .datetimepicker(this.datetimepickers_options); - this.$(".product_config_datepicker") - .parent() - .datetimepicker(this.datepickers_options); - return def; - }, - - _onChangeConfigAttribute: function (event) { - var self = this; - var attribute = [event.currentTarget]; - self._checkRequiredFields(attribute); - var flag = self._checkChange(self); - if (flag) { - var form_data = self.config_form.serializeArray(); - for (var field_name in self.image_dict) { - form_data.push({ - name: field_name, - value: self.image_dict[field_name], - }); - } - $.blockUI(self.blockui_opts); - ajax.jsonRpc("/website_product_configurator/onchange", "call", { - form_values: form_data, - field_name: $(attribute[0]).getAttributes().name, - }).then(function (data) { - if (data.error) { - self.openWarningDialog(data.error); - } else { - var values = data.value; - var domains = data.domain; - - var open_cfg_step_line_ids = data.open_cfg_step_line_ids; - var config_image_vals = data.config_image_vals; - - self._applyDomainOnValues(domains); - self._setDataOldValId(); - self._handleOpenSteps(open_cfg_step_line_ids); - self._setImageUrl(config_image_vals); - self._setWeightPrice( - values.weight, - values.price, - data.decimal_precision - ); - } - if ($.blockUI) { - $.unblockUI(); - } - }); - self._handleCustomAttribute(event); - } - }, - - _checkChange: function (attr_field) { - var flag = true; - if ($(attr_field).hasClass("cfg-radio")) { - flag = !( - $(attr_field).attr("data-old-val-id") === - $(attr_field).find("input:checked").val() - ); - } else if ($(attr_field).hasClass("cfg-select")) { - flag = !($(attr_field).attr("data-old-val-id") === $(attr_field).val()); - } - return flag; - }, - - openWarningDialog: function (message) { - var self = this; - new Dialog(self.config_form, { - title: "Warning!!!", - size: "medium", - $content: "
" + message + "
", - }).open(); - }, - - _applyDomainOnValues: function (domains) { - var self = this; - _.each(domains, function (domain, attr_id) { - var $selection = self.config_form.find("#" + attr_id); - var $options = $selection.find(".config_attr_value"); - _.each($options, function (option) { - var condition = domain[0][1]; - if (condition === "in" || condition === "=") { - if ($.inArray(parseInt(option.value, 10), domain[0][2]) < 0) { - $(option).attr("disabled", true); - if ($(option).parent().parent().find("label")) { - $(option) - .parent() - .parent() - .find("label") - .attr("enabled", "False"); - } - if (option.selected) { - option.selected = false; - } else if (option.checked) { - option.checked = false; - } - } else { - $(option).attr("disabled", false); - if ($(option).parent().parent().find("label")) { - $(option) - .parent() - .parent() - .find("label") - .attr("enabled", "True"); - } - } - } else if (condition === "not in" || condition === "!=") { - if ($.inArray(parseInt(option.value, 10), domain[0][2]) < 0) { - $(option).attr("disabled", false); - if ($(option).parent().parent().find("label")) { - $(option) - .parent() - .parent() - .find("label") - .attr("enabled", "True"); - } - } else { - $(option).attr("disabled", true); - if ($(option).parent().parent().find("label")) { - $(option) - .parent() - .parent() - .find("label") - .attr("enabled", "False"); - } - if (option.selected) { - option.selected = false; - } else if (option.checked) { - option.checked = false; - } - } - } - }); - if ( - !domain[0][2].length && - $selection.attr("data-attr-required") && - $selection.hasClass("required_config_attrib") - ) { - $selection.removeClass("required_config_attrib"); - $selection.removeClass("textbox-border-color"); - } else if ( - domain[0][2].length && - !$selection.hasClass("required_config_attrib") && - $selection.attr("data-attr-required") - ) { - $selection.addClass("required_config_attrib"); - } - }); - }, - - _setDataOldValId: function () { - var selections = $(".cfg-select.config_attribute"); - _.each(selections, function (select) { - $(select).attr("data-old-val-id", $(select).val()); - }); - var fieldsets = $(".cfg-radio.config_attribute"); - _.each(fieldsets, function (fieldset) { - $(fieldset).attr( - "data-old-val-id", - $(fieldset).find("input:checked").val() || "" - ); - }); - }, - - _handleOpenSteps: function (open_cfg_step_line_ids) { - var self = this; - var $steps = self.config_form.find(".config_step"); - _.each($steps, function (step) { - var config_step = $(step); - var step_id = config_step.attr("data-step-id"); - if ($.inArray(step_id, open_cfg_step_line_ids) < 0) { - if (!config_step.hasClass("d-none")) { - config_step.addClass("d-none"); - } - } else if (config_step.hasClass("d-none")) { - config_step.removeClass("d-none"); - } - }); - }, - - _setImageUrl: function (config_image_vals) { - var images = ""; - if (config_image_vals) { - var model = config_image_vals.name; - config_image_vals.config_image_ids.forEach(function (line) { - images += - ""; - }); - } - if (images) { - $("#product_config_image").html(images); - } - }, - - price_to_str: function (price, precision) { - var l10n = _t.database.parameters; - var formatted = _.str.sprintf("%." + precision + "f", price).split("."); - formatted[0] = utils.insert_thousand_seps(formatted[0]); - return formatted.join(l10n.decimal_point); - }, - - weight_to_str: function (weight, precision) { - var l10n = _t.database.parameters; - var formatted = _.str.sprintf("%." + precision + "f", weight).split("."); - formatted[0] = utils.insert_thousand_seps(formatted[0]); - return formatted.join(l10n.decimal_point); - }, - - _setWeightPrice: function (weight, price, decimal_precisions) { - var self = this; - var formatted_price = self.price_to_str(price, decimal_precisions.price); - var formatted_weight = self.weight_to_str( - weight, - decimal_precisions.weight - ); - $(".config_product_weight").text(formatted_weight); - $(".config_product_price").find(".oe_currency_value").text(formatted_price); - }, - - _handleCustomAttribute: function (event) { - var container = $(event.currentTarget).closest(".tab-pane.container"); - var attribute_id = $(event.currentTarget).attr("data-oe-id"); - var custom_value = container.find( - ".custom_config_value[data-oe-id=" + attribute_id + "]" - ); - var custom_value_container = custom_value.closest( - ".custom_field_container[data-oe-id=" + attribute_id + "]" - ); - var attr_field = container.find( - ".config_attribute[data-oe-id=" + attribute_id + "]" - ); - var custom_config_attr = attr_field.find(".custom_config_attr_value"); - var flag_custom = false; - if ( - custom_config_attr.length && - custom_config_attr[0].tagName === "OPTION" && - custom_config_attr[0].selected - ) { - flag_custom = true; - } else if ( - custom_config_attr.length && - custom_config_attr[0].tagName === "INPUT" && - custom_config_attr[0].checked - ) { - flag_custom = true; - } - if (flag_custom && custom_value_container.hasClass("d-none")) { - custom_value_container.removeClass("d-none"); - custom_value.addClass("required_config_attrib"); - } else if (!flag_custom && !custom_value_container.hasClass("d-none")) { - custom_value_container.addClass("d-none"); - if (custom_value.hasClass("required_config_attrib")) { - custom_value.removeClass("required_config_attrib"); - } - } - }, - - _onChangeDateTime: function (event) { - var self = this; - var attribute = $(event.currentTarget).find("input.required_config_attrib"); - self._checkRequiredFields(attribute); - }, - - _checkRequiredFields: function (config_attr) { - var self = this; - var flag_all = true; - for (var i = 0; i < config_attr.length; i++) { - var flag = true; - if (!$(config_attr[i]).hasClass("required_config_attrib")) { - flag = true; - } else if ($(config_attr[i]).hasClass("cfg-radio")) { - flag = self._checkRequiredFieldsRadio($(config_attr[i])); - } else if ( - !config_attr[i].value.trim() || - config_attr[i].value === "0" - ) { - flag = false; - } - if (!flag) { - $(config_attr[i]).addClass("textbox-border-color"); - } else if (flag && $(config_attr[i]).hasClass("textbox-border-color")) { - $(config_attr[i]).removeClass("textbox-border-color"); - } - flag_all &= flag; - } - return flag_all; - }, - - _checkRequiredFieldsRadio: function (parent_container) { - var radio_inputs = parent_container.find(".config_attr_value:checked"); - if (radio_inputs.length) { - return true; - } - return false; - }, - - _onChangeFile: function (ev) { - var self = this; - var result = $.Deferred(); - var file = ev.target.files[0]; - if (!file) { - return true; - } - var files_data = ""; - var BinaryReader = new FileReader(); - // File read as DataURL - BinaryReader.readAsDataURL(file); - BinaryReader.onloadend = function (upload) { - var buffer = upload.target.result; - buffer = buffer.split(",")[1]; - files_data = buffer; - self.image_dict[ev.target.name] = files_data; - result.resolve(); - }; - return result.promise(); - }, - - _onChangeCustomField: function (event) { - var self = this; - var attribute = [event.currentTarget]; - self._checkRequiredFields(attribute); - }, - - _onClickConfigStep: function (event) { - var self = this; - var next_step = event.currentTarget.getAttribute("data-step-id"); - var result = self._onChangeConfigStep(event, next_step); - if (result) { - self._handleFooterButtons($(event.currentTarget)); - } else { - event.preventDefault(); - event.stopPropagation(); - } - }, - - _onChangeConfigStep: function (event, next_step) { - var self = this; - var active_step = self.config_form - .find(".tab-content") - .find(".tab-pane.active.show"); - var config_attr = active_step.find(".form-control.required_config_attrib"); - var flag = self._checkRequiredFields(config_attr); - var config_step_header = self.config_form.find(".nav.nav-tabs"); - var current_config_step = config_step_header - .find(".nav-item.config_step > a.active") - .parent() - .attr("data-step-id"); - var form_data = self.config_form.serializeArray(); - for (var field_name in self.image_dict) { - form_data.push({name: field_name, value: self.image_dict[field_name]}); - } - if (flag) { - $.blockUI(self.blockui_opts); - return ajax - .jsonRpc( - "/website_product_configurator/save_configuration", - "call", - { - form_values: form_data, - next_step: next_step || false, - current_step: current_config_step || false, - submit_configuration: event.type === "submit", - } - ) - .then(function (data) { - if (data.error) { - self.openWarningDialog(data.error); - } - if ($.blockUI) { - $.unblockUI(); - } - - return data; - }); - } - return false; - }, - - _handleFooterButtons: function (step) { - var step_count = step.attr("data-step-count"); - var total_steps = $("#total_attributes").val(); - if (step_count === "1") { - $(".btnPreviousStep").addClass("d-none"); - $(".btnNextStep").removeClass("d-none"); - $(".configureProduct").addClass("d-none"); - } else if (step_count === total_steps) { - $(".btnPreviousStep").removeClass("d-none"); - $(".btnNextStep").addClass("d-none"); - $(".configureProduct").removeClass("d-none"); - } else { - $(".btnPreviousStep").removeClass("d-none"); - $(".btnNextStep").removeClass("d-none"); - $(".configureProduct").addClass("d-none"); - } - }, - - _onClickBtnNext: function () { - $(".nav-tabs > .config_step > .active") - .parent() - .nextAll("li:not(.d-none):first") - .find("a") - .trigger("click"); - }, - - _onClickBtnPrevious: function () { - $(".nav-tabs > .config_step > .active") - .parent() - .prevAll("li:not(.d-none):first") - .find("a") - .trigger("click"); - }, - - _onSubmitConfigForm: function (event) { - var self = this; - event.preventDefault(); - event.stopPropagation(); - - var result = self._onChangeConfigStep(event, false); - if (result) { - result.then(function (data) { - if (data) { - if (data.next_step) { - self._openNextStep(data.next_step); - } - if (data.redirect_url) { - window.location = data.redirect_url; - } - } - }); - } - }, - - _openNextStep: function (step) { - var self = this; - var config_step_header = self.config_form.find(".nav.nav-tabs"); - var config_step = config_step_header.find( - ".nav-item.config_step > .nav-link.active" - ); - if (config_step.length) { - config_step.removeClass("active"); - } - var active_step = self.config_form - .find(".tab-content") - .find(".tab-pane.active.show"); - active_step.removeClass("active"); - active_step.removeClass("show"); - - var next_step = config_step_header.find( - ".nav-item.config_step[data-step-id=" + step + "] > .nav-link" - ); - if (next_step.length) { - next_step.addClass("active"); - var selector = next_step.attr("href"); - var step_to_active = self.config_form - .find(".tab-content") - .find(selector); - step_to_active.addClass("active"); - step_to_active.addClass("show"); - } - }, - - _onClickRadioImage: function (event) { - var val_id = $(event.currentTarget).data("val-id"); - var value_input = $(event.currentTarget) - .closest(".cfg-radio") - .find('.config_attr_value[data-oe-id="' + val_id + '"]'); - if (value_input.prop("disabled")) { - return; - } - if (value_input.length) { - if ( - value_input.attr("type") === "checkbox" && - value_input.prop("checked") - ) { - value_input.prop("checked", false); - } else { - value_input.prop("checked", "checked"); - } - value_input.change(); - } - }, - - addRequiredAttr: function (config_step) { - this.config_form - .find(".tab-content") - .find("tab-pane container[data-step-id=" + config_step + "]"); - _.each( - config_step.find(".form-control.config_attribute"), - function (attribute_field) { - $(attribute_field).attr("required", true); - } - ); - }, - - _onChangeQtySpinner: function (ev) { - this._handleSppinerCustomValue(ev); - }, - - _onClickAddQty: function (ev) { - var custom_value = this._handleSppinerCustomValue(ev); - this._checkRequiredFields(custom_value); - }, - - _onClickRemoveQty: function (ev) { - var custom_value = this._handleSppinerCustomValue(ev); - this._checkRequiredFields(custom_value); - }, - - _handleSppinerCustomValue: function (ev) { - var self = this; - ev.preventDefault(); - ev.stopPropagation(); - - var current_target = $(ev.currentTarget); - var custom_value = current_target - .closest(".input-group") - .find("input.custom_config_value"); - var max_val = parseFloat(custom_value.attr("max") || Infinity); - var min_val = parseFloat(custom_value.attr("min") || 0); - var new_qty = min_val; - var ui_val = parseFloat(custom_value.val()); - var custom_type = custom_value.attr("data-type"); - var message = ""; - var attribute_name = custom_value; - if (isNaN(ui_val)) { - message = "Please enter a number."; - self._displayTooltip(custom_value, message); - } else if (custom_type === "int" && ui_val % 1 !== 0) { - message = "Please enter a Integer."; - self._displayTooltip(custom_value, message); - } else { - var quantity = ui_val || 0; - new_qty = quantity; - if (current_target.has(".fa-minus").length) { - new_qty = quantity - 1; - } else if (current_target.has(".fa-plus").length) { - new_qty = quantity + 1; - } - if (new_qty > max_val) { - attribute_name = custom_value - .closest(".tab-pane") - .find( - 'label[data-oe-id="' + - custom_value.attr("data-oe-id") + - '"]' - ); - message = - "Selected custom value " + - attribute_name.text() + - " must not be greater than " + - max_val; - self._displayTooltip(custom_value, message); - new_qty = max_val; - } else if (new_qty < min_val) { - attribute_name = custom_value - .closest(".tab-pane") - .find( - 'label[data-oe-id="' + - custom_value.attr("data-oe-id") + - '"]' - ); - message = - "Selected custom value " + - attribute_name.text() + - " must be at least " + - min_val; - self._displayTooltip(custom_value, message); - new_qty = min_val; - } - } - custom_value.val(new_qty); - self._disableEnableAddRemoveQtyButton( - current_target, - new_qty, - max_val, - min_val - ); - return custom_value; - }, - - _displayTooltip: function (config_attribute, message) { - $(config_attribute) - .tooltip({ - title: message, - placement: "bottom", - trigger: "manual", - }) - .tooltip("show"); - setTimeout(function () { - $(config_attribute).tooltip("dispose"); - }, 4000); - }, - - _disableEnableAddRemoveQtyButton: function ( - current_target, - quantity, - max_val, - min_val - ) { - var container = current_target.closest(".custom_field_container"); - if (quantity >= max_val) { - container.find(".js_add_qty").addClass("btn-disabled"); - } else if ( - quantity < max_val && - $(".js_add_qty").hasClass("btn-disabled") - ) { - container.find(".js_add_qty").removeClass("btn-disabled"); - } - if (quantity <= min_val) { - container.find(".js_remove_qty").addClass("btn-disabled"); - } else if ( - quantity > min_val && - $(".js_remove_qty").hasClass("btn-disabled") - ) { - container.find(".js_remove_qty").removeClass("btn-disabled"); - } - }, - }); - return publicWidget.registry.ProductConfigurator; -}); diff --git a/website_product_configurator/static/src/js/website_config_tour.js b/website_product_configurator/static/src/js/website_config_tour.js deleted file mode 100644 index 326cf39526..0000000000 --- a/website_product_configurator/static/src/js/website_config_tour.js +++ /dev/null @@ -1,180 +0,0 @@ -odoo.define("website_product_configurator.tour_configuration", function (require) { - "use strict"; - - var tour = require("web_tour.tour"); - var base = require("web_editor.base"); - - tour.register( - "config", - { - url: "/shop", - wait_for: base.ready(), - }, - [ - { - content: "search 2 series", - trigger: 'form input[name="search"]', - run: "text 2 series", - }, - { - content: "search 2 series", - trigger: 'form:has(input[name="search"]) .oe_search_button', - }, - { - content: "select 2 series", - trigger: '.oe_product_cart a:contains("2 Series")', - }, - { - content: "click to select fuel", - trigger: ".tab-pane.fade.container.show.active select", - run: function () { - $( - ".tab-pane.fade.container.show.active select:first option:contains(Gasoline)" - )[0].selected = true; - $( - ".tab-pane.fade.container.show.active select:first option:contains(Gasoline)" - ) - .closest("select") - .change(); - }, - }, - { - content: "click to select engine", - trigger: - ".tab-pane.fade.container.show.active select.form-control.config_attribute.cfg-select.required_config_attrib", - run: function () { - $( - ".tab-pane.fade.container.show.active select > option:contains(218i)" - )[0].selected = true; - $( - ".tab-pane.fade.container.show.active select > option:contains(218i)" - ) - .closest("select") - .change(); - }, - }, - { - content: "click on continue", - trigger: "span:contains(Continue)", - run: "click", - }, - { - content: "click to select color", - trigger: ".tab-pane.fade.container.show.active select", - run: function () { - $( - ".tab-pane.fade.container.show.active select:first option:contains(Silver)" - )[0].selected = true; - $( - ".tab-pane.fade.container.show.active select:first option:contains(Silver)" - ) - .closest("select") - .change(); - }, - }, - { - content: "click to select rims", - trigger: - ".tab-pane.fade.container.show.active select.form-control.config_attribute.cfg-select.required_config_attrib", - run: function () { - $( - ".tab-pane.fade.container.show.active select > option:contains(V-spoke 16)" - )[0].selected = true; - $( - ".tab-pane.fade.container.show.active select > option:contains(V-spoke 16)" - ) - .closest("select") - .change(); - }, - }, - { - content: "click on continue", - extra_trigger: ".nav-item.config_step a:contains(Lines)", - trigger: "span:contains(Continue)", - run: "click", - }, - { - content: "click to select Lines", - trigger: ".tab-pane.fade.container.show.active select", - run: function () { - $( - ".tab-pane.fade.container.show.active select option:contains(Sport Line)" - )[0].selected = true; - $( - ".tab-pane.fade.container.show.active select option:contains(Sport Line)" - ) - .closest("select") - .change(); - }, - }, - { - content: "click on continue", - trigger: "span:contains(Continue)", - run: "click", - }, - { - content: "click to select tapistry", - trigger: ".tab-pane.fade.container.show.active select", - run: function () { - $( - ".tab-pane.fade.container.show.active select option:contains(Black)" - )[0].selected = true; - $( - ".tab-pane.fade.container.show.active select option:contains(Black)" - ) - .closest("select") - .change(); - }, - }, - { - content: "click on continue", - trigger: "span:contains(Continue)", - run: "click", - }, - { - content: "click to select Transmission", - trigger: ".tab-pane.fade.container.show.active select", - run: function () { - $( - '.tab-pane.fade.container.show.active select:first option:contains("Automatic (Steptronic)")' - )[0].selected = true; - $( - '.tab-pane.fade.container.show.active select:first option:contains("Automatic (Steptronic)")' - ) - .closest("select") - .change(); - }, - }, - { - content: "click to select Options", - trigger: - ".tab-pane.fade.container.show.active select.form-control.config_attribute.cfg-select.required_config_attrib", - run: function () { - $( - ".tab-pane.fade.container.show.active select > option:contains(Armrest)" - )[0].selected = true; - $( - ".tab-pane.fade.container.show.active select > option:contains(Armrest)" - ) - .closest("select") - .change(); - }, - }, - { - content: "click on continue", - trigger: "span:contains(Continue)", - run: "click", - }, - { - content: "click on add to cart", - trigger: "#add_to_cart", - run: "click", - }, - { - content: "proceed to checkout product", - trigger: 'a[href*="/shop/checkout"]', - run: "click", - }, - ] - ); -}); diff --git a/website_product_configurator/static/src/js/website_sale.esm.js b/website_product_configurator/static/src/js/website_sale.esm.js new file mode 100644 index 0000000000..d12a702577 --- /dev/null +++ b/website_product_configurator/static/src/js/website_sale.esm.js @@ -0,0 +1,23 @@ +/** @odoo-module **/ + +import publicWidget from "@web/legacy/js/public/public_widget"; + +publicWidget.registry.WebsiteSale.include({ + /** + * Update the root product during an Add process. + * + * @private + * @param {Object} $form + * @param {Number} productId + */ + // eslint-disable-next-line no-unused-vars + _updateRootProduct($form, productId) { + // Call the original method to keep existing functionality + this._super.apply(this, arguments); + + // Extend the rootProduct to include the `config_session_id` + this.rootProduct.config_session_id = $form + .find('input[name="config_session_id"]') + .val(); + }, +}); diff --git a/website_product_configurator/static/src/js/website_sale.js b/website_product_configurator/static/src/js/website_sale.js deleted file mode 100644 index 7b24f74e05..0000000000 --- a/website_product_configurator/static/src/js/website_sale.js +++ /dev/null @@ -1,55 +0,0 @@ -odoo.define("website_product_configurator.config_website_sale", function (require) { - "use strict"; - var publicWidget = require("web.public.widget"); - - publicWidget.registry.WebsiteSale.include({ - /** - * Initializes the optional products modal - * and add handlers to the modal events (confirm, back, ...) - * - * @private - * @param {$.Element} $form the related webshop form - * @returns {Object} - * Override method to add config session - */ - _handleAdd: function ($form) { - var self = this; - this.$form = $form; - - var productSelector = [ - 'input[type="hidden"][name="product_id"]', - 'input[type="radio"][name="product_id"]:checked', - ]; - - var productReady = this.selectOrCreateProduct( - $form, - parseInt($form.find(productSelector.join(", ")).first().val(), 10), - $form.find(".product_template_id").val(), - false - ); - return productReady.then(function (productId) { - $form.find(productSelector.join(", ")).val(productId); - - self.rootProduct = { - product_id: productId, - quantity: parseFloat( - $form.find('input[name="add_qty"]').val() || 1 - ), - product_custom_attribute_values: self.getCustomVariantValues( - $form.find(".js_product") - ), - variant_values: self.getSelectedVariantValues( - $form.find(".js_product") - ), - no_variant_attribute_values: self.getNoVariantAttributeValues( - $form.find(".js_product") - ), - config_session_id: $form - .find('input[name="config_session_id"]') - .val(), - }; - return self._onProductReady(); - }); - }, - }); -}); diff --git a/website_product_configurator/static/tests/tours/website_config_tour.esm.js b/website_product_configurator/static/tests/tours/website_config_tour.esm.js new file mode 100644 index 0000000000..e0e926b425 --- /dev/null +++ b/website_product_configurator/static/tests/tours/website_config_tour.esm.js @@ -0,0 +1,170 @@ +/** @odoo-module **/ + +import {registry} from "@web/core/registry"; +import {stepUtils} from "@web_tour/tour_service/tour_utils"; + +registry.category("web_tour.tours").add("config", { + test: true, + url: "/shop", + sequence: 20, + + steps: () => [ + stepUtils.showAppsMenuItem(), + + { + content: "search 2 series", + trigger: 'form input[name="search"]', + run: "text 2 series", + }, + { + content: "search 2 series", + trigger: 'form:has(input[name="search"]) .oe_search_button', + }, + { + content: "select 2 series", + trigger: '.oe_product_cart a:contains("2 Series")', + }, + { + content: "click to select fuel", + trigger: ".tab-pane.fade.container.show.active select", + run: function () { + $( + ".tab-pane.fade.container.show.active select:first option:contains(Gasoline)" + )[0].selected = true; + $( + ".tab-pane.fade.container.show.active select:first option:contains(Gasoline)" + ) + .closest("select") + .change(); + }, + }, + { + content: "click to select engine", + trigger: + ".tab-pane.fade.container.show.active select.form-control.config_attribute.cfg-select.required_config_attrib", + run: function () { + $( + ".tab-pane.fade.container.show.active select > option:contains(218i)" + )[0].selected = true; + $(".tab-pane.fade.container.show.active select > option:contains(218i)") + .closest("select") + .change(); + }, + }, + { + content: "click on continue", + trigger: "span:contains(Continue)", + run: "click", + }, + { + content: "click to select color", + trigger: ".tab-pane.fade.container.show.active select", + run: function () { + $( + ".tab-pane.fade.container.show.active select:first option:contains(Silver)" + )[0].selected = true; + $( + ".tab-pane.fade.container.show.active select:first option:contains(Silver)" + ) + .closest("select") + .change(); + }, + }, + { + content: "click to select rims", + trigger: + ".tab-pane.fade.container.show.active select.form-control.config_attribute.cfg-select.required_config_attrib", + run: function () { + $( + ".tab-pane.fade.container.show.active select > option:contains(V-spoke 16)" + )[0].selected = true; + $( + ".tab-pane.fade.container.show.active select > option:contains(V-spoke 16)" + ) + .closest("select") + .change(); + }, + }, + { + content: "click on continue", + extra_trigger: ".nav-item.config_step a:contains(Lines)", + trigger: "span:contains(Continue)", + run: "click", + }, + { + content: "click to select Lines", + trigger: ".tab-pane.fade.container.show.active select", + run: function () { + $( + ".tab-pane.fade.container.show.active select option:contains(Sport Line)" + )[0].selected = true; + $( + ".tab-pane.fade.container.show.active select option:contains(Sport Line)" + ) + .closest("select") + .change(); + }, + }, + { + content: "click on continue", + trigger: "span:contains(Continue)", + run: "click", + }, + { + content: "click to select tapistry", + trigger: ".tab-pane.fade.container.show.active select", + run: function () { + $( + ".tab-pane.fade.container.show.active select option:contains(Black)" + )[0].selected = true; + $(".tab-pane.fade.container.show.active select option:contains(Black)") + .closest("select") + .change(); + }, + }, + { + content: "click on continue", + trigger: "span:contains(Continue)", + run: "click", + }, + { + content: "click to select Transmission", + trigger: ".tab-pane.fade.container.show.active select", + run: function () { + $( + '.tab-pane.fade.container.show.active select:first option:contains("Automatic (Steptronic)")' + )[0].selected = true; + $( + '.tab-pane.fade.container.show.active select:first option:contains("Automatic (Steptronic)")' + ) + .closest("select") + .change(); + }, + }, + { + content: "click to select Options", + trigger: + ".tab-pane.fade.container.show.active select.form-control.config_attribute.cfg-select.required_config_attrib", + run: function () { + $( + ".tab-pane.fade.container.show.active select > option:contains(Armrest)" + )[0].selected = true; + $( + ".tab-pane.fade.container.show.active select > option:contains(Armrest)" + ) + .closest("select") + .change(); + }, + }, + { + content: "click on continue", + trigger: "span:contains(Continue)", + run: "click", + }, + { + content: "click on add to cart", + trigger: "#add_to_cart", + run: "click", + }, + ], +}); diff --git a/website_product_configurator/tests/__init__.py b/website_product_configurator/tests/__init__.py index e191b34660..8bba4958a3 100644 --- a/website_product_configurator/tests/__init__.py +++ b/website_product_configurator/tests/__init__.py @@ -2,5 +2,4 @@ from . import test_product_config from . import test_res_config_settings from . import test_sale_order - -# from . import test_website_product_config +from . import test_website_product_config diff --git a/website_product_configurator/tests/test_res_config_settings.py b/website_product_configurator/tests/test_res_config_settings.py index 43ca9d4060..a1232613a7 100644 --- a/website_product_configurator/tests/test_res_config_settings.py +++ b/website_product_configurator/tests/test_res_config_settings.py @@ -3,7 +3,7 @@ class TestResConfigSettings(TransactionCase): def setUp(self): - super(TestResConfigSettings, self).setUp() + super().setUp() self.ResConfigObj = self.env["res.config.settings"] self.res_config = self.ResConfigObj.create( { @@ -21,18 +21,14 @@ def setUp(self): ) self.res_config_select.set_values() - def test_xml_id_to_record_id(self): - website_tmpl_id = self.res_config.xml_id_to_record_id(False) - self.assertFalse(website_tmpl_id, "xml_id is set") - website_tmpl_id = self.res_config.xml_id_to_record_id( - self.res_config.website_tmpl_id.xml_id - ) - + def test_res_config_settings(self): + self.assertTrue(self.res_config) self.assertEqual( - website_tmpl_id, - False, + self.res_config.website_tmpl_id, + self.env.ref("website_product_configurator.config_form_base"), ) - website_tmpl_id_select = self.res_config_select.xml_id_to_record_id( - self.res_config_select.website_tmpl_id.xml_id + self.assertTrue(self.res_config_select) + self.assertEqual( + self.res_config_select.website_tmpl_id, + self.env.ref("website_product_configurator.config_form_select"), ) - self.assertTrue(website_tmpl_id_select, "website_tmpl_id_select is not set") diff --git a/website_product_configurator/tests/test_sale_order.py b/website_product_configurator/tests/test_sale_order.py index e2a126c934..13df31779f 100644 --- a/website_product_configurator/tests/test_sale_order.py +++ b/website_product_configurator/tests/test_sale_order.py @@ -5,11 +5,17 @@ class TestSaleOrder(TestProductConfiguratorValues): def setUp(self): - super(TestSaleOrder, self).setUp() + super().setUp() self.partner = self.env.ref("base.res_partner_1") self.product = self.env["product.product"].create({"name": "test product"}) self.product_uom_unit = self.env.ref("uom.product_uom_unit") - self.pricelist = self.env.ref("product.list0") + self.pricelist = self.env["product.pricelist"].create( + { + "name": "New Pricelist", + "currency_id": self.env.user.company_id.currency_id.id, + "discount_policy": "without_discount", + } + ) self.sale_order = self.env["sale.order"].create( { "name": "test SO", @@ -99,30 +105,3 @@ def test_cart_update(self): 1, "If wrong value is added then Order Line quantity as it is.", ) - - def test_get_real_price_currency(self): - price, rule_id = self.sale_order.pricelist_id.get_product_price_rule( - self.sale_order.order_line.product_id, - self.sale_order.order_line.product_uom_qty, - self.sale_order.partner_id, - ) - self.sale_order.order_line._get_real_price_currency( - self.sale_order.order_line.product_id, - rule_id, - self.sale_order.order_line.product_uom_qty, - self.sale_order.order_line.product_uom, - self.sale_order.pricelist_id.id, - ) - self.assertFalse( - self.product.product_tmpl_id.config_ok, "product is config_ok True" - ) - self.product.product_tmpl_id.write({"config_ok": True}) - price, currency = self.sale_order.order_line._get_real_price_currency( - self.sale_order.order_line.product_id, - rule_id, - self.sale_order.order_line.product_uom_qty, - self.sale_order.order_line.product_uom, - self.sale_order.pricelist_id.id, - ) - self.assertEqual(price, 0.0) - self.assertEqual(currency, self.env.ref("base.USD")) diff --git a/website_product_configurator/tests/test_website_product_config.py b/website_product_configurator/tests/test_website_product_config.py index f64bdf2388..27c3b0eedf 100644 --- a/website_product_configurator/tests/test_website_product_config.py +++ b/website_product_configurator/tests/test_website_product_config.py @@ -4,17 +4,7 @@ @odoo.tests.common.tagged("post_install", "-at_install") class TestUi(odoo.tests.HttpCase): def test_01_admin_config_tour(self): - self.start_tour( - "/", - "odoo.__DEBUG__.services['web_tour.tour'].run('config')", - "odoo.__DEBUG__.services['web_tour.tour'].tours.config.ready", - login="admin", - ) + self.start_tour("/web", "config", login="admin") def test_02_demo_config_tour(self): - self.start_tour( - "/", - "odoo.__DEBUG__.services['web_tour.tour'].run('config')", - "odoo.__DEBUG__.services['web_tour.tour'].tours.config.ready", - login="demo", - ) + self.start_tour("/web", "config", login="demo") diff --git a/website_product_configurator/tests/test_website_product_configurator_values.py b/website_product_configurator/tests/test_website_product_configurator_values.py index ef8b39596d..6d3a8eda1d 100644 --- a/website_product_configurator/tests/test_website_product_configurator_values.py +++ b/website_product_configurator/tests/test_website_product_configurator_values.py @@ -2,14 +2,12 @@ from odoo import fields -from odoo.addons.product_configurator.tests import test_product_configurator_test_cases +from odoo.addons.product_configurator.tests import common -class TestProductConfiguratorValues( - test_product_configurator_test_cases.ProductConfiguratorTestCases -): +class TestProductConfiguratorValues(common.ProductConfiguratorTestCases): def setUp(self): - super(TestProductConfiguratorValues, self).setUp() + super().setUp() self.productConfigStepLine = self.env["product.config.step.line"] self.productAttributeLine = self.env["product.template.attribute.line"] self.product_category = self.env.ref("product.product_category_5") diff --git a/website_product_configurator/views/assets.xml b/website_product_configurator/views/assets.xml deleted file mode 100644 index 71169b6fad..0000000000 --- a/website_product_configurator/views/assets.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - -