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 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. """ Attempt to extract serial numbers from an input string.
- Serial numbers must be integer values - Serial numbers must be integer values
- Serial numbers must be positive - Serial numbers must be positive
- Serial numbers can be split by whitespace / newline / commma chars - 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 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>+ for getting all expecteded numbers starting from <start>
- Serial numbers can be supplied as <start>+<length> for getting <length> numbers starting from <start> - Serial numbers can be supplied as <start>+<length> for getting <length> numbers starting from <start>
Args: Args:
serials: input string with patterns
expected_quantity: The number of (unique) serial numbers we expect expected_quantity: The number of (unique) serial numbers we expect
next_number(int): the next possible serial number
""" """
serials = serials.strip() 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) groups = re.split("[\s,]+", serials)
numbers = [] numbers = []
@ -493,11 +500,20 @@ def extract_serial_numbers(serials, expected_quantity):
errors.append(_("Invalid group: {g}").format(g=group)) errors.append(_("Invalid group: {g}").format(g=group))
continue 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: else:
if group in numbers: raise ValidationError(_(f"Invalid/no group {group}"))
errors.append(_("Duplicate serial: {g}".format(g=group)))
else:
numbers.append(group)
if len(errors) > 0: if len(errors) > 0:
raise ValidationError(errors) raise ValidationError(errors)

View File

@ -237,25 +237,41 @@ class TestSerialNumberExtraction(TestCase):
e = helpers.extract_serial_numbers e = helpers.extract_serial_numbers
sn = e("1-5", 5) sn = e("1-5", 5, 1)
self.assertEqual(len(sn), 5) self.assertEqual(len(sn), 5, 1)
for i in range(1, 6): for i in range(1, 6):
self.assertIn(i, sn) 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) 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(3, sn)
self.assertIn(13, sn) self.assertIn(13, sn)
sn = e("1+", 10) sn = e("1+", 10, 1)
self.assertEqual(len(sn), 10) self.assertEqual(len(sn), 10)
self.assertEqual(sn, [_ for _ in range(1, 11)]) 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(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): def test_failures(self):
@ -263,26 +279,45 @@ class TestSerialNumberExtraction(TestCase):
# Test duplicates # Test duplicates
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
e("1,2,3,3,3", 5) e("1,2,3,3,3", 5, 1)
# Test invalid length # Test invalid length
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
e("1,2,3", 5) e("1,2,3", 5, 1)
# Test empty string # Test empty string
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
e(", , ,", 0) e(", , ,", 0, 1)
# Test incorrect sign in group # Test incorrect sign in group
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
e("10-2", 8) e("10-2", 8, 1)
# Test invalid group # Test invalid group
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
e("1-5-10", 10) e("1-5-10", 10, 1)
with self.assertRaises(ValidationError): 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): class TestVersionNumber(TestCase):

View File

@ -109,7 +109,7 @@ class BuildOutputCreate(AjaxUpdateView):
# Check that the serial numbers are valid # Check that the serial numbers are valid
if serials: if serials:
try: try:
extracted = extract_serial_numbers(serials, quantity) extracted = extract_serial_numbers(serials, quantity, build.part.getLatestSerialNumberInt())
if extracted: if extracted:
# Check for conflicting serial numbers # Check for conflicting serial numbers
@ -143,7 +143,7 @@ class BuildOutputCreate(AjaxUpdateView):
serials = data.get('serial_numbers', None) serials = data.get('serial_numbers', None)
if serials: if serials:
serial_numbers = extract_serial_numbers(serials, quantity) serial_numbers = extract_serial_numbers(serials, quantity, build.part.getLatestSerialNumberInt())
else: else:
serial_numbers = None serial_numbers = None

View File

@ -861,7 +861,7 @@ class SOSerialAllocationSerializer(serializers.Serializer):
part = line_item.part part = line_item.part
try: try:
data['serials'] = extract_serial_numbers(serial_numbers, quantity) data['serials'] = extract_serial_numbers(serial_numbers, quantity, part.getLatestSerialNumberInt())
except DjangoValidationError as e: except DjangoValidationError as e:
raise ValidationError({ raise ValidationError({
'serial_numbers': e.messages, 'serial_numbers': e.messages,

View File

@ -594,6 +594,26 @@ class Part(MPTTModel):
# No serial numbers found # No serial numbers found
return None 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): def getSerialNumberString(self, quantity=1):
""" """
Return a formatted string representing the next available serial numbers, Return a formatted string representing the next available serial numbers,

View File

@ -480,18 +480,6 @@ class StockList(generics.ListCreateAPIView):
notes = data.get('notes', '') 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(): with transaction.atomic():
# Create an initial stock item # Create an initial stock item
@ -507,6 +495,19 @@ class StockList(generics.ListCreateAPIView):
if item.part.default_expiry > 0: if item.part.default_expiry > 0:
item.expiry_date = datetime.now().date() + timedelta(days=item.part.default_expiry) 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) # Finally, save the item (with user information)
item.save(user=user) item.save(user=user)

View File

@ -350,7 +350,7 @@ class SerializeStockItemSerializer(serializers.Serializer):
serial_numbers = data['serial_numbers'] serial_numbers = data['serial_numbers']
try: 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: except DjangoValidationError as e:
raise ValidationError({ raise ValidationError({
'serial_numbers': e.messages, 'serial_numbers': e.messages,
@ -379,6 +379,7 @@ class SerializeStockItemSerializer(serializers.Serializer):
serials = InvenTree.helpers.extract_serial_numbers( serials = InvenTree.helpers.extract_serial_numbers(
data['serial_numbers'], data['serial_numbers'],
data['quantity'], data['quantity'],
item.part.getLatestSerialNumberInt()
) )
item.serializeStock( item.serializeStock(

View File

@ -1241,7 +1241,7 @@ class StockItemCreate(AjaxCreateView):
if len(sn) > 0: if len(sn) > 0:
try: try:
serials = extract_serial_numbers(sn, quantity) serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt())
except ValidationError as e: except ValidationError as e:
serials = None serials = None
form.add_error('serial_numbers', e.messages) form.add_error('serial_numbers', e.messages)
@ -1283,7 +1283,7 @@ class StockItemCreate(AjaxCreateView):
# Create a single stock item for each provided serial number # Create a single stock item for each provided serial number
if len(sn) > 0: if len(sn) > 0:
serials = extract_serial_numbers(sn, quantity) serials = extract_serial_numbers(sn, quantity, part.getLatestSerialNumberInt())
for serial in serials: for serial in serials:
item = StockItem( item = StockItem(