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:
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user