mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #2721 from SchrodingersGat/big-serial-fix
Big serial fix
This commit is contained in:
commit
6e82709a48
@ -133,7 +133,7 @@ class ReferenceIndexingMixin(models.Model):
|
||||
reference_int = models.BigIntegerField(default=0)
|
||||
|
||||
|
||||
def extract_int(reference):
|
||||
def extract_int(reference, clip=0x7fffffff):
|
||||
# Default value if we cannot convert to an integer
|
||||
ref_int = 0
|
||||
|
||||
@ -146,6 +146,15 @@ def extract_int(reference):
|
||||
ref_int = int(ref)
|
||||
except:
|
||||
ref_int = 0
|
||||
|
||||
# Ensure that the returned values are within the range that can be stored in an IntegerField
|
||||
# Note: This will result in large values being "clipped"
|
||||
if clip is not None:
|
||||
if ref_int > clip:
|
||||
ref_int = clip
|
||||
elif ref_int < -clip:
|
||||
ref_int = -clip
|
||||
|
||||
return ref_int
|
||||
|
||||
|
||||
|
@ -112,17 +112,16 @@ class PurchaseOrderTest(OrderTest):
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
url = reverse('api-po-list')
|
||||
huge_numer = 9223372036854775808
|
||||
huge_number = 9223372036854775808
|
||||
|
||||
# too big
|
||||
self.post(
|
||||
url,
|
||||
{
|
||||
'supplier': 1,
|
||||
'reference': huge_numer,
|
||||
'reference': huge_number,
|
||||
'description': 'PO not created via the API',
|
||||
},
|
||||
expected_code=400
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
def test_po_attachments(self):
|
||||
|
@ -269,10 +269,62 @@ class StockItem(MPTTModel):
|
||||
serial_int = 0
|
||||
|
||||
if serial is not None:
|
||||
serial_int = extract_int(str(serial))
|
||||
|
||||
serial = str(serial).strip()
|
||||
|
||||
serial_int = extract_int(serial)
|
||||
|
||||
self.serial_int = serial_int
|
||||
|
||||
def get_next_serialized_item(self, include_variants=True, reverse=False):
|
||||
"""
|
||||
Get the "next" serial number for the part this stock item references.
|
||||
|
||||
e.g. if this stock item has a serial number 100, we may return the stock item with serial number 101
|
||||
|
||||
Note that this only works for "serialized" stock items with integer values
|
||||
|
||||
Args:
|
||||
include_variants: True if we wish to include stock for variant parts
|
||||
reverse: True if we want to return the "previous" (lower) serial number
|
||||
|
||||
Returns:
|
||||
A StockItem object matching the requirements, or None
|
||||
|
||||
"""
|
||||
|
||||
if not self.serialized:
|
||||
return None
|
||||
|
||||
# Find only serialized stock items
|
||||
items = StockItem.objects.exclude(serial=None).exclude(serial='')
|
||||
|
||||
if include_variants:
|
||||
# Match against any part within the variant tree
|
||||
items = items.filter(part__tree_id=self.part.tree_id)
|
||||
else:
|
||||
# Match only against the specific part
|
||||
items = items.filter(part=self.part)
|
||||
|
||||
serial = self.serial_int
|
||||
|
||||
if reverse:
|
||||
# Select only stock items with lower serial numbers, in decreasing order
|
||||
items = items.filter(serial_int__lt=serial)
|
||||
items = items.order_by('-serial_int')
|
||||
else:
|
||||
# Select only stock items with higher serial numbers, in increasing order
|
||||
items = items.filter(serial_int__gt=serial)
|
||||
items = items.order_by('serial_int')
|
||||
|
||||
if items.count() > 0:
|
||||
item = items.first()
|
||||
|
||||
if item.serialized:
|
||||
return item
|
||||
|
||||
return None
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Save this StockItem to the database. Performs a number of checks:
|
||||
|
@ -380,6 +380,84 @@ class StockTest(TestCase):
|
||||
item.save()
|
||||
self.assertTrue(item.serialized)
|
||||
|
||||
def test_big_serials(self):
|
||||
"""
|
||||
Unit tests for "large" serial numbers which exceed integer encoding
|
||||
"""
|
||||
|
||||
p = Part.objects.create(
|
||||
name='trackable part',
|
||||
description='trackable part',
|
||||
trackable=True,
|
||||
)
|
||||
|
||||
item = StockItem.objects.create(
|
||||
part=p,
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
for sn in [12345, '12345', ' 12345 ']:
|
||||
item.serial = sn
|
||||
item.save()
|
||||
|
||||
self.assertEqual(item.serial_int, 12345)
|
||||
|
||||
item.serial = "-123"
|
||||
item.save()
|
||||
|
||||
# Negative number should map to zero
|
||||
self.assertEqual(item.serial_int, 0)
|
||||
|
||||
# Test a very very large value
|
||||
item.serial = '99999999999999999999999999999999999999999999999999999'
|
||||
item.save()
|
||||
|
||||
self.assertEqual(item.serial_int, 0x7fffffff)
|
||||
|
||||
# Non-numeric values should encode to zero
|
||||
for sn in ['apple', 'banana', 'carrot']:
|
||||
item.serial = sn
|
||||
item.save()
|
||||
|
||||
self.assertEqual(item.serial_int, 0)
|
||||
|
||||
# Next, test for incremenet / decrement functionality
|
||||
item.serial = 100
|
||||
item.save()
|
||||
|
||||
item_next = StockItem.objects.create(
|
||||
part=p,
|
||||
serial=150,
|
||||
quantity=1
|
||||
)
|
||||
|
||||
self.assertEqual(item.get_next_serialized_item(), item_next)
|
||||
|
||||
item_prev = StockItem.objects.create(
|
||||
part=p,
|
||||
serial=' 57',
|
||||
quantity=1,
|
||||
)
|
||||
|
||||
self.assertEqual(item.get_next_serialized_item(reverse=True), item_prev)
|
||||
|
||||
# Create a number of serialized stock items around the current item
|
||||
for i in range(75, 125):
|
||||
try:
|
||||
StockItem.objects.create(
|
||||
part=p,
|
||||
serial=i,
|
||||
quantity=1,
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
item_next = item.get_next_serialized_item()
|
||||
item_prev = item.get_next_serialized_item(reverse=True)
|
||||
|
||||
self.assertEqual(item_next.serial_int, 101)
|
||||
self.assertEqual(item_prev.serial_int, 99)
|
||||
|
||||
def test_serialize_stock_invalid(self):
|
||||
"""
|
||||
Test manual serialization of parts.
|
||||
|
@ -101,43 +101,16 @@ class StockItemDetail(InvenTreeRoleMixin, DetailView):
|
||||
model = StockItem
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
""" add previous and next item """
|
||||
"""
|
||||
Add information on the "next" and "previous" StockItem objects,
|
||||
based on the serial numbers.
|
||||
"""
|
||||
|
||||
data = super().get_context_data(**kwargs)
|
||||
|
||||
if self.object.serialized:
|
||||
|
||||
serial_elem = {}
|
||||
|
||||
try:
|
||||
current = int(self.object.serial)
|
||||
|
||||
for item in self.object.part.stock_items.all():
|
||||
|
||||
if item.serialized:
|
||||
try:
|
||||
sn = int(item.serial)
|
||||
serial_elem[sn] = item
|
||||
except ValueError:
|
||||
# We only support integer serial number progression
|
||||
pass
|
||||
|
||||
serials = serial_elem.keys()
|
||||
|
||||
# previous
|
||||
for nbr in range(current - 1, min(serials), -1):
|
||||
if nbr in serials:
|
||||
data['previous'] = serial_elem.get(nbr, None)
|
||||
break
|
||||
|
||||
# next
|
||||
for nbr in range(current + 1, max(serials) + 1):
|
||||
if nbr in serials:
|
||||
data['next'] = serial_elem.get(nbr, None)
|
||||
break
|
||||
|
||||
except ValueError:
|
||||
# We only support integer serial number progression
|
||||
pass
|
||||
data['previous'] = self.object.get_next_serialized_item(reverse=True)
|
||||
data['next'] = self.object.get_next_serialized_item()
|
||||
|
||||
data['ownership_enabled'] = common.models.InvenTreeSetting.get_setting('STOCK_OWNERSHIP_CONTROL')
|
||||
data['item_owner'] = self.object.get_item_owner()
|
||||
|
Loading…
Reference in New Issue
Block a user