2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-18 10:46: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)
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 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:
self.status = BuildStatus.PRODUCTION.value

View File

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

View File

@@ -696,7 +696,10 @@ class SerializeStockItemSerializer(serializers.Serializer):
def validate_quantity(self, quantity):
"""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:
raise ValidationError(_('Quantity must be greater than zero'))
@@ -736,7 +739,10 @@ class SerializeStockItemSerializer(serializers.Serializer):
"""Check that the supplied serial numbers are valid."""
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:
raise ValidationError(_('Serial numbers cannot be assigned to this part'))
@@ -771,7 +777,11 @@ class SerializeStockItemSerializer(serializers.Serializer):
Returns:
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')
user = request.user if request else None
@@ -905,7 +915,10 @@ class UninstallStockItemSerializer(serializers.Serializer):
def save(self):
"""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
request = self.context['request']
@@ -1035,7 +1048,11 @@ class ReturnStockItemSerializer(serializers.Serializer):
def save(self):
"""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']
data = self.validated_data

View File

@@ -66,6 +66,15 @@ export function useInstance<T = any>({
JSON.stringify(pathParams),
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 () => {
if (disabled) {
return defaultValue;

View File

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