diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 593a01bbf6..e103fdb65b 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -629,6 +629,13 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= def add_serial(serial): """Helper function to check for duplicated values""" + + serial = serial.strip() + + # Ignore blank / emtpy serials + if len(serial) == 0: + return + if serial in serials: add_error(_("Duplicate serial") + f": {serial}") else: @@ -645,6 +652,10 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= return serials for group in groups: + + # Calculate the "remaining" quantity of serial numbers + remaining = expected_quantity - len(serials) + group = group.strip() if '-' in group: @@ -680,20 +691,21 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= group_items.append(b) break - elif count > expected_quantity: + elif count > remaining: # More than the allowed number of items break elif a_next is None: break - if len(group_items) > 0 and group_items[0] == a and group_items[-1] == b: + if len(group_items) > remaining: + add_error(_("Group range {g} exceeds allowed quantity ({q})".format(g=group, q=expected_quantity))) + elif len(group_items) > 0 and group_items[0] == a and group_items[-1] == b: # In this case, the range extraction looks like it has worked for item in group_items: add_serial(item) else: - add_serial(group) - # add_error(_("Invalid group range: {g}").format(g=group)) + add_error(_("Invalid group range: {g}").format(g=group)) else: # In the case of a different number of hyphens, simply add the entire group @@ -715,7 +727,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= continue elif len(items) == 2: try: - if items[1] not in ['', None]: + if items[1]: sequence_count = int(items[1]) + 1 except ValueError: add_error(_("Invalid group sequence: {g}").format(g=group)) @@ -745,7 +757,7 @@ def extract_serial_numbers(input_string, expected_quantity: int, starting_value= if len(serials) == 0: raise ValidationError([_("No serial numbers found")]) - if len(serials) != expected_quantity: + if len(errors) == 0 and len(serials) != expected_quantity: raise ValidationError([_("Number of unique serial numbers ({s}) must match quantity ({q})").format(s=len(serials), q=expected_quantity)]) return serials diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index f8d64d1261..5bae7281fc 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -447,11 +447,15 @@ class TestSerialNumberExtraction(TestCase): """Test simple serial numbers.""" e = helpers.extract_serial_numbers + # Test a range of numbers sn = e("1-5", 5, 1) - self.assertEqual(len(sn), 5, 1) + self.assertEqual(len(sn), 5) for i in range(1, 6): self.assertIn(str(i), sn) + sn = e("11-30", 20, 1) + self.assertEqual(len(sn), 20) + sn = e("1, 2, 3, 4, 5", 5, 1) self.assertEqual(len(sn), 5) @@ -470,11 +474,6 @@ class TestSerialNumberExtraction(TestCase): self.assertEqual(len(sn), 5) self.assertEqual(sn, ['1', '2', "TG-4SR-92", '4', '5']) - # Test groups are not interpolated with alpha characters - sn = e("1, A-2, 3+", 5, 1) - self.assertEqual(len(sn), 5) - self.assertEqual(sn, ['1', "A-2", '3', '4', '5']) - # Test multiple placeholders sn = e("1 2 ~ ~ ~", 5, 2) self.assertEqual(len(sn), 5) @@ -539,6 +538,16 @@ class TestSerialNumberExtraction(TestCase): with self.assertRaises(ValidationError): e("1, 2, 3, E-5", 5, 1) + # Extract a range of values with a smaller range + with self.assertRaises(ValidationError) as exc: + e("11-50", 10, 1) + self.assertIn('Range quantity exceeds 10', str(exc)) + + # Test groups are not interpolated with alpha characters + with self.assertRaises(ValidationError) as exc: + e("1, A-2, 3+", 5, 1) + self.assertIn('Invalid group range: A-2', str(exc)) + def test_combinations(self): """Test complex serial number combinations.""" e = helpers.extract_serial_numbers @@ -559,6 +568,24 @@ class TestSerialNumberExtraction(TestCase): self.assertEqual(len(sn), 2) self.assertEqual(sn, ['14', '15']) + # Test multiple increment groups + sn = e("~+4, 20+4, 30+4", 15, 10) + self.assertEqual(len(sn), 15) + + for v in [14, 24, 34]: + self.assertIn(str(v), sn) + + # Test multiple range groups + sn = e("11-20, 41-50, 91-100", 30, 1) + self.assertEqual(len(sn), 30) + + for v in range(11, 21): + self.assertIn(str(v), sn) + for v in range(41, 51): + self.assertIn(str(v), sn) + for v in range(91, 101): + self.assertIn(str(v), sn) + class TestVersionNumber(TestCase): """Unit tests for version number functions."""