[PUI] Child item table (#6334)

* Add child stock item table

* Fix stock item splitting bug

- StockItem tree was not being rebuilt correctly
- Add unit tests

* Annotate StockItem serializer with "child_items" count

* Show or hide "child items" panel

* Account for case where tree_id is zero
This commit is contained in:
Oliver 2024-01-24 23:33:34 +11:00 committed by GitHub
parent 65ecb975c6
commit 0f2675c139
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 3 deletions

View File

@ -655,6 +655,16 @@ class StockFilter(rest_filters.FilterSet):
return queryset.filter(installed_items__gt=0) return queryset.filter(installed_items__gt=0)
return queryset.filter(installed_items=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( sent_to_customer = rest_filters.BooleanFilter(
label='Sent to customer', method='filter_sent_to_customer' label='Sent to customer', method='filter_sent_to_customer'
) )

View File

@ -3,6 +3,8 @@
from django.db.models import F, Func, IntegerField, OuterRef, Q, Subquery from django.db.models import F, Func, IntegerField, OuterRef, Q, Subquery
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from sql_util.utils import SubqueryCount
import stock.models import stock.models
@ -33,3 +35,23 @@ def annotate_location_items(filter: Q = None):
0, 0,
output_field=IntegerField(), 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(),
)

View File

@ -170,6 +170,7 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
'allocated', 'allocated',
'expired', 'expired',
'installed_items', 'installed_items',
'child_items',
'stale', 'stale',
'tracking_items', 'tracking_items',
'tags', 'tags',
@ -288,6 +289,9 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeTagModelSerializer):
# Annotate with the total number of "installed items" # Annotate with the total number of "installed items"
queryset = queryset.annotate(installed_items=SubqueryCount('installed_parts')) 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 return queryset
status_text = serializers.CharField(source='get_status_display', read_only=True) 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) allocated = serializers.FloatField(required=False)
expired = serializers.BooleanField(required=False, read_only=True) expired = serializers.BooleanField(required=False, read_only=True)
installed_items = serializers.IntegerField(read_only=True, required=False) 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) stale = serializers.BooleanField(required=False, read_only=True)
tracking_items = serializers.IntegerField(read_only=True, required=False) tracking_items = serializers.IntegerField(read_only=True, required=False)

View File

@ -1,5 +1,5 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Alert, LoadingOverlay, Stack, Text } from '@mantine/core'; import { Alert, LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
import { import {
IconBookmark, IconBookmark,
IconBoxPadding, IconBoxPadding,
@ -34,6 +34,7 @@ import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree'; import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { AttachmentTable } from '../../components/tables/general/AttachmentTable'; import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
import { NotesEditor } from '../../components/widgets/MarkdownEditor'; import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiPaths } from '../../enums/ApiEndpoints'; import { ApiPaths } from '../../enums/ApiEndpoints';
import { useEditStockItem } from '../../forms/StockForms'; import { useEditStockItem } from '../../forms/StockForms';
@ -94,14 +95,18 @@ export default function StockDetail() {
name: 'installed_items', name: 'installed_items',
label: t`Installed Items`, label: t`Installed Items`,
icon: <IconBoxPadding />, icon: <IconBoxPadding />,
content: <PlaceholderPanel />,
hidden: !stockitem?.part_detail?.assembly hidden: !stockitem?.part_detail?.assembly
}, },
{ {
name: 'child_items', name: 'child_items',
label: t`Child Items`, label: t`Child Items`,
icon: <IconSitemap />, icon: <IconSitemap />,
content: <PlaceholderPanel /> hidden: (stockitem?.child_items ?? 0) == 0,
content: stockitem?.pk ? (
<StockItemTable params={{ ancestor: stockitem.pk }} />
) : (
<Skeleton />
)
}, },
{ {
name: 'attachments', name: 'attachments',