2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-01 03:00:54 +00:00

More serial number validation and unit testing

-
This commit is contained in:
Oliver Walters
2020-05-16 08:43:57 +10:00
parent 2d6c531fda
commit ea88a03b5a
7 changed files with 346 additions and 65 deletions

View File

@ -68,4 +68,160 @@
level: 0
tree_id: 0
lft: 0
rght: 0
# Stock items for template / variant parts
- model: stock.stockitem
pk: 500
fields:
part: 10001
location: 7
quantity: 5
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 501
fields:
part: 10001
location: 7
quantity: 1
serial: 1
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 501
fields:
part: 10001
location: 7
quantity: 1
serial: 1
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 502
fields:
part: 10001
location: 7
quantity: 1
serial: 2
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 503
fields:
part: 10001
location: 7
quantity: 1
serial: 3
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 504
fields:
part: 10001
location: 7
quantity: 1
serial: 4
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 505
fields:
part: 10001
location: 7
quantity: 1
serial: 5
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 510
fields:
part: 10002
location: 7
quantity: 1
serial: 10
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 511
fields:
part: 10002
location: 7
quantity: 1
serial: 11
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 512
fields:
part: 10002
location: 7
quantity: 1
serial: 12
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 520
fields:
part: 10004
location: 7
quantity: 1
serial: 20
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 521
fields:
part: 10004
location: 7
quantity: 1
serial: 21
level: 0
tree_id: 0
lft: 0
rght: 0
- model: stock.stockitem
pk: 522
fields:
part: 10004
location: 7
quantity: 1
serial: 22
level: 0
tree_id: 0
lft: 0
rght: 0

View File

@ -142,11 +142,31 @@ class StockItem(MPTTModel):
)
def save(self, *args, **kwargs):
"""
Save this StockItem to the database. Performs a number of checks:
- Unique serial number requirement
- Adds a transaction note when the item is first created.
"""
# Query to look for duplicate serial numbers
parts = PartModels.Part.objects.filter(tree_id=self.part.tree_id)
stock = StockItem.objects.filter(part__in=parts, serial=self.serial)
if not self.pk:
# StockItem has not yet been saved
add_note = True
else:
# StockItem has already been saved
add_note = False
stock = stock.exclude(pk=self.pk)
if self.serial is not None:
# Check for presence of stock with same serial number
if stock.exists():
raise ValidationError({"serial": _("StockItem with this serial number already exists")})
user = kwargs.pop('user', None)
add_note = add_note and kwargs.pop('note', True)
@ -172,37 +192,6 @@ class StockItem(MPTTModel):
""" Return True if this StockItem is serialized """
return self.serial is not None and self.quantity == 1
@classmethod
def check_serial_number(cls, part, serial_number):
""" Check if a new stock item can be created with the provided part_id
Args:
part: The part to be checked
"""
if not part.trackable:
return False
# Return False if an invalid serial number is supplied
try:
serial_number = int(serial_number)
except ValueError:
return False
items = StockItem.objects.filter(serial=serial_number)
# Is this part a variant? If so, check S/N across all sibling variants
if part.variant_of is not None:
items = items.filter(part__variant_of=part.variant_of)
else:
items = items.filter(part=part)
# An existing serial number exists
if items.exists():
return False
return True
def validate_unique(self, exclude=None):
super(StockItem, self).validate_unique(exclude)
@ -210,18 +199,21 @@ class StockItem(MPTTModel):
# ensure that the serial number is unique
# across all variants of the same template part
print("validating...")
print(self.pk, self.serial)
try:
if self.serial is not None:
# This is a variant part (check S/N across all sibling variants)
if self.part.variant_of is not None:
if StockItem.objects.filter(part__variant_of=self.part.variant_of, serial=self.serial).exclude(id=self.id).exists():
raise ValidationError({
'serial': _('A stock item with this serial number already exists for template part {part}'.format(part=self.part.variant_of))
})
else:
if StockItem.objects.filter(part=self.part, serial=self.serial).exclude(id=self.id).exists():
raise ValidationError({
'serial': _('A stock item with this serial number already exists')
parts = PartModels.Part.objects.filter(tree_id=self.part.tree_id)
stock = StockItem.objects.filter(
part__in=parts,
serial=self.serial,
).exclude(pk=self.pk)
if stock.exists():
raise ValidationError({
'serial': _('A stock item with this serial number already exists for this part'),
})
except PartModels.Part.DoesNotExist:
pass
@ -599,6 +591,9 @@ class StockItem(MPTTModel):
if self.serialized:
return
if not self.part.trackable:
raise ValidationError({"part": _("Part is not set as trackable")})
# Quantity must be a valid integer value
try:
quantity = int(quantity)
@ -624,7 +619,7 @@ class StockItem(MPTTModel):
existing = []
for serial in serials:
if not StockItem.check_serial_number(self.part, serial):
if self.part.check_if_serial_number_exists(serial):
existing.append(serial)
if len(existing) > 0:

View File

@ -38,6 +38,10 @@ class StockTest(TestCase):
self.user = User.objects.get(username='username')
# Ensure the MPTT objects are correctly rebuild
Part.objects.rebuild()
StockItem.objects.rebuild()
def test_loc_count(self):
self.assertEqual(StockLocation.objects.count(), 7)
@ -91,13 +95,16 @@ class StockTest(TestCase):
self.assertFalse(self.drawer2.has_items())
# Drawer 3 should have three stock items
self.assertEqual(self.drawer3.stock_items.count(), 3)
self.assertEqual(self.drawer3.item_count, 3)
self.assertEqual(self.drawer3.stock_items.count(), 15)
self.assertEqual(self.drawer3.item_count, 15)
def test_stock_count(self):
part = Part.objects.get(pk=1)
entries = part.stock_entries()
# There should be 5000 screws in stock
self.assertEqual(entries.count(), 2)
# There should be 9000 screws in stock
self.assertEqual(part.total_stock, 9000)
# There should be 18 widgets in stock
@ -301,6 +308,7 @@ class StockTest(TestCase):
item.delete_on_deplete = True
item.save()
n = StockItem.objects.filter(part=25).count()
self.assertEqual(item.quantity, 10)
@ -327,3 +335,73 @@ class StockTest(TestCase):
# Serialize the remainder of the stock
item.serializeStock(2, [99, 100], self.user)
class VariantTest(StockTest):
"""
Tests for calculation stock counts against templates / variants
"""
def test_variant_stock(self):
# Check the 'Chair' variant
chair = Part.objects.get(pk=10000)
# No stock items for the variant part itself
self.assertEqual(chair.stock_entries(include_variants=False).count(), 0)
self.assertEqual(chair.stock_entries().count(), 12)
green = Part.objects.get(pk=10003)
self.assertEqual(green.stock_entries(include_variants=False).count(), 0)
self.assertEqual(green.stock_entries().count(), 3)
def test_serial_numbers(self):
# Test serial number functionality for variant / template parts
chair = Part.objects.get(pk=10000)
# Operations on the top-level object
self.assertTrue(chair.check_if_serial_number_exists(1))
self.assertTrue(chair.check_if_serial_number_exists(2))
self.assertTrue(chair.check_if_serial_number_exists(3))
self.assertTrue(chair.check_if_serial_number_exists(4))
self.assertTrue(chair.check_if_serial_number_exists(5))
self.assertTrue(chair.check_if_serial_number_exists(20))
self.assertTrue(chair.check_if_serial_number_exists(21))
self.assertTrue(chair.check_if_serial_number_exists(22))
self.assertFalse(chair.check_if_serial_number_exists(30))
self.assertEqual(chair.get_next_serial_number(), 23)
# Same operations on a sub-item
variant = Part.objects.get(pk=10003)
self.assertEqual(variant.get_next_serial_number(), 23)
# Create a new serial number
n = variant.get_highest_serial_number()
item = StockItem(
part=variant,
quantity=1,
serial=n
)
# This should fail
with self.assertRaises(ValidationError):
item.save()
# This should pass
item.serial = n + 1
item.save()
# Attempt to create the same serial number but for a variant (should fail!)
item.pk = None
item.part = Part.objects.get(pk=10004)
with self.assertRaises(ValidationError):
item.save()
item.serial += 1
item.save()