mirror of
https://github.com/inventree/InvenTree.git
synced 2025-11-16 04:42:16 +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:
@@ -309,13 +309,23 @@ class StatusCodeMixin:
|
||||
|
||||
return status is not None and status == self.get_custom_status()
|
||||
|
||||
def set_status(self, status: int) -> bool:
|
||||
"""Set the status code for this object."""
|
||||
def set_status(self, status: int, custom_values=None) -> bool:
|
||||
"""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:
|
||||
raise NotImplementedError('Status class not defined')
|
||||
|
||||
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'
|
||||
|
||||
|
||||
@@ -976,6 +976,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
# Prefetch line item objects for DB efficiency
|
||||
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(
|
||||
pk__in=line_items_ids
|
||||
).prefetch_related('part', 'part__part', 'order')
|
||||
@@ -1050,7 +1053,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
'supplier_part': supplier_part,
|
||||
'purchase_order': self,
|
||||
'purchase_price': purchase_price,
|
||||
'status': item.get('status', StockStatus.OK.value),
|
||||
'location': stock_location,
|
||||
'quantity': 1 if serialize else stock_quantity,
|
||||
'batch': item.get('batch_code', ''),
|
||||
@@ -1059,6 +1061,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
'packaging': item.get('packaging') or supplier_part.packaging,
|
||||
}
|
||||
|
||||
# Extract the "status" field
|
||||
status = item.get('status', StockStatus.OK.value)
|
||||
|
||||
# Check linked build order
|
||||
# This is for receiving against an *external* build order
|
||||
if build_order := line.build_order:
|
||||
@@ -1099,11 +1104,14 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
|
||||
# Now, create the new stock items
|
||||
if serialize:
|
||||
stock_items.extend(
|
||||
stock.models.StockItem._create_serial_numbers(
|
||||
serials=serials, **stock_data
|
||||
)
|
||||
new_items = stock.models.StockItem._create_serial_numbers(
|
||||
serials=serials, **stock_data
|
||||
)
|
||||
|
||||
for item in new_items:
|
||||
item.set_status(status, custom_values=custom_stock_status_values)
|
||||
stock_items.append(item)
|
||||
|
||||
else:
|
||||
new_item = stock.models.StockItem(
|
||||
**stock_data,
|
||||
@@ -1115,10 +1123,11 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
rght=2,
|
||||
)
|
||||
|
||||
new_item.set_status(status, custom_values=custom_stock_status_values)
|
||||
|
||||
if barcode:
|
||||
new_item.assign_barcode(barcode_data=barcode, save=False)
|
||||
|
||||
# new_item.save()
|
||||
bulk_create_items.append(new_item)
|
||||
|
||||
# Update the line item quantity
|
||||
|
||||
@@ -1226,6 +1226,8 @@ class PurchaseOrderReceiveTest(OrderTest):
|
||||
|
||||
def test_receive_large_quantity(self):
|
||||
"""Test receipt of a large number of items."""
|
||||
from stock.status_codes import StockStatus
|
||||
|
||||
sp = SupplierPart.objects.first()
|
||||
|
||||
# Create a new order
|
||||
@@ -1256,7 +1258,12 @@ class PurchaseOrderReceiveTest(OrderTest):
|
||||
url,
|
||||
{
|
||||
'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,
|
||||
},
|
||||
@@ -1269,6 +1276,7 @@ class PurchaseOrderReceiveTest(OrderTest):
|
||||
|
||||
for item in response:
|
||||
self.assertEqual(item['purchase_order'], po.pk)
|
||||
self.assertEqual(item['status'], StockStatus.QUARANTINED)
|
||||
|
||||
# Check that the order has been completed
|
||||
po.refresh_from_db()
|
||||
|
||||
@@ -827,7 +827,7 @@ class StockItem(
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# 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}
|
||||
|
||||
self.add_tracking_entry(
|
||||
|
||||
@@ -1060,12 +1060,19 @@ class StockChangeStatusSerializer(serializers.Serializer):
|
||||
# Instead of performing database updates for each item,
|
||||
# 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:
|
||||
# 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)
|
||||
|
||||
# Create a new transaction note for each item
|
||||
|
||||
@@ -1701,12 +1701,18 @@ class StockItemTest(StockAPITestCase):
|
||||
|
||||
prt = Part.objects.first()
|
||||
|
||||
# Number of items to create
|
||||
N_ITEMS = 10
|
||||
|
||||
# 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:
|
||||
item.refresh_from_db()
|
||||
self.assertEqual(item.status, StockStatus.OK.value)
|
||||
self.assertEqual(item.tracking_info.count(), 1)
|
||||
|
||||
data = {
|
||||
'items': [item.pk for item in items],
|
||||
@@ -1719,10 +1725,10 @@ class StockItemTest(StockAPITestCase):
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
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
|
||||
items[0].status = StockStatus.ATTENTION.value
|
||||
items[0].set_status(StockStatus.ATTENTION.value)
|
||||
items[0].save()
|
||||
|
||||
data['status'] = StockStatus.ATTENTION.value
|
||||
@@ -1732,7 +1738,7 @@ class StockItemTest(StockAPITestCase):
|
||||
for item in items:
|
||||
item.refresh_from_db()
|
||||
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()
|
||||
self.assertEqual(tracking.tracking_type, StockHistoryCode.EDITED.value)
|
||||
|
||||
Reference in New Issue
Block a user