mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
commit
d3909ef766
@ -404,21 +404,28 @@ 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 <start>+ for getting all expecteded numbers starting from <start>
|
||||
- Serial numbers can be supplied as <start>+<length> for getting <length> numbers starting from <start>
|
||||
|
||||
Args:
|
||||
serials: input string with patterns
|
||||
expected_quantity: The number of (unique) serial numbers we expect
|
||||
next_number(int): the next possible serial number
|
||||
"""
|
||||
|
||||
serials = serials.strip()
|
||||
|
||||
# fill in the next serial number into the serial
|
||||
if '~' in serials:
|
||||
serials = serials.replace('~', str(next_number))
|
||||
|
||||
groups = re.split("[\s,]+", serials)
|
||||
|
||||
numbers = []
|
||||
@ -493,11 +500,20 @@ def extract_serial_numbers(serials, expected_quantity):
|
||||
errors.append(_("Invalid group: {g}").format(g=group))
|
||||
continue
|
||||
|
||||
# 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(group)
|
||||
raise ValidationError(_(f"Invalid/no group {group}"))
|
||||
|
||||
if len(errors) > 0:
|
||||
raise ValidationError(errors)
|
||||
|
@ -237,25 +237,41 @@ 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])
|
||||
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", 4, 5)
|
||||
self.assertEqual(len(sn), 4)
|
||||
self.assertEqual(sn, [5, 6, 7, 8])
|
||||
|
||||
def test_failures(self):
|
||||
|
||||
@ -263,26 +279,45 @@ class TestSerialNumberExtraction(TestCase):
|
||||
|
||||
# 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)
|
||||
|
||||
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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -594,6 +594,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,
|
||||
|
@ -480,18 +480,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, data['quantity'])
|
||||
except DjangoValidationError as e:
|
||||
raise ValidationError({
|
||||
'quantity': e.messages,
|
||||
'serial_numbers': e.messages,
|
||||
})
|
||||
|
||||
with transaction.atomic():
|
||||
|
||||
# Create an initial stock item
|
||||
@ -507,6 +495,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)
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user