mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #3017 from SchrodingersGat/build-output-delete-fix
Bug Fix: Build output delete API endpoint
This commit is contained in:
commit
c4dfb03fd2
@ -282,6 +282,13 @@ class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView):
|
|||||||
API endpoint for deleting multiple build outputs
|
API endpoint for deleting multiple build outputs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def get_serializer_context(self):
|
||||||
|
ctx = super().get_serializer_context()
|
||||||
|
|
||||||
|
ctx['to_complete'] = False
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
|
||||||
queryset = Build.objects.none()
|
queryset = Build.objects.none()
|
||||||
|
|
||||||
serializer_class = build.serializers.BuildOutputDeleteSerializer
|
serializer_class = build.serializers.BuildOutputDeleteSerializer
|
||||||
|
@ -199,7 +199,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
def validate_quantity(self, quantity):
|
def validate_quantity(self, quantity):
|
||||||
|
|
||||||
if quantity < 0:
|
if quantity <= 0:
|
||||||
raise ValidationError(_("Quantity must be greater than zero"))
|
raise ValidationError(_("Quantity must be greater than zero"))
|
||||||
|
|
||||||
part = self.get_part()
|
part = self.get_part()
|
||||||
@ -209,7 +209,7 @@ class BuildOutputCreateSerializer(serializers.Serializer):
|
|||||||
if part.trackable:
|
if part.trackable:
|
||||||
raise ValidationError(_("Integer quantity required for trackable parts"))
|
raise ValidationError(_("Integer quantity required for trackable parts"))
|
||||||
|
|
||||||
if part.has_trackable_parts():
|
if part.has_trackable_parts:
|
||||||
raise ValidationError(_("Integer quantity required, as the bill of materials contains trackable parts"))
|
raise ValidationError(_("Integer quantity required, as the bill of materials contains trackable parts"))
|
||||||
|
|
||||||
return quantity
|
return quantity
|
||||||
@ -232,7 +232,6 @@ class BuildOutputCreateSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
serial_numbers = serial_numbers.strip()
|
serial_numbers = serial_numbers.strip()
|
||||||
|
|
||||||
# TODO: Field level validation necessary here?
|
|
||||||
return serial_numbers
|
return serial_numbers
|
||||||
|
|
||||||
auto_allocate = serializers.BooleanField(
|
auto_allocate = serializers.BooleanField(
|
||||||
|
@ -305,6 +305,215 @@ class BuildTest(BuildAPITest):
|
|||||||
|
|
||||||
self.assertEqual(bo.status, BuildStatus.CANCELLED)
|
self.assertEqual(bo.status, BuildStatus.CANCELLED)
|
||||||
|
|
||||||
|
def test_create_delete_output(self):
|
||||||
|
"""
|
||||||
|
Test that we can create and delete build outputs via the API
|
||||||
|
"""
|
||||||
|
|
||||||
|
bo = Build.objects.get(pk=1)
|
||||||
|
|
||||||
|
n_outputs = bo.output_count
|
||||||
|
|
||||||
|
create_url = reverse('api-build-output-create', kwargs={'pk': 1})
|
||||||
|
|
||||||
|
# Attempt to create outputs with invalid data
|
||||||
|
response = self.post(
|
||||||
|
create_url,
|
||||||
|
{
|
||||||
|
'quantity': 'not a number',
|
||||||
|
},
|
||||||
|
expected_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('A valid number is required', str(response.data))
|
||||||
|
|
||||||
|
for q in [-100, -10.3, 0]:
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
create_url,
|
||||||
|
{
|
||||||
|
'quantity': q,
|
||||||
|
},
|
||||||
|
expected_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
if q == 0:
|
||||||
|
self.assertIn('Quantity must be greater than zero', str(response.data))
|
||||||
|
else:
|
||||||
|
self.assertIn('Ensure this value is greater than or equal to 0', str(response.data))
|
||||||
|
|
||||||
|
# Mark the part being built as 'trackable' (requires integer quantity)
|
||||||
|
bo.part.trackable = True
|
||||||
|
bo.part.save()
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
create_url,
|
||||||
|
{
|
||||||
|
'quantity': 12.3,
|
||||||
|
},
|
||||||
|
expected_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('Integer quantity required for trackable parts', str(response.data))
|
||||||
|
|
||||||
|
# Erroneous serial numbers
|
||||||
|
response = self.post(
|
||||||
|
create_url,
|
||||||
|
{
|
||||||
|
'quantity': 5,
|
||||||
|
'serial_numbers': '1, 2, 3, 4, 5, 6',
|
||||||
|
'batch': 'my-batch',
|
||||||
|
},
|
||||||
|
expected_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('Number of unique serial numbers (6) must match quantity (5)', str(response.data))
|
||||||
|
|
||||||
|
# At this point, no new build outputs should have been created
|
||||||
|
self.assertEqual(n_outputs, bo.output_count)
|
||||||
|
|
||||||
|
# Now, create with *good* data
|
||||||
|
response = self.post(
|
||||||
|
create_url,
|
||||||
|
{
|
||||||
|
'quantity': 5,
|
||||||
|
'serial_numbers': '1, 2, 3, 4, 5',
|
||||||
|
'batch': 'my-batch',
|
||||||
|
},
|
||||||
|
expected_code=201,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 5 new outputs have been created
|
||||||
|
self.assertEqual(n_outputs + 5, bo.output_count)
|
||||||
|
|
||||||
|
# Attempt to create with identical serial numbers
|
||||||
|
response = self.post(
|
||||||
|
create_url,
|
||||||
|
{
|
||||||
|
'quantity': 3,
|
||||||
|
'serial_numbers': '1-3',
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('The following serial numbers already exist : 1,2,3', str(response.data))
|
||||||
|
|
||||||
|
# Double check no new outputs have been created
|
||||||
|
self.assertEqual(n_outputs + 5, bo.output_count)
|
||||||
|
|
||||||
|
# Now, let's delete each build output individually via the API
|
||||||
|
outputs = bo.build_outputs.all()
|
||||||
|
|
||||||
|
delete_url = reverse('api-build-output-delete', kwargs={'pk': 1})
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
delete_url,
|
||||||
|
{
|
||||||
|
'outputs': [],
|
||||||
|
},
|
||||||
|
expected_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('A list of build outputs must be provided', str(response.data))
|
||||||
|
|
||||||
|
# Mark 1 build output as complete
|
||||||
|
bo.complete_build_output(outputs[0], self.user)
|
||||||
|
|
||||||
|
self.assertEqual(n_outputs + 5, bo.output_count)
|
||||||
|
self.assertEqual(1, bo.complete_count)
|
||||||
|
|
||||||
|
# Delete all outputs at once
|
||||||
|
# Note: One has been completed, so this should fail!
|
||||||
|
response = self.post(
|
||||||
|
delete_url,
|
||||||
|
{
|
||||||
|
'outputs': [
|
||||||
|
{
|
||||||
|
'output': output.pk,
|
||||||
|
} for output in outputs
|
||||||
|
]
|
||||||
|
},
|
||||||
|
expected_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('This build output has already been completed', str(response.data))
|
||||||
|
|
||||||
|
# No change to the build outputs
|
||||||
|
self.assertEqual(n_outputs + 5, bo.output_count)
|
||||||
|
self.assertEqual(1, bo.complete_count)
|
||||||
|
|
||||||
|
# Let's delete 2 build outputs
|
||||||
|
response = self.post(
|
||||||
|
delete_url,
|
||||||
|
{
|
||||||
|
'outputs': [
|
||||||
|
{
|
||||||
|
'output': output.pk,
|
||||||
|
} for output in outputs[1:3]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
expected_code=201
|
||||||
|
)
|
||||||
|
|
||||||
|
# Two build outputs have been removed
|
||||||
|
self.assertEqual(n_outputs + 3, bo.output_count)
|
||||||
|
self.assertEqual(1, bo.complete_count)
|
||||||
|
|
||||||
|
# Tests for BuildOutputComplete serializer
|
||||||
|
complete_url = reverse('api-build-output-complete', kwargs={'pk': 1})
|
||||||
|
|
||||||
|
# Let's mark the remaining outputs as complete
|
||||||
|
response = self.post(
|
||||||
|
complete_url,
|
||||||
|
{
|
||||||
|
'outputs': [],
|
||||||
|
'location': 4,
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('A list of build outputs must be provided', str(response.data))
|
||||||
|
|
||||||
|
for output in outputs[3:]:
|
||||||
|
output.refresh_from_db()
|
||||||
|
self.assertTrue(output.is_building)
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
complete_url,
|
||||||
|
{
|
||||||
|
'outputs': [
|
||||||
|
{
|
||||||
|
'output': output.pk
|
||||||
|
} for output in outputs[3:]
|
||||||
|
],
|
||||||
|
'location': 4,
|
||||||
|
},
|
||||||
|
expected_code=201,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that the outputs have been completed
|
||||||
|
self.assertEqual(3, bo.complete_count)
|
||||||
|
|
||||||
|
for output in outputs[3:]:
|
||||||
|
output.refresh_from_db()
|
||||||
|
self.assertEqual(output.location.pk, 4)
|
||||||
|
self.assertFalse(output.is_building)
|
||||||
|
|
||||||
|
# Try again, with an output which has already been completed
|
||||||
|
response = self.post(
|
||||||
|
complete_url,
|
||||||
|
{
|
||||||
|
'outputs': [
|
||||||
|
{
|
||||||
|
'output': outputs.last().pk,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
expected_code=400,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn('This build output has already been completed', str(response.data))
|
||||||
|
|
||||||
|
|
||||||
class BuildAllocationTest(BuildAPITest):
|
class BuildAllocationTest(BuildAPITest):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user