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/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 26dd04ac52..0d19a0afb9 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -328,13 +328,20 @@ 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') - - 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): """ @@ -344,9 +351,9 @@ class Part(MPTTModel): n = self.getHighestSerialNumber() if n is None: - return 1 + return None else: - return n + 1 + return int(n) + 1 def getSerialNumberString(self, quantity): """ @@ -356,6 +363,9 @@ class Part(MPTTModel): sn = self.getNextSerialNumber() + if sn is None: + return None + if quantity >= 2: sn = "{n}-{m}".format( n=sn, 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 cccc138523..f50b8572c9 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -113,7 +113,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) diff --git a/InvenTree/stock/tests.py b/InvenTree/stock/tests.py index 513368c422..bac7e735f8 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) @@ -394,8 +391,16 @@ class VariantTest(StockTest): with self.assertRaises(ValidationError): item.save() - # This should pass - item.serial = n + 1 + # Verify items with a non-numeric serial don't offer a next serial. + item.serial = "string" + 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() # Attempt to create the same serial number but for a variant (should fail!)