From f7ed48809cb0d73f21b488988f067dcecdc33099 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Fri, 21 Aug 2020 17:36:49 +0100 Subject: [PATCH 1/8] Support non-integer serial numbers --- InvenTree/InvenTree/helpers.py | 12 ++++-------- InvenTree/part/models.py | 4 +++- .../migrations/0050_auto_20200821_1403.py | 18 ++++++++++++++++++ InvenTree/stock/models.py | 7 ++----- InvenTree/stock/serializers.py | 2 +- 5 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 InvenTree/stock/migrations/0050_auto_20200821_1403.py diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 28cebbcd3d..4ec84c7912 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -371,14 +371,10 @@ def ExtractSerialNumbers(serials, expected_quantity): continue else: - try: - n = int(group) - if n in numbers: - errors.append(_("Duplicate serial: {n}".format(n=n))) - else: - numbers.append(n) - except ValueError: - errors.append(_("Invalid group: {g}".format(g=group))) + if group in numbers: + errors.append(_("Duplicate serial: {g}".format(g=group))) + else: + numbers.append(group) if len(errors) > 0: raise ValidationError(errors) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index dd126d5730..6d42b08101 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -346,8 +346,10 @@ class Part(MPTTModel): if n is None: return 1 - else: + elif n is int: return n + 1 + else: + return None def getSerialNumberString(self, quantity): """ diff --git a/InvenTree/stock/migrations/0050_auto_20200821_1403.py b/InvenTree/stock/migrations/0050_auto_20200821_1403.py new file mode 100644 index 0000000000..fa02c0d0f7 --- /dev/null +++ b/InvenTree/stock/migrations/0050_auto_20200821_1403.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-08-21 14:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stock', '0049_auto_20200820_0454'), + ] + + operations = [ + migrations.AlterField( + model_name='stockitem', + name='serial', + field=models.CharField(blank=True, help_text='Serial number for this item', max_length=100, null=True, verbose_name='Serial Number'), + ), + ] diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 49c185220f..c10295ee6a 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -355,9 +355,9 @@ class StockItem(MPTTModel): verbose_name=_("Customer"), ) - serial = models.PositiveIntegerField( + serial = models.CharField( verbose_name=_('Serial Number'), - blank=True, null=True, + max_length=100, blank=True, null=True, help_text=_('Serial number for this item') ) @@ -687,9 +687,6 @@ class StockItem(MPTTModel): if not type(serials) in [list, tuple]: raise ValidationError({"serial_numbers": _("Serial numbers must be a list of integers")}) - if any([type(i) is not int for i in serials]): - raise ValidationError({"serial_numbers": _("Serial numbers must be a list of integers")}) - if not quantity == len(serials): raise ValidationError({"quantity": _("Quantity does not match serial numbers")}) diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index bed3f8f7c1..2967538e88 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -129,7 +129,7 @@ class StockItemSerializer(InvenTreeModelSerializer): allocated = serializers.FloatField(source='allocation_count', required=False) - serial = serializers.IntegerField(required=False) + serial = serializers.CharField(required=False) required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False) From c31b30bf83c27bb4c2ac212bfff00fac4c30b833 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Fri, 21 Aug 2020 18:53:51 +0100 Subject: [PATCH 2/8] Fix simple tests --- InvenTree/build/test_build.py | 2 +- InvenTree/part/models.py | 8 +++++--- InvenTree/stock/tests.py | 5 +---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/InvenTree/build/test_build.py b/InvenTree/build/test_build.py index 32ad33dab3..bb7931d2f1 100644 --- a/InvenTree/build/test_build.py +++ b/InvenTree/build/test_build.py @@ -220,5 +220,5 @@ class BuildTest(TestCase): # And a new stock item created for the build output self.assertEqual(StockItem.objects.get(pk=7).quantity, 1) - self.assertEqual(StockItem.objects.get(pk=7).serial, 1) + self.assertEqual(StockItem.objects.get(pk=7).serial, "1") self.assertEqual(StockItem.objects.get(pk=7).build, self.build) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 6d42b08101..cea78543c6 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -346,10 +346,12 @@ class Part(MPTTModel): if n is None: return 1 - elif n is int: - return n + 1 else: - return None + try: + return int(n) + 1 + except ValueError: + return None + def getSerialNumberString(self, quantity): """ diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 513368c422..03a04b73d8 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -295,10 +295,7 @@ class StockTest(TestCase): with self.assertRaises(ValidationError): item.serializeStock(-1, [], self.user) - # Try invalid serial numbers - with self.assertRaises(ValidationError): - item.serializeStock(3, [1, 2, 'k'], self.user) - + # Not enough serial numbers for all stock items. with self.assertRaises(ValidationError): item.serializeStock(3, "hello", self.user) From 23cc3d9b060e6651cf2b8dc385838989777b8760 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Fri, 21 Aug 2020 19:17:58 +0100 Subject: [PATCH 3/8] Handle 'next serial' more gracefully --- InvenTree/part/models.py | 5 ++++- InvenTree/stock/tests.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index cea78543c6..fba217e0d9 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -14,6 +14,8 @@ from django.urls import reverse from django.db import models, transaction from django.db.models import Sum from django.db.models.functions import Coalesce +from django.db.models import IntegerField +from django.db.models.functions import Cast from django.core.validators import MinValueValidator from django.contrib.auth.models import User @@ -329,7 +331,8 @@ class Part(MPTTModel): """ parts = Part.objects.filter(tree_id=self.tree_id) - stock = StockModels.StockItem.objects.filter(part__in=parts).exclude(serial=None).order_by('-serial') + stock = StockModels.StockItem.objects.filter(part__in=parts).exclude(serial=None).annotate( + serial_as_int=Cast('serial', output_field=IntegerField())).order_by('-serial_as_int') if stock.count() > 0: return stock.first().serial diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 03a04b73d8..5442584b7d 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -391,8 +391,8 @@ class VariantTest(StockTest): with self.assertRaises(ValidationError): item.save() - # This should pass - item.serial = n + 1 + # This should pass, although not strictly an int field now. + item.serial = int(n) + 1 item.save() # Attempt to create the same serial number but for a variant (should fail!) From d5a374f1fda3e738f706913bc61bcb21eaca5661 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Mon, 24 Aug 2020 19:49:32 +0100 Subject: [PATCH 4/8] Make serial number suggestion DB independent, handle mixed types more cleanly and test --- InvenTree/part/models.py | 26 +++++++++++++++----------- InvenTree/stock/tests.py | 5 +++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index b8e9c859bb..8a62746bd5 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -330,14 +330,21 @@ class Part(MPTTModel): """ parts = Part.objects.filter(tree_id=self.tree_id) - stock = StockModels.StockItem.objects.filter(part__in=parts).exclude(serial=None).annotate( - serial_as_int=Cast('serial', output_field=IntegerField())).order_by('-serial_as_int') - - if stock.count() > 0: - return stock.first().serial + stock = StockModels.StockItem.objects.filter(part__in=parts).exclude(serial=None) + try: + ordered = sorted(stock.all(), reverse=True, key=lambda n: int(n.serial)) + + if len(ordered) > 0: + return ordered[0].serial + + # Non-numeric serials, so don't suggest one. + except ValueError: + return None + # No serial numbers found - return None + return 0 + def getNextSerialNumber(self): """ @@ -347,12 +354,9 @@ class Part(MPTTModel): n = self.getHighestSerialNumber() if n is None: - return 1 + return None else: - try: - return int(n) + 1 - except ValueError: - return None + return int(n) + 1 def getSerialNumberString(self, quantity): diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 5442584b7d..df37153559 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -391,6 +391,11 @@ class VariantTest(StockTest): with self.assertRaises(ValidationError): item.save() + # Verify items with a non-numeric serial don't offer a next serial. + item.serial="string" + item.save() + self.assertEqual(variant.getNextSerialNumber(), None) + # This should pass, although not strictly an int field now. item.serial = int(n) + 1 item.save() From 0da2682c689ae9642ceef527023c897fdef787b4 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Mon, 24 Aug 2020 20:00:19 +0100 Subject: [PATCH 5/8] handle non-int serial range suggestions cleanly --- InvenTree/part/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 8a62746bd5..5a3fd229b4 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -367,6 +367,9 @@ class Part(MPTTModel): sn = self.getNextSerialNumber() + if sn is None: + return None + if quantity >= 2: sn = "{n}-{m}".format( n=sn, From 9c2d13b487825ad6734e49a3dbc2ba7e347354da Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Mon, 24 Aug 2020 20:04:10 +0100 Subject: [PATCH 6/8] test non-numeric serials are handled correctly for ranges --- InvenTree/stock/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index df37153559..5f4e140ebd 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -396,6 +396,9 @@ class VariantTest(StockTest): item.save() self.assertEqual(variant.getNextSerialNumber(), None) + # And the same for the range when serializing. + self.assertEqual(variant.getSerialNumberString(5), None) + # This should pass, although not strictly an int field now. item.serial = int(n) + 1 item.save() From e0a744b01dc991a47310d492e8f70c0f04e465c5 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Mon, 24 Aug 2020 20:09:06 +0100 Subject: [PATCH 7/8] Remove no-longer-needed cast functions --- InvenTree/part/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 5a3fd229b4..49dd572d8c 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -14,8 +14,6 @@ from django.urls import reverse from django.db import models, transaction from django.db.models import Sum from django.db.models.functions import Coalesce -from django.db.models import IntegerField -from django.db.models.functions import Cast from django.core.validators import MinValueValidator from django.contrib.auth.models import User From 06552832cb6e9323f2cf0951c19205b526771e33 Mon Sep 17 00:00:00 2001 From: Ben Charlton Date: Fri, 28 Aug 2020 17:30:16 +0100 Subject: [PATCH 8/8] Style corrections --- InvenTree/part/models.py | 2 -- InvenTree/stock/tests.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 49cd26b039..0bf3e0bb35 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -343,7 +343,6 @@ class Part(MPTTModel): # No serial numbers found return 0 - def getNextSerialNumber(self): """ Return the next-available serial number for this Part. @@ -356,7 +355,6 @@ class Part(MPTTModel): else: return int(n) + 1 - def getSerialNumberString(self, quantity): """ Return a formatted string representing the next available serial numbers, diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 5f4e140ebd..bac7e735f8 100644 --- a/InvenTree/stock/tests.py +++ b/InvenTree/stock/tests.py @@ -392,7 +392,7 @@ class VariantTest(StockTest): item.save() # Verify items with a non-numeric serial don't offer a next serial. - item.serial="string" + item.serial = "string" item.save() self.assertEqual(variant.getNextSerialNumber(), None)