mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 05:05:42 +00:00 
			
		
		
		
	Merge pull request #3017 from SchrodingersGat/build-output-delete-fix
Bug Fix: Build output delete API endpoint
This commit is contained in:
		| @@ -282,6 +282,13 @@ class BuildOutputDelete(BuildOrderContextMixin, generics.CreateAPIView): | ||||
|     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() | ||||
|  | ||||
|     serializer_class = build.serializers.BuildOutputDeleteSerializer | ||||
|   | ||||
| @@ -199,7 +199,7 @@ class BuildOutputCreateSerializer(serializers.Serializer): | ||||
|  | ||||
|     def validate_quantity(self, quantity): | ||||
|  | ||||
|         if quantity < 0: | ||||
|         if quantity <= 0: | ||||
|             raise ValidationError(_("Quantity must be greater than zero")) | ||||
|  | ||||
|         part = self.get_part() | ||||
| @@ -209,7 +209,7 @@ class BuildOutputCreateSerializer(serializers.Serializer): | ||||
|             if part.trackable: | ||||
|                 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")) | ||||
|  | ||||
|         return quantity | ||||
| @@ -232,7 +232,6 @@ class BuildOutputCreateSerializer(serializers.Serializer): | ||||
|  | ||||
|         serial_numbers = serial_numbers.strip() | ||||
|  | ||||
|         # TODO: Field level validation necessary here? | ||||
|         return serial_numbers | ||||
|  | ||||
|     auto_allocate = serializers.BooleanField( | ||||
|   | ||||
| @@ -305,6 +305,215 @@ class BuildTest(BuildAPITest): | ||||
|  | ||||
|         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): | ||||
|     """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user