From f8ce23217e617d6b7e3df666ef49bfe1f46d0d6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 02:22:10 +0100 Subject: [PATCH 01/15] simplify call --- InvenTree/stock/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index 9961bb7bae..d69c94b4a8 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -471,7 +471,7 @@ class StockList(generics.ListCreateAPIView): if serial_numbers: # If serial numbers are specified, check that they match! try: - serials = extract_serial_numbers(serial_numbers, data['quantity']) + serials = extract_serial_numbers(serial_numbers, quantity) except DjangoValidationError as e: raise ValidationError({ 'quantity': e.messages, From c9f0528dfc255425b2a15257d4236259c5878d1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 02:24:00 +0100 Subject: [PATCH 02/15] append api call --- InvenTree/InvenTree/helpers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index fd86306627..a97b32a9ba 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -404,17 +404,19 @@ def DownloadFile(data, filename, content_type='application/text', inline=False): return response -def extract_serial_numbers(serials, expected_quantity): +def extract_serial_numbers(serials, expected_quantity, next_number: int): """ Attempt to extract serial numbers from an input string. - Serial numbers must be integer values - Serial numbers must be positive - Serial numbers can be split by whitespace / newline / commma chars - Serial numbers can be supplied as an inclusive range using hyphen char e.g. 10-20 + - Serial numbers can be defined as ~ for getting the next available serial number - Serial numbers can be supplied as + for getting all expecteded numbers starting from - Serial numbers can be supplied as + for getting numbers starting from Args: expected_quantity: The number of (unique) serial numbers we expect + next_number(int): the next possible serial number """ serials = serials.strip() From 750e3297ccc68a579e0611ebceeef15c52d68932 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 02:24:13 +0100 Subject: [PATCH 03/15] expand docstring --- InvenTree/InvenTree/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index a97b32a9ba..42dae832c8 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -415,6 +415,7 @@ def extract_serial_numbers(serials, expected_quantity, next_number: int): - Serial numbers can be supplied as + for getting numbers starting from Args: + serials: input string with patterns expected_quantity: The number of (unique) serial numbers we expect next_number(int): the next possible serial number """ From 7ef0e7941e15c8c26e3ba9f5927c1736191fb1fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 02:24:57 +0100 Subject: [PATCH 04/15] expand tests TDD for the win? --- InvenTree/InvenTree/tests.py | 40 +++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 06dcad9797..91b94deb1c 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -237,52 +237,68 @@ class TestSerialNumberExtraction(TestCase): e = helpers.extract_serial_numbers - sn = e("1-5", 5) - self.assertEqual(len(sn), 5) + sn = e("1-5", 5, 1) + self.assertEqual(len(sn), 5, 1) for i in range(1, 6): self.assertIn(i, sn) - sn = e("1, 2, 3, 4, 5", 5) + sn = e("1, 2, 3, 4, 5", 5, 1) self.assertEqual(len(sn), 5) - sn = e("1-5, 10-15", 11) + sn = e("1-5, 10-15", 11, 1) self.assertIn(3, sn) self.assertIn(13, sn) - sn = e("1+", 10) + sn = e("1+", 10, 1) self.assertEqual(len(sn), 10) self.assertEqual(sn, [_ for _ in range(1, 11)]) - sn = e("4, 1+2", 4) + sn = e("4, 1+2", 4, 1) self.assertEqual(len(sn), 4) self.assertEqual(sn, ["4", 1, 2, 3]) + sn = e("~", 1, 1) + self.assertEqual(len(sn), 1) + self.assertEqual(sn, [1]) + + sn = e("~", 1, 3) + self.assertEqual(len(sn), 1) + self.assertEqual(sn, [3]) + + sn = e("~+", 2, 5) + self.assertEqual(len(sn), 2) + self.assertEqual(sn, [5, 6]) + + sn = e("~+3", 2, 5) + self.assertEqual(len(sn), 4) + self.assertEqual(sn, [5, 6, 7, 8]) + def test_failures(self): e = helpers.extract_serial_numbers # Test duplicates with self.assertRaises(ValidationError): - e("1,2,3,3,3", 5) + e("1,2,3,3,3", 5, 1) # Test invalid length with self.assertRaises(ValidationError): - e("1,2,3", 5) + e("1,2,3", 5, 1) # Test empty string with self.assertRaises(ValidationError): - e(", , ,", 0) + e(", , ,", 0, 1) # Test incorrect sign in group with self.assertRaises(ValidationError): - e("10-2", 8) + e("10-2", 8, 1) # Test invalid group with self.assertRaises(ValidationError): - e("1-5-10", 10) + e("1-5-10", 10, 1) with self.assertRaises(ValidationError): - e("10, a, 7-70j", 4) + e("10, a, 7-70j", 4, 1) class TestVersionNumber(TestCase): From 771f3d6b7b8deaa77bc1a12c2a57fa362c40b3cd Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 02:38:32 +0100 Subject: [PATCH 05/15] add fnc for new matcher --- InvenTree/InvenTree/helpers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 42dae832c8..e030d51abf 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -422,6 +422,10 @@ def extract_serial_numbers(serials, expected_quantity, next_number: int): serials = serials.strip() + # fill in the next serial number into the serial + if '~' in serials: + serials = serials.replace('~', next_number) + groups = re.split("[\s,]+", serials) numbers = [] From 1d0aa900e8b6bfb4d5ccd5ec94cbffb902cc8ad8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 02:50:23 +0100 Subject: [PATCH 06/15] add helper fnc to get latest SN as int --- InvenTree/part/models.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 5c6c07d9d8..4f06d43e60 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -583,6 +583,26 @@ class Part(MPTTModel): # No serial numbers found return None + def getLatestSerialNumberInt(self): + """ + Return the "latest" serial number for this Part as a integer. + If it is not an integer the result is 0 + """ + + latest = self.getLatestSerialNumber() + + # No serial number = > 0 + if latest is None: + latest = 0 + + # Attempt to turn into an integer and return + try: + latest = int(latest) + return latest + except: + # not an integer so 0 + return 0 + def getSerialNumberString(self, quantity=1): """ Return a formatted string representing the next available serial numbers, From 2ec41b02f82336a121110611e277e2a0dd82f4b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 03:01:52 +0100 Subject: [PATCH 07/15] append information about next serial numbe to fnc call Fixes #2484 --- InvenTree/build/views.py | 4 ++-- InvenTree/order/serializers.py | 2 +- InvenTree/stock/api.py | 25 +++++++++++++------------ InvenTree/stock/serializers.py | 3 ++- InvenTree/stock/views.py | 4 ++-- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index fd730b6a7e..1d28cb8d50 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -109,7 +109,7 @@ class BuildOutputCreate(AjaxUpdateView): # Check that the serial numbers are valid if serials: try: - extracted = extract_serial_numbers(serials, quantity) + extracted = extract_serial_numbers(serials, quantity, build.part.getLatestSerialNumberInt()) if extracted: # Check for conflicting serial numbers @@ -143,7 +143,7 @@ class BuildOutputCreate(AjaxUpdateView): serials = data.get('serial_numbers', None) if serials: - serial_numbers = extract_serial_numbers(serials, quantity) + serial_numbers = extract_serial_numbers(serials, quantity, build.part.getLatestSerialNumberInt()) else: serial_numbers = None diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 32e50943b1..6097a707c7 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -861,7 +861,7 @@ class SOSerialAllocationSerializer(serializers.Serializer): part = line_item.part try: - data['serials'] = extract_serial_numbers(serial_numbers, quantity) + data['serials'] = extract_serial_numbers(serial_numbers, quantity, part.getLatestSerialNumberInt()) except DjangoValidationError as e: raise ValidationError({ 'serial_numbers': e.messages, diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py index d69c94b4a8..fa15708b6d 100644 --- a/InvenTree/stock/api.py +++ b/InvenTree/stock/api.py @@ -466,18 +466,6 @@ class StockList(generics.ListCreateAPIView): notes = data.get('notes', '') - serials = None - - if serial_numbers: - # If serial numbers are specified, check that they match! - try: - serials = extract_serial_numbers(serial_numbers, quantity) - except DjangoValidationError as e: - raise ValidationError({ - 'quantity': e.messages, - 'serial_numbers': e.messages, - }) - with transaction.atomic(): # Create an initial stock item @@ -493,6 +481,19 @@ class StockList(generics.ListCreateAPIView): if item.part.default_expiry > 0: item.expiry_date = datetime.now().date() + timedelta(days=item.part.default_expiry) + # fetch serial numbers + serials = None + + if serial_numbers: + # If serial numbers are specified, check that they match! + try: + serials = extract_serial_numbers(serial_numbers, quantity, item.part.getLatestSerialNumberInt()) + except DjangoValidationError as e: + raise ValidationError({ + 'quantity': e.messages, + 'serial_numbers': e.messages, + }) + # Finally, save the item (with user information) item.save(user=user) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index e69cd90f82..5379273a2e 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -350,7 +350,7 @@ class SerializeStockItemSerializer(serializers.Serializer): serial_numbers = data['serial_numbers'] try: - serials = InvenTree.helpers.extract_serial_numbers(serial_numbers, quantity) + serials = InvenTree.helpers.extract_serial_numbers(serial_numbers, quantity, item.part.getLatestSerialNumberInt()) except DjangoValidationError as e: raise ValidationError({ 'serial_numbers': e.messages, @@ -379,6 +379,7 @@ class SerializeStockItemSerializer(serializers.Serializer): serials = InvenTree.helpers.extract_serial_numbers( data['serial_numbers'], data['quantity'], + item.part.getLatestSerialNumberInt() ) item.serializeStock( diff --git a/InvenTree/stock/views.py b/InvenTree/stock/views.py index 27801f0ed6..6c89db0f2f 100644 --- a/InvenTree/stock/views.py +++ b/InvenTree/stock/views.py @@ -1241,7 +1241,7 @@ class StockItemCreate(AjaxCreateView): if len(sn) > 0: try: - serials = extract_serial_numbers(sn, quantity) + serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt()) except ValidationError as e: serials = None form.add_error('serial_numbers', e.messages) @@ -1283,7 +1283,7 @@ class StockItemCreate(AjaxCreateView): # Create a single stock item for each provided serial number if len(sn) > 0: - serials = extract_serial_numbers(sn, quantity) + serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt()) for serial in serials: item = StockItem( From a745aa500e79789af3a19fba12116dec700b64b9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 03:02:43 +0100 Subject: [PATCH 08/15] replace must be str --- InvenTree/InvenTree/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index e030d51abf..4848b49e33 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -424,7 +424,7 @@ def extract_serial_numbers(serials, expected_quantity, next_number: int): # fill in the next serial number into the serial if '~' in serials: - serials = serials.replace('~', next_number) + serials = serials.replace('~', str(next_number)) groups = re.split("[\s,]+", serials) From b1188be019c3ac4f84b98e0f19e74932e3d84df6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 15:34:03 +0100 Subject: [PATCH 09/15] fix test assertations --- InvenTree/InvenTree/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 91b94deb1c..843cf0db3c 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -259,17 +259,17 @@ class TestSerialNumberExtraction(TestCase): sn = e("~", 1, 1) self.assertEqual(len(sn), 1) - self.assertEqual(sn, [1]) + self.assertEqual(sn, ['1']) sn = e("~", 1, 3) self.assertEqual(len(sn), 1) - self.assertEqual(sn, [3]) + self.assertEqual(sn, ['3']) sn = e("~+", 2, 5) self.assertEqual(len(sn), 2) self.assertEqual(sn, [5, 6]) - sn = e("~+3", 2, 5) + sn = e("~+3", 4, 5) self.assertEqual(len(sn), 4) self.assertEqual(sn, [5, 6, 7, 8]) From 1eb9b395bc976f1e3d7fabf1e7dadd6f9096fc96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 15:43:34 +0100 Subject: [PATCH 10/15] more complicated tests --- InvenTree/InvenTree/tests.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 843cf0db3c..e4ab3c7a0c 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -301,6 +301,26 @@ class TestSerialNumberExtraction(TestCase): e("10, a, 7-70j", 4, 1) + def test_combinations(self): + e = helpers.extract_serial_numbers + + sn = e("1 3-5 9+2", 7, 1) + self.assertEqual(len(sn), 7) + self.assertEqual(sn, ['1', 3, 4, 5, 9, 10, 11]) + + sn = e("1,3-5,9+2", 7, 1) + self.assertEqual(len(sn), 7) + self.assertEqual(sn, ['1', 3, 4, 5, 9, 10, 11]) + + sn = e("~+2", 3, 14) + self.assertEqual(len(sn), 3) + self.assertEqual(sn, [14, 15, 16]) + + sn = e("~+", 2, 14) + self.assertEqual(len(sn), 2) + self.assertEqual(sn, [14, 15]) + + class TestVersionNumber(TestCase): """ Unit tests for version number functions From 624b16a4192d76a3342774ded2aaaa06e869cef6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 15:51:44 +0100 Subject: [PATCH 11/15] fix pep style --- InvenTree/InvenTree/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index e4ab3c7a0c..0e99962afb 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -300,7 +300,6 @@ class TestSerialNumberExtraction(TestCase): with self.assertRaises(ValidationError): e("10, a, 7-70j", 4, 1) - def test_combinations(self): e = helpers.extract_serial_numbers From 972f93f6d82898eeeba690a9afb1b4773659088f Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 15:53:04 +0100 Subject: [PATCH 12/15] always return a number --- InvenTree/InvenTree/helpers.py | 2 +- InvenTree/InvenTree/tests.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 4848b49e33..44d49c445f 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -504,7 +504,7 @@ def extract_serial_numbers(serials, expected_quantity, next_number: int): if group in numbers: errors.append(_("Duplicate serial: {g}".format(g=group))) else: - numbers.append(group) + numbers.append(int(group)) if len(errors) > 0: raise ValidationError(errors) diff --git a/InvenTree/InvenTree/tests.py b/InvenTree/InvenTree/tests.py index 0e99962afb..7fd62908a0 100644 --- a/InvenTree/InvenTree/tests.py +++ b/InvenTree/InvenTree/tests.py @@ -255,15 +255,15 @@ class TestSerialNumberExtraction(TestCase): sn = e("4, 1+2", 4, 1) self.assertEqual(len(sn), 4) - self.assertEqual(sn, ["4", 1, 2, 3]) + self.assertEqual(sn, [4, 1, 2, 3]) sn = e("~", 1, 1) self.assertEqual(len(sn), 1) - self.assertEqual(sn, ['1']) + self.assertEqual(sn, [1]) sn = e("~", 1, 3) self.assertEqual(len(sn), 1) - self.assertEqual(sn, ['3']) + self.assertEqual(sn, [3]) sn = e("~+", 2, 5) self.assertEqual(len(sn), 2) @@ -305,11 +305,11 @@ class TestSerialNumberExtraction(TestCase): sn = e("1 3-5 9+2", 7, 1) self.assertEqual(len(sn), 7) - self.assertEqual(sn, ['1', 3, 4, 5, 9, 10, 11]) + self.assertEqual(sn, [1, 3, 4, 5, 9, 10, 11]) sn = e("1,3-5,9+2", 7, 1) self.assertEqual(len(sn), 7) - self.assertEqual(sn, ['1', 3, 4, 5, 9, 10, 11]) + self.assertEqual(sn, [1, 3, 4, 5, 9, 10, 11]) sn = e("~+2", 3, 14) self.assertEqual(len(sn), 3) From 410c190ddfb4033b6189278f34eff9826ae3c086 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 15:53:23 +0100 Subject: [PATCH 13/15] more docs --- InvenTree/InvenTree/helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 44d49c445f..669cc56029 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -500,6 +500,7 @@ def extract_serial_numbers(serials, expected_quantity, next_number: int): errors.append(_("Invalid group: {g}").format(g=group)) continue + # Group is a number else: if group in numbers: errors.append(_("Duplicate serial: {g}".format(g=group))) From ed09d5015a07739e2fa22849f6aef7bef77fff75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 16:13:44 +0100 Subject: [PATCH 14/15] better group handeling --- InvenTree/InvenTree/helpers.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 669cc56029..96fa5c7608 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -11,6 +11,7 @@ from PIL import Image from decimal import Decimal, InvalidOperation from wsgiref.util import FileWrapper +from django.db.models.expressions import RawSQL from django.http import StreamingHttpResponse from django.core.exceptions import ValidationError, FieldError from django.utils.translation import ugettext_lazy as _ @@ -500,12 +501,20 @@ def extract_serial_numbers(serials, expected_quantity, next_number: int): errors.append(_("Invalid group: {g}").format(g=group)) continue - # Group is a number + # Group should be a number + elif group: + # try conversion + try: + number = int(group) + except: + # seem like it is not a number + raise ValidationError(_(f"Invalid group {group}")) + + number_add(number) + + # No valid input group detected else: - if group in numbers: - errors.append(_("Duplicate serial: {g}".format(g=group))) - else: - numbers.append(int(group)) + raise ValidationError(_(f"Invalid/no group {group}")) if len(errors) > 0: raise ValidationError(errors) From f2c1fe1f9513ab6f05ee4338d362b0a2042fcb7b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Dec 2021 16:16:20 +0100 Subject: [PATCH 15/15] damm vscode intellisense adding stuff I do not need --- InvenTree/InvenTree/helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 96fa5c7608..1418fddd41 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -11,7 +11,6 @@ from PIL import Image from decimal import Decimal, InvalidOperation from wsgiref.util import FileWrapper -from django.db.models.expressions import RawSQL from django.http import StreamingHttpResponse from django.core.exceptions import ValidationError, FieldError from django.utils.translation import ugettext_lazy as _