diff --git a/product_configurator/models/product_attribute.py b/product_configurator/models/product_attribute.py index 9ec1dba5..00786887 100644 --- a/product_configurator/models/product_attribute.py +++ b/product_configurator/models/product_attribute.py @@ -45,8 +45,24 @@ def onchange_custom_type(self): 'disable a attribute without deleting it' ) - min_val = fields.Integer(string="Min Value", help="Minimum value allowed") - max_val = fields.Integer(string="Max Value", help="Minimum value allowed") + min_val = fields.Integer( + string="Min Value", + help="Minimum value allowed" + ) + max_val = fields.Integer( + string="Max Value", + help="Maximum value allowed, or 0 if not checked" + ) + min_fval = fields.Float( + string="Min Float Value", + digits=(16, 4), + help="Minimum value allowed" + ) + max_fval = fields.Float( + string="Max Float Value", + digits=(16, 4), + help="Maximum value allowed, or 0 if not checked" + ) # TODO: Exclude self from result-set of dependency val_custom = fields.Boolean( @@ -59,6 +75,10 @@ def onchange_custom_type(self): size=64, help='The type of the custom field generated in the frontend' ) + custom_digits = fields.Integer( + string='Decimal Digits', + help='Number of digits accuracy on float entry' + ) description = fields.Text(string='Description', translate=True) @@ -109,24 +129,28 @@ def validate_custom_val(self, val): Probaly should check type, etc, but let's assume fine for the moment. """ self.ensure_one() - if self.custom_type in ('int', 'float'): + if self.custom_type == 'float': + minv = self.min_fval + maxv = self.max_fval + elif self.custom_type == 'int': minv = self.min_val maxv = self.max_val + if self.custom_type in ('int', 'float'): val = literal_eval(val) if minv and maxv and (val < minv or val > maxv): raise ValidationError( _("Selected custom value '%s' must be between %s and %s" - % (self.name, self.min_val, self.max_val)) + % (self.name, minv, maxv)) ) elif minv and val < minv: raise ValidationError( _("Selected custom value '%s' must be at least %s" % - (self.name, self.min_val)) + (self.name, minv)) ) elif maxv and val > maxv: raise ValidationError( - _("Selected custom value '%s' must be lower than %s" % - (self.name, self.max_val + 1)) + _("Selected custom value '%s' cannot be greater than %s" % + (self.name, maxv)) ) diff --git a/product_configurator/tests/__init__.py b/product_configurator/tests/__init__.py index 7bc90d79..04e40d42 100644 --- a/product_configurator/tests/__init__.py +++ b/product_configurator/tests/__init__.py @@ -2,3 +2,4 @@ from . import test_create from . import test_configuration_rules +from . import test_numeric_customvals diff --git a/product_configurator/tests/test_numeric_customvals.py b/product_configurator/tests/test_numeric_customvals.py new file mode 100644 index 00000000..879086f9 --- /dev/null +++ b/product_configurator/tests/test_numeric_customvals.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError + + +class ConfigurationRules(TransactionCase): + + def setUp(self): + super(ConfigurationRules, self).setUp() + + self.attr_repeater = self.env['product.attribute'].create( + {'name': 'Repeater', + 'value_ids': [ + (0, 0, {'name': '1'}), + (0, 0, {'name': '2'}), + ], + 'custom_type': 'int', + 'min_val': 3, + 'max_val': 10, + } + ) + self.attr_flux = self.env['product.attribute'].create( + {'name': 'Flux Adjustment', + 'value_ids': [ + (0, 0, {'name': 'Standard'}), + ], + 'custom_type': 'float', + 'custom_digits': 3, + 'min_fval': 0.750, + 'max_fval': 1.823, + } + ) + self.flux_capacitor = self.env['product.template'].create( + {'name': 'Flux Capacitor', + 'config_ok': True, + 'type': 'product', + 'categ_id': self.env['ir.model.data'] .xmlid_to_res_id( + 'product.product_category_5' + ), + 'attribute_line_ids': [ + (0, 0, {'attribute_id': self.attr_repeater.id, + 'value_ids': [ + (6, 0, self.attr_repeater.value_ids.ids), + ], + 'required': True, + 'custom': True, + }), + (0, 0, {'attribute_id': self.attr_flux.id, + 'value_ids': [ + (6, 0, self.attr_flux.value_ids.ids), + ], + 'required': True, + 'custom': True, + }) + ] + } + ) + + def test_without_custom(self): + attr_val_ids = [self.attr_repeater.value_ids[0].id, + self.attr_flux.value_ids[0].id, + ] + custom_vals = {} + validation = self.flux_capacitor.validate_configuration( + attr_val_ids, custom_vals) + self.assertTrue( + validation, + "Configuration with custom values rejected with all " + "standard selections" + ) + + def test_valid_custom_numerics(self): + attr_val_ids = [] + custom_vals = { + self.attr_repeater.id: '5', + self.attr_flux.id: '1.234', + } + validation = self.flux_capacitor.validate_configuration( + attr_val_ids, custom_vals) + self.assertTrue( + validation, + "Configuration with valid custom numeric values rejected" + ) + + def test_invalid_custom_numerics(self): + attr_val_ids = [] + validation_method = self.flux_capacitor.validate_configuration + + custom_vals = { + self.attr_repeater.id: '2', + self.attr_flux.id: '1.234', + } + self.assertRaises(ValidationError, + validation_method, attr_val_ids, custom_vals) + + custom_vals = { + self.attr_repeater.id: '11', + self.attr_flux.id: '1.234', + } + self.assertRaises(ValidationError, + validation_method, attr_val_ids, custom_vals) + + custom_vals = { + self.attr_repeater.id: '5', + self.attr_flux.id: '0.749', + } + self.assertRaises(ValidationError, + validation_method, attr_val_ids, custom_vals) + + custom_vals = { + self.attr_repeater.id: '5', + self.attr_flux.id: '1.824', + } + self.assertRaises(ValidationError, + validation_method, attr_val_ids, custom_vals) diff --git a/product_configurator/views/product_attribute_view.xml b/product_configurator/views/product_attribute_view.xml index bf373967..0b496aa8 100644 --- a/product_configurator/views/product_attribute_view.xml +++ b/product_configurator/views/product_attribute_view.xml @@ -51,11 +51,14 @@ + - - + + + + diff --git a/product_configurator_wizard/wizard/product_configurator.py b/product_configurator_wizard/wizard/product_configurator.py index 5e0055d0..a7c35409 100644 --- a/product_configurator_wizard/wizard/product_configurator.py +++ b/product_configurator_wizard/wizard/product_configurator.py @@ -317,13 +317,17 @@ def fields_get(self, allfields=None, attributes=None): elif custom_type in [f[0] for f in field_types]: field_type = custom_type + custom_field = self.custom_field_prefix + str(attribute.id) # TODO: Implement custom string on custom attribute - res[self.custom_field_prefix + str(attribute.id)] = dict( + res[custom_field] = dict( default_attrs, string="Custom", type=field_type, sequence=line.sequence, ) + if field_type == 'float' and line.attribute_id.custom_digits: + res[custom_field]['digits'] = \ + (16, line.attribute_id.custom_digits) # Add the dynamic field to the resultset using the convention # "__attribute-DBID" to later identify and extract it diff --git a/website_product_configurator/controllers/main.py b/website_product_configurator/controllers/main.py index 22f288d0..d80610bb 100644 --- a/website_product_configurator/controllers/main.py +++ b/website_product_configurator/controllers/main.py @@ -371,9 +371,13 @@ def config_parse(self, product_tmpl, post, config_step=None, custom_val = post.get(custom_field_name) if custom_val: custom_type = line.attribute_id.custom_type - if custom_type in ['float', 'integer']: + if custom_type == 'float': + max_val = line.attribute_id.max_fval + min_val = line.attribute_id.min_fval + elif custom_type == 'integer': max_val = line.attribute_id.max_val min_val = line.attribute_id.min_val + if custom_type in ['float', 'integer']: if max_val and custom_val > max_val: continue elif min_val and custom_val < min_val: diff --git a/website_product_configurator/data/config_layout.xml b/website_product_configurator/data/config_layout.xml index 1fd64887..09a2701e 100644 --- a/website_product_configurator/data/config_layout.xml +++ b/website_product_configurator/data/config_layout.xml @@ -52,6 +52,7 @@ +