mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Simplify creation of serialized stock via the API
- Fixes atomicity issues - Don't create-then-delete stock items
This commit is contained in:
parent
9153b62ea0
commit
d0aa09337a
@ -465,12 +465,6 @@ class StockList(generics.ListCreateAPIView):
|
||||
user = request.user
|
||||
data = request.data
|
||||
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
# Check if a set of serial numbers was provided
|
||||
serial_numbers = data.get('serial_numbers', '')
|
||||
|
||||
quantity = data.get('quantity', None)
|
||||
|
||||
if quantity is None:
|
||||
@ -478,77 +472,84 @@ class StockList(generics.ListCreateAPIView):
|
||||
'quantity': _('Quantity is required'),
|
||||
})
|
||||
|
||||
notes = data.get('notes', '')
|
||||
try:
|
||||
part = Part.objects.get(pk=data.get('part', None))
|
||||
except (ValueError, Part.DoesNotExist):
|
||||
raise ValidationError({
|
||||
'part': _('Valid part ID must be supplied'),
|
||||
})
|
||||
|
||||
with transaction.atomic():
|
||||
|
||||
# Create an initial stock item
|
||||
item = serializer.save()
|
||||
|
||||
# A location was *not* specified - try to infer it
|
||||
# Set default location (if not provided)
|
||||
if 'location' not in data:
|
||||
item.location = item.part.get_default_location()
|
||||
location = part.get_default_location()
|
||||
|
||||
if location:
|
||||
data['location'] = location.pk
|
||||
|
||||
# An expiry date was *not* specified - try to infer it!
|
||||
if 'expiry_date' not in data:
|
||||
|
||||
if item.part.default_expiry > 0:
|
||||
item.expiry_date = datetime.now().date() + timedelta(days=item.part.default_expiry)
|
||||
if part.default_expiry > 0:
|
||||
data['expiry_date'] = datetime.now().date() + timedelta(days=part.default_expiry)
|
||||
|
||||
# fetch serial numbers
|
||||
# Attempt to extract serial numbers from submitted data
|
||||
serials = None
|
||||
|
||||
if serial_numbers:
|
||||
# Check if a set of serial numbers was provided
|
||||
serial_numbers = data.get('serial_numbers', '')
|
||||
|
||||
# Assign serial numbers for a trackable part
|
||||
if serial_numbers and part.trackable:
|
||||
|
||||
# If serial numbers are specified, check that they match!
|
||||
try:
|
||||
serials = extract_serial_numbers(serial_numbers, quantity, item.part.getLatestSerialNumberInt())
|
||||
serials = extract_serial_numbers(serial_numbers, quantity, part.getLatestSerialNumberInt())
|
||||
except DjangoValidationError as e:
|
||||
raise ValidationError({
|
||||
'quantity': e.messages,
|
||||
'serial_numbers': e.messages,
|
||||
})
|
||||
|
||||
# Finally, save the item (with user information)
|
||||
if serials is not None:
|
||||
"""
|
||||
If the stock item is going to be serialized, set the quantity to 1
|
||||
"""
|
||||
data['quantity'] = 1
|
||||
|
||||
# De-serialize the provided data
|
||||
serializer = self.get_serializer(data=data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
with transaction.atomic():
|
||||
|
||||
# Create an initial StockItem object
|
||||
item = serializer.save()
|
||||
|
||||
if serials:
|
||||
# Assign the first serial number to the "master" item
|
||||
item.serial = serials[0]
|
||||
|
||||
# Save the item (with user information)
|
||||
item.save(user=user)
|
||||
|
||||
if serials:
|
||||
"""
|
||||
Serialize the stock, if required
|
||||
for serial in serials[1:]:
|
||||
|
||||
- Note that the "original" stock item needs to be created first, so it can be serialized
|
||||
- It is then immediately deleted
|
||||
"""
|
||||
# Create a duplicate stock item with the next serial number
|
||||
item.pk = None
|
||||
item.serial = serial
|
||||
|
||||
try:
|
||||
item.serializeStock(
|
||||
quantity,
|
||||
serials,
|
||||
user,
|
||||
notes=notes,
|
||||
location=item.location,
|
||||
)
|
||||
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
|
||||
# Delete the original item
|
||||
item.delete()
|
||||
item.save()
|
||||
|
||||
response_data = {
|
||||
'quantity': quantity,
|
||||
'serial_numbers': serials,
|
||||
}
|
||||
|
||||
return Response(response_data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
else:
|
||||
response_data = serializer.data
|
||||
|
||||
except DjangoValidationError as e:
|
||||
raise ValidationError({
|
||||
'quantity': e.messages,
|
||||
'serial_numbers': e.messages,
|
||||
})
|
||||
|
||||
# Return a response
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||||
return Response(response_data, status=status.HTTP_201_CREATED, headers=self.get_success_headers(serializer.data))
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
|
@ -380,6 +380,67 @@ class StockItemTest(StockAPITestCase):
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
def test_creation_with_serials(self):
|
||||
"""
|
||||
Test that serialized stock items can be created via the API,
|
||||
"""
|
||||
|
||||
trackable_part = part.models.Part.objects.create(
|
||||
name='My part',
|
||||
description='A trackable part',
|
||||
trackable=True,
|
||||
default_location=StockLocation.objects.get(pk=1),
|
||||
)
|
||||
|
||||
self.assertEqual(trackable_part.stock_entries().count(), 0)
|
||||
self.assertEqual(trackable_part.get_stock_count(), 0)
|
||||
|
||||
# This should fail, incorrect serial number count
|
||||
response = self.post(
|
||||
self.list_url,
|
||||
data={
|
||||
'part': trackable_part.pk,
|
||||
'quantity': 10,
|
||||
'serial_numbers': '1-20',
|
||||
},
|
||||
expected_code=400,
|
||||
)
|
||||
|
||||
response = self.post(
|
||||
self.list_url,
|
||||
data={
|
||||
'part': trackable_part.pk,
|
||||
'quantity': 10,
|
||||
'serial_numbers': '1-10',
|
||||
},
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
data = response.data
|
||||
|
||||
self.assertEqual(data['quantity'], 10)
|
||||
sn = data['serial_numbers']
|
||||
|
||||
# Check that each serial number was created
|
||||
for i in range(1, 11):
|
||||
self.assertTrue(i in sn)
|
||||
|
||||
# Check the unique stock item has been created
|
||||
|
||||
item = StockItem.objects.get(
|
||||
part=trackable_part,
|
||||
serial=str(i),
|
||||
)
|
||||
|
||||
# Item location should have been set automatically
|
||||
self.assertIsNotNone(item.location)
|
||||
|
||||
self.assertEqual(str(i), item.serial)
|
||||
|
||||
# There now should be 10 unique stock entries for this part
|
||||
self.assertEqual(trackable_part.stock_entries().count(), 10)
|
||||
self.assertEqual(trackable_part.get_stock_count(), 10)
|
||||
|
||||
def test_default_expiry(self):
|
||||
"""
|
||||
Test that the "default_expiry" functionality works via the API.
|
||||
|
Loading…
Reference in New Issue
Block a user