From 94024ad23e9f82863266c25049161e1df0be62fa Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 8 Aug 2026 23:27:53 +1000 Subject: [PATCH] [bug] Validate location when completing build outputs (#12570) * [bug] Validate location when completing build outputs - Cannot be structural * Fix unit test --------- Co-authored-by: Matthias Mair --- src/backend/InvenTree/build/serializers.py | 9 ++++++ src/backend/InvenTree/build/test_api.py | 33 ++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py index bdcdaf790e..97edd4350d 100644 --- a/src/backend/InvenTree/build/serializers.py +++ b/src/backend/InvenTree/build/serializers.py @@ -570,6 +570,15 @@ class BuildOutputCompleteSerializer(serializers.Serializer): help_text=_('Location for completed build outputs'), ) + def validate_location(self, location): + """Validate the provided location.""" + if location and location.structural: + raise ValidationError( + _('Structural locations cannot be assigned stock items') + ) + + return location + status_custom_key = StockStatusCustomSerializer(default=StockStatus.OK.value) accept_incomplete_allocation = serializers.BooleanField( diff --git a/src/backend/InvenTree/build/test_api.py b/src/backend/InvenTree/build/test_api.py index 18475c3198..8af5e524e3 100644 --- a/src/backend/InvenTree/build/test_api.py +++ b/src/backend/InvenTree/build/test_api.py @@ -498,6 +498,39 @@ class BuildTest(BuildAPITest): 'This build output has already been completed', str(response.data) ) + def test_complete_build_output_structural_location(self): + """Test that a structural location is rejected by the BuildOutputComplete API. + + Ref: this validation must happen synchronously in the serializer, + before the completion is offloaded to the background worker. + """ + bo = Build.objects.get(pk=1) + + # Create a new build output + create_url = reverse('api-build-output-create', kwargs={'pk': bo.pk}) + response = self.post(create_url, {'quantity': 1}, expected_code=201) + output = StockItem.objects.get(pk=response.data[0]['pk']) + + structural_location = StockLocation.objects.create( + name='Structural location', structural=True + ) + + complete_url = reverse('api-build-output-complete', kwargs={'pk': bo.pk}) + + response = self.post( + complete_url, + {'outputs': [{'output': output.pk}], 'location': structural_location.pk}, + expected_code=400, + ) + + self.assertIn( + 'Structural locations cannot be assigned stock items', str(response.data) + ) + + # None of the outputs should have been completed + output.refresh_from_db() + self.assertTrue(output.is_building) + def test_download_build_orders(self): """Test that we can download a list of build orders via the API.""" required_cols = [