2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-01-08 04:07:59 +00:00

[bug] State change fixes (#10832)

* Fix for setting custom status

* Fix for setting custom status when receiving stock items

* Allow caching for set_status

* Updated code and unit tests
This commit is contained in:
Oliver
2025-11-15 07:42:48 +11:00
committed by GitHub
parent af4d9efd1d
commit aa9958bf11
6 changed files with 58 additions and 18 deletions

View File

@@ -309,13 +309,23 @@ class StatusCodeMixin:
return status is not None and status == self.get_custom_status() return status is not None and status == self.get_custom_status()
def set_status(self, status: int) -> bool: def set_status(self, status: int, custom_values=None) -> bool:
"""Set the status code for this object.""" """Set the status code for this object.
Arguments:
status: The status code to set
custom_values: Optional list of custom values to consider (can be used to avoid DB queries)
"""
if not self.status_class: if not self.status_class:
raise NotImplementedError('Status class not defined') raise NotImplementedError('Status class not defined')
base_values = self.status_class.values() base_values = self.status_class.values()
custom_value_set = self.status_class.custom_values()
custom_value_set = (
self.status_class.custom_values()
if custom_values is None
else custom_values
)
custom_field = f'{self.STATUS_FIELD}_custom_key' custom_field = f'{self.STATUS_FIELD}_custom_key'

View File

@@ -976,6 +976,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
# Prefetch line item objects for DB efficiency # Prefetch line item objects for DB efficiency
line_items_ids = [item['line_item'].pk for item in items] line_items_ids = [item['line_item'].pk for item in items]
# Cache the custom status options for the StockItem model
custom_stock_status_values = stock.models.StockItem.STATUS_CLASS.custom_values()
line_items = PurchaseOrderLineItem.objects.filter( line_items = PurchaseOrderLineItem.objects.filter(
pk__in=line_items_ids pk__in=line_items_ids
).prefetch_related('part', 'part__part', 'order') ).prefetch_related('part', 'part__part', 'order')
@@ -1050,7 +1053,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
'supplier_part': supplier_part, 'supplier_part': supplier_part,
'purchase_order': self, 'purchase_order': self,
'purchase_price': purchase_price, 'purchase_price': purchase_price,
'status': item.get('status', StockStatus.OK.value),
'location': stock_location, 'location': stock_location,
'quantity': 1 if serialize else stock_quantity, 'quantity': 1 if serialize else stock_quantity,
'batch': item.get('batch_code', ''), 'batch': item.get('batch_code', ''),
@@ -1059,6 +1061,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
'packaging': item.get('packaging') or supplier_part.packaging, 'packaging': item.get('packaging') or supplier_part.packaging,
} }
# Extract the "status" field
status = item.get('status', StockStatus.OK.value)
# Check linked build order # Check linked build order
# This is for receiving against an *external* build order # This is for receiving against an *external* build order
if build_order := line.build_order: if build_order := line.build_order:
@@ -1099,11 +1104,14 @@ class PurchaseOrder(TotalPriceMixin, Order):
# Now, create the new stock items # Now, create the new stock items
if serialize: if serialize:
stock_items.extend( new_items = stock.models.StockItem._create_serial_numbers(
stock.models.StockItem._create_serial_numbers( serials=serials, **stock_data
serials=serials, **stock_data
)
) )
for item in new_items:
item.set_status(status, custom_values=custom_stock_status_values)
stock_items.append(item)
else: else:
new_item = stock.models.StockItem( new_item = stock.models.StockItem(
**stock_data, **stock_data,
@@ -1115,10 +1123,11 @@ class PurchaseOrder(TotalPriceMixin, Order):
rght=2, rght=2,
) )
new_item.set_status(status, custom_values=custom_stock_status_values)
if barcode: if barcode:
new_item.assign_barcode(barcode_data=barcode, save=False) new_item.assign_barcode(barcode_data=barcode, save=False)
# new_item.save()
bulk_create_items.append(new_item) bulk_create_items.append(new_item)
# Update the line item quantity # Update the line item quantity

View File

@@ -1226,6 +1226,8 @@ class PurchaseOrderReceiveTest(OrderTest):
def test_receive_large_quantity(self): def test_receive_large_quantity(self):
"""Test receipt of a large number of items.""" """Test receipt of a large number of items."""
from stock.status_codes import StockStatus
sp = SupplierPart.objects.first() sp = SupplierPart.objects.first()
# Create a new order # Create a new order
@@ -1256,7 +1258,12 @@ class PurchaseOrderReceiveTest(OrderTest):
url, url,
{ {
'items': [ 'items': [
{'line_item': line.pk, 'quantity': line.quantity} for line in lines {
'line_item': line.pk,
'quantity': line.quantity,
'status': StockStatus.QUARANTINED.value,
}
for line in lines
], ],
'location': location.pk, 'location': location.pk,
}, },
@@ -1269,6 +1276,7 @@ class PurchaseOrderReceiveTest(OrderTest):
for item in response: for item in response:
self.assertEqual(item['purchase_order'], po.pk) self.assertEqual(item['purchase_order'], po.pk)
self.assertEqual(item['status'], StockStatus.QUARANTINED)
# Check that the order has been completed # Check that the order has been completed
po.refresh_from_db() po.refresh_from_db()

View File

@@ -827,7 +827,7 @@ class StockItem(
super().save(*args, **kwargs) super().save(*args, **kwargs)
# If user information is provided, and no existing note exists, create one! # If user information is provided, and no existing note exists, create one!
if user and add_note and self.tracking_info.count() == 0: if add_note and self.tracking_info.count() == 0:
tracking_info = {'status': self.status} tracking_info = {'status': self.status}
self.add_tracking_entry( self.add_tracking_entry(

View File

@@ -1060,12 +1060,19 @@ class StockChangeStatusSerializer(serializers.Serializer):
# Instead of performing database updates for each item, # Instead of performing database updates for each item,
# perform bulk database updates (much more efficient) # perform bulk database updates (much more efficient)
# Pre-cache the custom status values (to reduce DB hits)
custom_status_codes = StockItem.STATUS_CLASS.custom_values()
for item in items: for item in items:
# Ignore items which are already in the desired status # Ignore items which are already in the desired status
if item.compare_status(status):
continue
item.set_status(status) # Careful check for custom status codes also
if item.compare_status(status):
custom_status = item.get_custom_status()
if status == custom_status or custom_status is None:
continue
item.set_status(status, custom_values=custom_status_codes)
item.save(add_note=False) item.save(add_note=False)
# Create a new transaction note for each item # Create a new transaction note for each item

View File

@@ -1701,12 +1701,18 @@ class StockItemTest(StockAPITestCase):
prt = Part.objects.first() prt = Part.objects.first()
# Number of items to create
N_ITEMS = 10
# Create a bunch of items # Create a bunch of items
items = [StockItem.objects.create(part=prt, quantity=10) for _ in range(10)] items = [
StockItem.objects.create(part=prt, quantity=10) for _ in range(N_ITEMS)
]
for item in items: for item in items:
item.refresh_from_db() item.refresh_from_db()
self.assertEqual(item.status, StockStatus.OK.value) self.assertEqual(item.status, StockStatus.OK.value)
self.assertEqual(item.tracking_info.count(), 1)
data = { data = {
'items': [item.pk for item in items], 'items': [item.pk for item in items],
@@ -1719,10 +1725,10 @@ class StockItemTest(StockAPITestCase):
for item in items: for item in items:
item.refresh_from_db() item.refresh_from_db()
self.assertEqual(item.status, StockStatus.DAMAGED.value) self.assertEqual(item.status, StockStatus.DAMAGED.value)
self.assertEqual(item.tracking_info.count(), 1) self.assertEqual(item.tracking_info.count(), 2)
# Same test, but with one item unchanged # Same test, but with one item unchanged
items[0].status = StockStatus.ATTENTION.value items[0].set_status(StockStatus.ATTENTION.value)
items[0].save() items[0].save()
data['status'] = StockStatus.ATTENTION.value data['status'] = StockStatus.ATTENTION.value
@@ -1732,7 +1738,7 @@ class StockItemTest(StockAPITestCase):
for item in items: for item in items:
item.refresh_from_db() item.refresh_from_db()
self.assertEqual(item.status, StockStatus.ATTENTION.value) self.assertEqual(item.status, StockStatus.ATTENTION.value)
self.assertEqual(item.tracking_info.count(), 2) self.assertEqual(item.tracking_info.count(), 3)
tracking = item.tracking_info.last() tracking = item.tracking_info.last()
self.assertEqual(tracking.tracking_type, StockHistoryCode.EDITED.value) self.assertEqual(tracking.tracking_type, StockHistoryCode.EDITED.value)