diff --git a/src/backend/InvenTree/part/filters.py b/src/backend/InvenTree/part/filters.py index 4d247529b9..a2a6440c98 100644 --- a/src/backend/InvenTree/part/filters.py +++ b/src/backend/InvenTree/part/filters.py @@ -299,15 +299,12 @@ def annotate_default_location(reference=''): rght__gt=OuterRef(f'{reference}rght'), level__lte=OuterRef(f'{reference}level'), parent__isnull=False, - ) + default_location__isnull=False, + ).order_by('-level') return Coalesce( F(f'{reference}default_location'), - Subquery( - subquery.order_by('-level') - .filter(default_location__isnull=False) - .values('default_location') - ), + Subquery(subquery.values('default_location')[:1]), Value(None), output_field=IntegerField(), ) diff --git a/src/backend/InvenTree/part/test_api.py b/src/backend/InvenTree/part/test_api.py index f65d06a425..10a6e9505e 100644 --- a/src/backend/InvenTree/part/test_api.py +++ b/src/backend/InvenTree/part/test_api.py @@ -505,6 +505,82 @@ class PartCategoryAPITest(InvenTreeAPITestCase): self.assertEqual(item['parent'], parent) self.assertEqual(item['subcategories'], subcategories) + def test_part_category_default_location(self): + """Test default location propagation through location trees.""" + """Making a tree structure like this: + main + loc 2 + sub1 + sub2 + loc 3 + sub3 + loc 4 + sub4 + sub5 + Expected behaviour: + main parent loc: Out of test scope. Parent category data not controlled by the test + sub1 parent loc: loc 2 + sub2 parent loc: loc 2 + sub3 parent loc: loc 3 + sub4 parent loc: loc 4 + sub5 parent loc: loc 3 + """ + main = PartCategory.objects.create( + name='main', + parent=PartCategory.objects.first(), + default_location=StockLocation.objects.get(id=2), + ) + sub1 = PartCategory.objects.create(name='sub1', parent=main) + sub2 = PartCategory.objects.create( + name='sub2', parent=sub1, default_location=StockLocation.objects.get(id=3) + ) + sub3 = PartCategory.objects.create( + name='sub3', parent=sub2, default_location=StockLocation.objects.get(id=4) + ) + sub4 = PartCategory.objects.create(name='sub4', parent=sub3) + sub5 = PartCategory.objects.create(name='sub5', parent=sub2) + part = Part.objects.create(name='test', category=sub4) + PartCategory.objects.rebuild() + + # This query will trigger an internal server error if annotation results are not limited to 1 + url = reverse('api-part-list') + response = self.get(url, expected_code=200) + + # sub1, expect main to be propagated + url = reverse('api-part-category-detail', kwargs={'pk': sub1.pk}) + response = self.get(url, expected_code=200) + self.assertEqual( + response.data['parent_default_location'], main.default_location.pk + ) + + # sub2, expect main to be propagated + url = reverse('api-part-category-detail', kwargs={'pk': sub2.pk}) + response = self.get(url, expected_code=200) + self.assertEqual( + response.data['parent_default_location'], main.default_location.pk + ) + + # sub3, expect sub2 to be propagated + url = reverse('api-part-category-detail', kwargs={'pk': sub3.pk}) + response = self.get(url, expected_code=200) + self.assertEqual( + response.data['parent_default_location'], sub2.default_location.pk + ) + + # sub4, expect sub3 to be propagated + url = reverse('api-part-category-detail', kwargs={'pk': sub4.pk}) + response = self.get(url, expected_code=200) + self.assertEqual( + response.data['parent_default_location'], sub3.default_location.pk + ) + + # sub5, expect sub2 to be propagated + url = reverse('api-part-category-detail', kwargs={'pk': sub5.pk}) + response = self.get(url, expected_code=200) + self.assertEqual( + response.data['parent_default_location'], sub2.default_location.pk + ) + class PartOptionsAPITest(InvenTreeAPITestCase): """Tests for the various OPTIONS endpoints in the /part/ API.