diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index 1845a3dff7..ac46683243 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -655,6 +655,16 @@ class StockFilter(rest_filters.FilterSet):
return queryset.filter(installed_items__gt=0)
return queryset.filter(installed_items=0)
+ has_child_items = rest_filters.BooleanFilter(
+ label='Has child items', method='filter_has_child_items'
+ )
+
+ def filter_has_child_items(self, queryset, name, value):
+ """Filter stock items by "belongs_to" field being empty."""
+ if str2bool(value):
+ return queryset.filter(child_items__gt=0)
+ return queryset.filter(child_items=0)
+
sent_to_customer = rest_filters.BooleanFilter(
label='Sent to customer', method='filter_sent_to_customer'
)
diff --git a/InvenTree/stock/filters.py b/InvenTree/stock/filters.py
index d1292d708c..c992fc05a0 100644
--- a/InvenTree/stock/filters.py
+++ b/InvenTree/stock/filters.py
@@ -3,6 +3,8 @@
from django.db.models import F, Func, IntegerField, OuterRef, Q, Subquery
from django.db.models.functions import Coalesce
+from sql_util.utils import SubqueryCount
+
import stock.models
@@ -33,3 +35,23 @@ def annotate_location_items(filter: Q = None):
0,
output_field=IntegerField(),
)
+
+
+def annotate_child_items():
+ """Construct a queryset annotation which returns the number of children below a certain StockItem node in a StockItem tree."""
+ child_stock_query = stock.models.StockItem.objects.filter(
+ tree_id=OuterRef('tree_id'),
+ lft__gt=OuterRef('lft'),
+ rght__lt=OuterRef('rght'),
+ level__gte=OuterRef('level'),
+ )
+
+ return Coalesce(
+ Subquery(
+ child_stock_query.annotate(
+ count=Func(F('pk'), function='COUNT', output_field=IntegerField())
+ ).values('count')
+ ),
+ 0,
+ output_field=IntegerField(),
+ )
diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py
index c38b67f32a..9357346b6e 100644
--- a/InvenTree/stock/serializers.py
+++ b/InvenTree/stock/serializers.py
@@ -170,6 +170,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
'allocated',
'expired',
'installed_items',
+ 'child_items',
'stale',
'tracking_items',
'tags',
@@ -288,6 +289,9 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
# Annotate with the total number of "installed items"
queryset = queryset.annotate(installed_items=SubqueryCount('installed_parts'))
+ # Annotate with the total number of "child items" (split stock items)
+ queryset = queryset.annotate(child_items=stock.filters.annotate_child_items())
+
return queryset
status_text = serializers.CharField(source='get_status_display', read_only=True)
@@ -315,6 +319,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
allocated = serializers.FloatField(required=False)
expired = serializers.BooleanField(required=False, read_only=True)
installed_items = serializers.IntegerField(read_only=True, required=False)
+ child_items = serializers.IntegerField(read_only=True, required=False)
stale = serializers.BooleanField(required=False, read_only=True)
tracking_items = serializers.IntegerField(read_only=True, required=False)
diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx
index 3f04626a70..b908cdd5a3 100644
--- a/src/frontend/src/pages/stock/StockDetail.tsx
+++ b/src/frontend/src/pages/stock/StockDetail.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
-import { Alert, LoadingOverlay, Stack, Text } from '@mantine/core';
+import { Alert, LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
import {
IconBookmark,
IconBoxPadding,
@@ -34,6 +34,7 @@ import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
+import { StockItemTable } from '../../components/tables/stock/StockItemTable';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiPaths } from '../../enums/ApiEndpoints';
import { useEditStockItem } from '../../forms/StockForms';
@@ -94,14 +95,18 @@ export default function StockDetail() {
name: 'installed_items',
label: t`Installed Items`,
icon: ,
- content: ,
hidden: !stockitem?.part_detail?.assembly
},
{
name: 'child_items',
label: t`Child Items`,
icon: ,
- content:
+ hidden: (stockitem?.child_items ?? 0) == 0,
+ content: stockitem?.pk ? (
+
+ ) : (
+
+ )
},
{
name: 'attachments',