diff --git a/product_configurator/demo/product_attribute.xml b/product_configurator/demo/product_attribute.xml index 5e833da0..4b69d8da 100644 --- a/product_configurator/demo/product_attribute.xml +++ b/product_configurator/demo/product_attribute.xml @@ -335,7 +335,7 @@ ref('product_configurator.product_attribute_value_tow_hook'), ] )]"/> - + diff --git a/product_configurator/demo/product_config_lines.xml b/product_configurator/demo/product_config_lines.xml index 84d54ff1..0e4f7b24 100644 --- a/product_configurator/demo/product_config_lines.xml +++ b/product_configurator/demo/product_config_lines.xml @@ -11,6 +11,7 @@ ref('product_attribute_value_m235i'), ref('product_attribute_value_m235i_xdrive')])]"/> + Gasoline Fuel requires a Gasoline Engine to be selected @@ -22,6 +23,7 @@ ref('product_attribute_value_220d_xdrive'), ref('product_attribute_value_225d')])]"/> + Diesel Fuel requires a Diesel Engine to be selected @@ -31,6 +33,7 @@ ref('product_attribute_value_sport_line'), ref('product_attribute_value_luxury_line')])]"/> + 218i Engine has a limited selection of Lines diff --git a/product_configurator/models/product.py b/product_configurator/models/product.py index 9a6bfe56..56cdd063 100644 --- a/product_configurator/models/product.py +++ b/product_configurator/models/product.py @@ -357,7 +357,8 @@ def create_get_variant(self, value_ids, custom_values=None): """ if custom_values is None: custom_values = {} - valid = self.validate_configuration(value_ids, custom_values) + valid = self.validate_configuration(value_ids, custom_values, + do_raise=True) if not valid: raise ValidationError(_('Invalid Configuration')) @@ -413,7 +414,7 @@ def validate_domains_against_sels(self, domains, sel_val_ids): return avail @api.multi - def values_available(self, attr_val_ids, sel_val_ids): + def values_available(self, attr_val_ids, sel_val_ids, do_raise=False): """Determines whether the attr_values from the product_template are available for selection given the configuration ids and the dependencies set on the product template @@ -421,6 +422,7 @@ def values_available(self, attr_val_ids, sel_val_ids): :param attr_val_ids: list of attribute value ids to check for availability :param sel_val_ids: list of attribute value ids already selected + :param do_raise: boolean on whether to raise a warning or just fail :returns: list of available attribute values """ @@ -436,11 +438,23 @@ def values_available(self, attr_val_ids, sel_val_ids): avail = self.validate_domains_against_sels(domains, sel_val_ids) if avail: avail_val_ids.append(attr_val_id) - + elif do_raise: + if len(config_lines) == 1 and config_lines[0].rule_description: + raise ValidationError(config_lines[0].rule_description) + else: + attribute_value = \ + self.env['product.attribute.value'].browse(attr_val_id) + raise ValidationError( + _('%s for %s is not valid in this configuration') % + (attribute_value.name, + attribute_value.attribute_id.name + ) + ) return avail_val_ids @api.multi - def validate_configuration(self, value_ids, custom_vals=None, final=True): + def validate_configuration(self, value_ids, + custom_vals=None, final=True, do_raise=False): """ Verifies if the configuration values passed via value_ids and custom_vals are valid @@ -448,12 +462,12 @@ def validate_configuration(self, value_ids, custom_vals=None, final=True): :param custom_vals: custom values dict {attr_id: custom_val} :param final: boolean marker to check required attributes. pass false to check non-final configurations + :param do_raise: boolean on whether to raise a warning or just fail :returns: Error dict with reason of validation failure or True """ - # TODO: Raise ConfigurationError with reason - # Check if required values are missing for final configuration + # TODO: Check if required values are missing for final configuration if custom_vals is None: custom_vals = {} @@ -466,11 +480,15 @@ def validate_configuration(self, value_ids, custom_vals=None, final=True): common_vals = set(value_ids) & set(line.value_ids.ids) custom_val = custom_vals.get(attr.id) if line.required and not common_vals and not custom_val: + if do_raise: + raise ValidationError(_("No value provided for %s") % + line.attribute.name) # TODO: Verify custom value type to be correct return False # Check if all all the values passed are not restricted - avail_val_ids = self.values_available(value_ids, value_ids) + avail_val_ids = self.values_available(value_ids, value_ids, + do_raise=do_raise) if set(value_ids) - set(avail_val_ids): return False @@ -478,7 +496,17 @@ def validate_configuration(self, value_ids, custom_vals=None, final=True): custom_attr_ids = self.attribute_line_ids.filtered( 'custom').mapped('attribute_id').ids - if not set(custom_vals.keys()) <= set(custom_attr_ids): + invalid_custom_vals = set(custom_vals.keys()) - set(custom_attr_ids) + if invalid_custom_vals: + if do_raise: + ProductAttribute = self.env['product.attribute'] + attributes_names = ', '.join(attr.name + for attr in + ProductAttribute.browse( + list(invalid_custom_vals))) + raise ValidationError( + _('Attributes (%s) cannot hold custom values') % + attributes_names) return False # Check if there are multiple values passed for non-multi attributes diff --git a/product_configurator/models/product_config.py b/product_configurator/models/product_config.py index 64ae1a31..0b27f395 100644 --- a/product_configurator/models/product_config.py +++ b/product_configurator/models/product_config.py @@ -180,6 +180,11 @@ def onchange_attribute(self): string='Restrictions' ) + rule_description = fields.Char( + string='Rule Description', + help='User Displayed error if rule is broken' + ) + sequence = fields.Integer(string='Sequence', default=10) _order = 'product_tmpl_id, sequence, id' @@ -229,7 +234,7 @@ def _check_value_ids(self): if not valid: raise ValidationError( _("Values entered for line '%s' generate " - "a incompatible configuration" % cfg_img.name) + "an incompatible configuration" % cfg_img.name) ) @@ -451,7 +456,7 @@ def write(self, vals): for x in self.custom_value_ids } valid = self.product_tmpl_id.validate_configuration( - self.value_ids.ids, custom_val_dict, final=False) + self.value_ids.ids, custom_val_dict, final=False, do_raise=True) if not valid: raise ValidationError(_('Invalid Configuration')) return res diff --git a/product_configurator/tests/test_configuration_rules.py b/product_configurator/tests/test_configuration_rules.py index 984a3010..649478e6 100644 --- a/product_configurator/tests/test_configuration_rules.py +++ b/product_configurator/tests/test_configuration_rules.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError class ConfigurationRules(TransactionCase): @@ -56,6 +57,10 @@ def test_invalid_configuration(self): validation = self.cfg_tmpl.validate_configuration(attr_val_ids) self.assertFalse(validation, "Incompatible values (Diesel Fuel -> " "Gasoline Engine) configuration passed validation") + self.assertRaises(ValidationError, + self.cfg_tmpl.validate_configuration, + attr_val_ids, do_raise=True, + ) def test_missing_val_configuration(self): conf = [ diff --git a/product_configurator/views/product_view.xml b/product_configurator/views/product_view.xml index 1519dece..69db3c7a 100644 --- a/product_configurator/views/product_view.xml +++ b/product_configurator/views/product_view.xml @@ -63,6 +63,7 @@ options="{'no_create': True, 'no_create_edit': True}" context="{'show_attribute': False}"/> +