Merge pull request #2487 from matmair/sn-append

Add new SN match group
This commit is contained in:
Oliver 2021-12-30 21:01:25 +11:00 committed by GitHub
commit d3909ef766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 109 additions and 36 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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)

View File

@ -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(

View File

@ -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(