diff --git a/product_configurator/models/product.py b/product_configurator/models/product.py index 413360d7d..a45910ba1 100644 --- a/product_configurator/models/product.py +++ b/product_configurator/models/product.py @@ -6,6 +6,7 @@ from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from collections import Counter _logger = logging.getLogger(__name__) @@ -348,29 +349,25 @@ def write(self, vals): ] raise ValidationError( _( - "The following attributes are missing\ - from Configuration Steps: %s", - (attrs), + "The following attributes are missing from Configuration Steps: %s", + (",".join(attrs)), ) ) - all_attrs = self.config_step_line_ids.attribute_line_ids.ids - check = False - dupAttrs = [] - for config_step in self.config_step_line_ids[1:]: - check = any(e in all_attrs for e in config_step.attribute_line_ids.ids) - if check: - dupAttrs.append( - [ - attr.attribute_id.name - for attr in config_step.attribute_line_ids - ] - ) - if check and dupAttrs: + couter = [] + for config_step in self.config_step_line_ids: + couter.extend(config_step.attribute_line_ids.ids) + counter = Counter(couter) + duplicates = [] + for k,v in dict(counter).items(): + if v > 1: + duplicates.append(k) + if duplicates: + duplicates = self.env['product.template.attribute.line'].browse(duplicates) + duplicates = ",".join(duplicates.mapped('attribute_id.name')) raise ValidationError( _( - "The following attributes have \ - duplicates in Configuration Steps: %s", - (dupAttrs), + "The following attributes have duplicates in Configuration Steps: %s", + (duplicates), ) ) return res diff --git a/product_configurator/models/product_config.py b/product_configurator/models/product_config.py index 3868d7e04..f8dbac75a 100644 --- a/product_configurator/models/product_config.py +++ b/product_configurator/models/product_config.py @@ -821,20 +821,24 @@ def get_components_prices(self, prices, pricelist, value_ids=None): return prices @api.model - def get_cfg_price(self, value_ids=None, custom_vals=None): + def get_cfg_price(self, value_ids=[], custom_vals=None): """Computes the price of the configured product based on the configuration passed in via value_ids and custom_values :param value_ids: list of attribute value_ids :param custom_vals: dictionary of custom attribute values :returns: final configuration price""" - - if value_ids is None: + if not value_ids: value_ids = self.value_ids.ids if custom_vals is None: custom_vals = {} - + value_ids = value_ids + self.value_ids.ids + if self.env.context.get("tobe_remove_attr", []): + value_ids = set(value_ids) - set( + self.env.context.get("tobe_remove_attr", []) + ) + value_ids = list(value_ids) product_tmpl = self.product_tmpl_id self = self.with_context(active_id=product_tmpl.id) @@ -1481,16 +1485,14 @@ def flatten_val_ids(self, value_ids): :param value_ids: list of value ids or mix of ids and list of ids (e.g: [1, 2, 3, [4, 5, 6]]) :returns: flattened list of ids ([1, 2, 3, 4, 5, 6])""" - flat_val_ids = set() - if value_ids and value_ids[0]: - for val in value_ids: - if not val: - continue - if isinstance(val, list): - flat_val_ids.add(val[1]) - elif isinstance(val, int): - flat_val_ids.add(val) - return list(flat_val_ids) + flatList = [] + for value in value_ids: + if isinstance(value, list): + for sub in value: + flatList.append(sub) + else: + flatList.append(value) + return flatList def formatPrices(self, prices=None, dp="Product Price"): if prices is None: diff --git a/product_configurator/wizard/product_configurator.py b/product_configurator/wizard/product_configurator.py index bcc62e9f9..e6ced97b9 100644 --- a/product_configurator/wizard/product_configurator.py +++ b/product_configurator/wizard/product_configurator.py @@ -15,11 +15,14 @@ class Base(models.AbstractModel): - _inherit = 'base' + _inherit = "base" + + def onchange(self, values: {}, field_names: [], fields_spec: {}): + fields_spec = self.env["product.configurator"]._remove_dynamic_fields( + fields_spec + ) + return super().onchange(values, field_names, fields_spec) - def onchange(self, values: {}, field_names: [] ,fields_spec: {}): - fields_spec = self.env['product.configurator']._remove_dynamic_fields(fields_spec) - return super().onchange(values,field_names,fields_spec) class FreeSelection(fields.Selection): def convert_to_cache(self, value, record, validate=True): @@ -198,9 +201,11 @@ def get_onchange_vals(self, cfg_val_ids, config_session_id=None): # Remove None from cfg_val_ids if exist cfg_val_ids = [val for val in cfg_val_ids if val] - + tobe_remove_attr = self.env.context.get("tobe_remove_attr", []) product_img = config_session_id.get_config_image(cfg_val_ids) - price = config_session_id.get_cfg_price(cfg_val_ids) + price = config_session_id.with_context( + tobe_remove_attr=tobe_remove_attr + ).get_cfg_price(cfg_val_ids) weight = config_session_id.get_cfg_weight(value_ids=cfg_val_ids) return { @@ -233,26 +238,62 @@ def get_form_vals( if not v: continue available_val_ids = domains[k][0][2] - if isinstance(v, list): - if any(not isinstance(el, int) for el in v): - v = v[0][1] - value_ids = available_val_ids.append(v) - value_ids = available_val_ids - dynamic_fields.update({k: value_ids}) - vals[k] = [[6, 0, value_ids]] + available_val_ids_m2m = [] + if isinstance(v, list) and self.env.context.get("is_m2m"): + for sub_value in v: + available_val_ids_m2m.append(sub_value[1]) + dynamic_fields.update({k: available_val_ids_m2m}) + vals[k] = [[6, 0, available_val_ids_m2m]] elif v not in available_val_ids: dynamic_fields.update({k: None}) vals[k] = None else: vals[k] = v - final_cfg_val_ids = list(dynamic_fields.values()) - vals.update(self.get_onchange_vals(final_cfg_val_ids, config_session_id)) + tobe_remove_attr = [] + if ( + self.env.context.get("is_action_previous") + and config_session_id + and config_session_id.value_ids + ): + session_attrb = config_session_id.value_ids + product_tmpl_attrb = config_session_id.product_tmpl_id.attribute_line_ids + dynamic_fields2 = {} + for tmpl_attr in product_tmpl_attrb: + if not tmpl_attr.custom: + sess_attr = session_attrb.filtered( + lambda value: value.attribute_id.id == tmpl_attr.attribute_id.id + ) + dyn_key = "__attribute_" + str(sess_attr.attribute_id.id) + if ( + sess_attr + and sess_attr.attribute_id.id + in config_session_id.product_tmpl_id.config_line_ids.mapped( + "attribute_line_id.attribute_id" + ).ids + ): + tobe_remove_attr.append(sess_attr.id) + dynamic_fields2.update({dyn_key: sess_attr.id}) + else: + if sess_attr and dyn_key in dynamic_fields: + tobe_remove_attr.append(sess_attr.id) + origin_updated_fields = set(dynamic_fields) + to_updated_fields = set(dynamic_fields2) + updated_fields = to_updated_fields - origin_updated_fields + + for fi in updated_fields: + dynamic_fields.update({fi: None}) + vals.update({fi: None}) + final_cfg_val_ids = list(dynamic_fields.values()) + vals.update( + self.with_context(tobe_remove_attr=tobe_remove_attr).get_onchange_vals( + final_cfg_val_ids, config_session_id + ) + ) # To solve the Multi selection problem removing extra [] if "value_ids" in vals: val_ids = vals["value_ids"][0] vals["value_ids"] = [[val_ids[0], val_ids[1], tools.flatten(val_ids[2])]] - return vals def apply_onchange_values(self, values, field_name, field_onchange): @@ -277,10 +318,11 @@ def apply_onchange_values(self, values, field_name, field_onchange): state = self.state cfg_vals = self.env["product.attribute.value"] - if values.get("value_ids", []): - cfg_vals = self.env["product.attribute.value"].browse( - values.get("value_ids", [])[0][2] - ) + # TODO: VP Need to Check + # if values.get("value_ids", []): + # cfg_vals = self.env["product.attribute.value"].browse( + # values.get("value_ids", [])[0][2] + # ) if not cfg_vals: cfg_vals = self.value_ids @@ -288,15 +330,12 @@ def apply_onchange_values(self, values, field_name, field_onchange): field_prefix = self._prefixes.get("field_prefix") custom_field_prefix = self._prefixes.get("custom_field_prefix") local_field_name = field_name and field_name[0].startswith(field_prefix) - local_custom_field = field_name and field_name[0].startswith(custom_field_prefix) - if field_type == list and ( - not local_field_name - and not local_custom_field - ): + local_custom_field = field_name and field_name[0].startswith( + custom_field_prefix + ) + if field_type == list and (not local_field_name and not local_custom_field): values = self._remove_dynamic_fields(values) - res = super().onchange( - values, field_name, field_onchange - ) + res = super().onchange(values, field_name, field_onchange) return res view_val_ids = set() @@ -457,8 +496,8 @@ def _onchange_state(self): def _onchange_product_preset(self): """Set value ids as from product preset""" preset_id = self.product_preset_id - if not preset_id and self.env.context.get('preset_values'): - preset_id = self.env.context.get('preset_values').get('product_preset_id') + if not preset_id and self.env.context.get("preset_values"): + preset_id = self.env.context.get("preset_values").get("product_preset_id") preset_id = self.env["product.product"].browse(preset_id) pta_value_ids = preset_id.product_template_attribute_value_ids attr_value_ids = pta_value_ids.mapped("product_attribute_value_id") @@ -779,7 +818,7 @@ def add_dynamic_fields(self, res, dynamic_fields, wiz): "field_name": field_name, "is_m2m": attr_line.multi, "value_ids": attr_line.value_ids.ids, - "active_model":self._name, + "active_model": self._name, } ), options=str( @@ -869,9 +908,7 @@ def create(self, vals_list): ) vals.update({"user_id": self.env.uid, "config_session_id": session.id}) wz_value_ids = vals.get("value_ids", []) - if session.value_ids and ( - (wz_value_ids and not wz_value_ids[0][2]) or not wz_value_ids - ): + if session.value_ids: vals.update({"value_ids": [(6, 0, session.value_ids.ids)]}) return super().create(vals_list) @@ -996,6 +1033,10 @@ def action_previous_step(self): wizard_action = self.with_context( wizard_id=self.id, view_cache=False, allow_preset_selection=False ).get_wizard_action(wizard=self) + if wizard_action.get("context") and "is_action_previous" in wizard_action.get( + "context" + ): + wizard_action.get("context")["is_action_previous"] = True cfg_step_lines = self.product_tmpl_id.config_step_line_ids if not cfg_step_lines: @@ -1030,9 +1071,9 @@ def action_reset(self): except Exception: session = self.env["product.config.step"] if session_product_tmpl_id: - action = session_product_tmpl_id.with_context(dict(self.env.context)).create_config_wizard( - model_name=self._name,click_next=False - ) + action = session_product_tmpl_id.with_context( + dict(self.env.context) + ).create_config_wizard(model_name=self._name, click_next=False) return action return False @@ -1047,7 +1088,8 @@ def get_wizard_action(self, view_cache=False, wizard=None): { "view_cache": view_cache, "differentiator": ctx.get("differentiator", 1) + 1, - "is_product_configurator":True, + "is_product_configurator": True, + "is_action_previous": False, } ) if wizard: