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 @@
+