mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-16 12:03:08 +00:00
Fix stock item splitting bug (#6335)
* Fix stock item splitting bug - StockItem tree was not being rebuilt correctly - Add unit tests * Account for zero tree_id value
This commit is contained in:
parent
f07d8a7a80
commit
fb0baa9e7a
@ -57,7 +57,6 @@ class AddFieldOrSkip(migrations.AddField):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
||||||
print(f'Added field {self.name} to model {self.model_name}')
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
@ -49,6 +50,8 @@ from part import models as PartModels
|
|||||||
from plugin.events import trigger_event
|
from plugin.events import trigger_event
|
||||||
from users.models import Owner
|
from users.models import Owner
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
class StockLocationType(MetadataMixin, models.Model):
|
class StockLocationType(MetadataMixin, models.Model):
|
||||||
"""A type of stock location like Warehouse, room, shelf, drawer.
|
"""A type of stock location like Warehouse, room, shelf, drawer.
|
||||||
@ -1706,9 +1709,12 @@ class StockItem(
|
|||||||
# Nullify the PK so a new record is created
|
# Nullify the PK so a new record is created
|
||||||
new_stock = StockItem.objects.get(pk=self.pk)
|
new_stock = StockItem.objects.get(pk=self.pk)
|
||||||
new_stock.pk = None
|
new_stock.pk = None
|
||||||
new_stock.parent = self
|
|
||||||
new_stock.quantity = quantity
|
new_stock.quantity = quantity
|
||||||
|
|
||||||
|
# Update the new stock item to ensure the tree structure is observed
|
||||||
|
new_stock.parent = self
|
||||||
|
new_stock.level = self.level + 1
|
||||||
|
|
||||||
# Move to the new location if specified, otherwise use current location
|
# Move to the new location if specified, otherwise use current location
|
||||||
if location:
|
if location:
|
||||||
new_stock.location = location
|
new_stock.location = location
|
||||||
@ -1748,6 +1754,19 @@ class StockItem(
|
|||||||
stockitem=new_stock,
|
stockitem=new_stock,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Rebuild the tree for this parent item
|
||||||
|
try:
|
||||||
|
StockItem.objects.partial_rebuild(tree_id=self.tree_id)
|
||||||
|
except Exception:
|
||||||
|
logger.warning('Rebuilding entire StockItem tree')
|
||||||
|
StockItem.objects.rebuild()
|
||||||
|
|
||||||
|
# Attempt to reload the new item from the database
|
||||||
|
try:
|
||||||
|
new_stock.refresh_from_db()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
# Return a copy of the "new" stock item
|
# Return a copy of the "new" stock item
|
||||||
return new_stock
|
return new_stock
|
||||||
|
|
||||||
|
@ -502,9 +502,18 @@ class StockTest(StockTestBase):
|
|||||||
ait = it.allocateToCustomer(
|
ait = it.allocateToCustomer(
|
||||||
customer, quantity=an, order=order, user=None, notes='Allocated some stock'
|
customer, quantity=an, order=order, user=None, notes='Allocated some stock'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.assertEqual(ait.quantity, an)
|
||||||
|
self.assertTrue(ait.parent, it)
|
||||||
|
|
||||||
|
# There should be only quantity 10x remaining
|
||||||
|
it.refresh_from_db()
|
||||||
|
self.assertEqual(it.quantity, 10)
|
||||||
|
|
||||||
ait.return_from_customer(it.location, None, notes='Stock removed from customer')
|
ait.return_from_customer(it.location, None, notes='Stock removed from customer')
|
||||||
|
|
||||||
# When returned stock is returned to its original (parent) location, check that the parent has correct quantity
|
# When returned stock is returned to its original (parent) location, check that the parent has correct quantity
|
||||||
|
it.refresh_from_db()
|
||||||
self.assertEqual(it.quantity, n)
|
self.assertEqual(it.quantity, n)
|
||||||
|
|
||||||
ait = it.allocateToCustomer(
|
ait = it.allocateToCustomer(
|
||||||
@ -987,6 +996,63 @@ class VariantTest(StockTestBase):
|
|||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
|
|
||||||
|
class StockTreeTest(StockTestBase):
|
||||||
|
"""Unit test for StockItem tree structure."""
|
||||||
|
|
||||||
|
def test_stock_split(self):
|
||||||
|
"""Test that stock splitting works correctly."""
|
||||||
|
StockItem.objects.rebuild()
|
||||||
|
|
||||||
|
part = Part.objects.create(name='My part', description='My part description')
|
||||||
|
location = StockLocation.objects.create(name='Test Location')
|
||||||
|
|
||||||
|
# Create an initial stock item
|
||||||
|
item = StockItem.objects.create(part=part, quantity=1000, location=location)
|
||||||
|
|
||||||
|
# Test that the initial MPTT values are correct
|
||||||
|
self.assertEqual(item.level, 0)
|
||||||
|
self.assertEqual(item.lft, 1)
|
||||||
|
self.assertEqual(item.rght, 2)
|
||||||
|
|
||||||
|
children = []
|
||||||
|
|
||||||
|
self.assertEqual(item.get_descendants(include_self=False).count(), 0)
|
||||||
|
self.assertEqual(item.get_descendants(include_self=True).count(), 1)
|
||||||
|
|
||||||
|
# Create child items by splitting stock
|
||||||
|
for idx in range(10):
|
||||||
|
child = item.splitStock(50, None, None)
|
||||||
|
children.append(child)
|
||||||
|
|
||||||
|
# Check that the child item has been correctly created
|
||||||
|
self.assertEqual(child.parent.pk, item.pk)
|
||||||
|
self.assertEqual(child.tree_id, item.tree_id)
|
||||||
|
self.assertEqual(child.level, 1)
|
||||||
|
|
||||||
|
item.refresh_from_db()
|
||||||
|
self.assertEqual(item.get_children().count(), idx + 1)
|
||||||
|
self.assertEqual(item.get_descendants(include_self=True).count(), idx + 2)
|
||||||
|
|
||||||
|
item.refresh_from_db()
|
||||||
|
n = item.get_descendants(include_self=True).count()
|
||||||
|
|
||||||
|
for child in children:
|
||||||
|
# Create multiple sub-childs
|
||||||
|
for _idx in range(3):
|
||||||
|
sub_child = child.splitStock(10, None, None)
|
||||||
|
self.assertEqual(sub_child.parent.pk, child.pk)
|
||||||
|
self.assertEqual(sub_child.tree_id, child.tree_id)
|
||||||
|
self.assertEqual(sub_child.level, 2)
|
||||||
|
|
||||||
|
self.assertEqual(sub_child.get_ancestors(include_self=True).count(), 3)
|
||||||
|
|
||||||
|
child.refresh_from_db()
|
||||||
|
self.assertEqual(child.get_descendants(include_self=True).count(), 4)
|
||||||
|
|
||||||
|
item.refresh_from_db()
|
||||||
|
self.assertEqual(item.get_descendants(include_self=True).count(), n + 30)
|
||||||
|
|
||||||
|
|
||||||
class TestResultTest(StockTestBase):
|
class TestResultTest(StockTestBase):
|
||||||
"""Tests for the StockItemTestResult model."""
|
"""Tests for the StockItemTestResult model."""
|
||||||
|
|
||||||
@ -1050,6 +1116,9 @@ class TestResultTest(StockTestBase):
|
|||||||
def test_duplicate_item_tests(self):
|
def test_duplicate_item_tests(self):
|
||||||
"""Test duplicate item behaviour."""
|
"""Test duplicate item behaviour."""
|
||||||
# Create an example stock item by copying one from the database (because we are lazy)
|
# Create an example stock item by copying one from the database (because we are lazy)
|
||||||
|
|
||||||
|
StockItem.objects.rebuild()
|
||||||
|
|
||||||
item = StockItem.objects.get(pk=522)
|
item = StockItem.objects.get(pk=522)
|
||||||
|
|
||||||
item.pk = None
|
item.pk = None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user