2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-18 02:36:31 +00:00

Stock serialize tweaks (#10017)

* Better item extraction

* Improve query efficiency

* Further queryset improvements

* Return correct data format

* Test with hugh number of serials

* Improve serialization UX

* Revert changes to unit tests
This commit is contained in:
Oliver
2025-07-15 00:00:38 +10:00
committed by GitHub
parent 22218fd5c6
commit 9f715337ec
6 changed files with 50 additions and 15 deletions

View File

@@ -659,7 +659,8 @@ class BuildOutputCreate(BuildOrderContextMixin, CreateAPI):
# Create the build output(s) # Create the build output(s)
outputs = serializer.save() outputs = serializer.save()
response = stock.serializers.StockItemSerializer(outputs, many=True) queryset = stock.serializers.StockItemSerializer.annotate_queryset(outputs)
response = stock.serializers.StockItemSerializer(queryset, many=True)
# Return the created outputs # Return the created outputs
return Response(response.data, status=status.HTTP_201_CREATED) return Response(response.data, status=status.HTTP_201_CREATED)

View File

@@ -1009,7 +1009,8 @@ class Build(
}, },
) )
outputs = [output] # Ensure we return a QuerySet object here, too
outputs = stock.models.StockItem.objects.filter(pk=output.pk)
if self.status == BuildStatus.PENDING: if self.status == BuildStatus.PENDING:
self.status = BuildStatus.PRODUCTION.value self.status = BuildStatus.PRODUCTION.value

View File

@@ -129,8 +129,10 @@ class StockItemSerialize(StockItemContextMixin, CreateAPI):
# Perform the actual serialization step # Perform the actual serialization step
items = serializer.save() items = serializer.save()
queryset = StockSerializers.StockItemSerializer.annotate_queryset(items)
response = StockSerializers.StockItemSerializer( response = StockSerializers.StockItemSerializer(
items, many=True, context=self.get_serializer_context() queryset, many=True, context=self.get_serializer_context()
) )
return Response(response.data, status=status.HTTP_201_CREATED) return Response(response.data, status=status.HTTP_201_CREATED)
@@ -1151,8 +1153,11 @@ class StockList(DataExportViewMixin, StockApiMixin, ListCreateDestroyAPIView):
StockItemTracking.objects.bulk_create(tracking) StockItemTracking.objects.bulk_create(tracking)
# Annotate the stock items with part information
queryset = StockSerializers.StockItemSerializer.annotate_queryset(items)
response = StockSerializers.StockItemSerializer( response = StockSerializers.StockItemSerializer(
items, many=True, context=self.get_serializer_context() queryset, many=True, context=self.get_serializer_context()
) )
response_data = response.data response_data = response.data

View File

@@ -696,7 +696,10 @@ class SerializeStockItemSerializer(serializers.Serializer):
def validate_quantity(self, quantity): def validate_quantity(self, quantity):
"""Validate that the quantity value is correct.""" """Validate that the quantity value is correct."""
item = self.context['item'] item = self.context.get('item')
if not item:
raise ValidationError(_('No stock item provided'))
if quantity < 0: if quantity < 0:
raise ValidationError(_('Quantity must be greater than zero')) raise ValidationError(_('Quantity must be greater than zero'))
@@ -736,7 +739,10 @@ class SerializeStockItemSerializer(serializers.Serializer):
"""Check that the supplied serial numbers are valid.""" """Check that the supplied serial numbers are valid."""
data = super().validate(data) data = super().validate(data)
item = self.context['item'] item = self.context.get('item')
if not item:
raise ValidationError(_('No stock item provided'))
if not item.part.trackable: if not item.part.trackable:
raise ValidationError(_('Serial numbers cannot be assigned to this part')) raise ValidationError(_('Serial numbers cannot be assigned to this part'))
@@ -771,7 +777,11 @@ class SerializeStockItemSerializer(serializers.Serializer):
Returns: Returns:
A list of StockItem objects that were created as a result of the serialization. A list of StockItem objects that were created as a result of the serialization.
""" """
item = self.context['item'] item = self.context.get('item')
if not item:
raise ValidationError(_('No stock item provided'))
request = self.context.get('request') request = self.context.get('request')
user = request.user if request else None user = request.user if request else None
@@ -905,7 +915,10 @@ class UninstallStockItemSerializer(serializers.Serializer):
def save(self): def save(self):
"""Uninstall stock item.""" """Uninstall stock item."""
item = self.context['item'] item = self.context.get('item')
if not item:
raise ValidationError(_('No stock item provided'))
data = self.validated_data data = self.validated_data
request = self.context['request'] request = self.context['request']
@@ -1035,7 +1048,11 @@ class ReturnStockItemSerializer(serializers.Serializer):
def save(self): def save(self):
"""Save the serializer to return the item into stock.""" """Save the serializer to return the item into stock."""
item = self.context['item'] item = self.context.get('item')
if not item:
raise ValidationError(_('No stock item provided'))
request = self.context['request'] request = self.context['request']
data = self.validated_data data = self.validated_data

View File

@@ -66,6 +66,15 @@ export function useInstance<T = any>({
JSON.stringify(pathParams), JSON.stringify(pathParams),
disabled disabled
], ],
retry: (failureCount, error: any) => {
// If it's a 404, don't retry
if (error.response?.status == 404) {
return false;
}
// Otherwise, retry up to 3 times
return failureCount < 3;
},
queryFn: async () => { queryFn: async () => {
if (disabled) { if (disabled) {
return defaultValue; return defaultValue;

View File

@@ -744,12 +744,14 @@ export default function StockDetail() {
quantity: stockitem.quantity, quantity: stockitem.quantity,
destination: stockitem.location ?? stockitem.part_detail?.default_location destination: stockitem.location ?? stockitem.part_detail?.default_location
}, },
onFormSuccess: () => { onFormSuccess: (response: any) => {
const partId = stockitem.part; if (response.length >= stockitem.quantity) {
refreshInstancePromise().catch(() => { // Entire item was serialized
// Part may have been deleted - redirect to the part detail page // Navigate to the first result
navigate(getDetailUrl(ModelType.part, partId)); navigate(getDetailUrl(ModelType.stockitem, response[0].pk));
}); } else {
refreshInstance();
}
}, },
successMessage: t`Stock item serialized` successMessage: t`Stock item serialized`
}); });