Fix StockItem parent update on delete (#12377)
* Fix StockItem parent update on delete * Check for null parents
This commit is contained in:
parent
afbe3b3ab9
commit
70749ed93c
|
|
@ -550,6 +550,9 @@ class StockItem(
|
|||
def delete(self, ignore_serial_check: bool = False, **kwargs):
|
||||
"""Custom delete method for StockItem model.
|
||||
|
||||
Any child items are re-linked to the parent of this item,
|
||||
to preserve the stock item genealogy chain.
|
||||
|
||||
Arguments:
|
||||
ignore_serial_check: If True, allow deletion of serialized stock items regardless of global setting
|
||||
"""
|
||||
|
|
@ -559,7 +562,14 @@ class StockItem(
|
|||
if self.serialized:
|
||||
raise ValidationError(_('Serialized stock items cannot be deleted'))
|
||||
|
||||
super().delete(**kwargs)
|
||||
with transaction.atomic():
|
||||
# Re-link any child items to the parent of this item,
|
||||
# so the genealogy chain survives deletion of an intermediate item
|
||||
|
||||
parent = StockItem.objects.filter(pk=self.parent_id).first()
|
||||
StockItem.objects.filter(parent=self).update(parent=parent)
|
||||
|
||||
super().delete(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
|
|
@ -1036,7 +1046,7 @@ class StockItem(
|
|||
"""Returns part name."""
|
||||
return self.part.full_name
|
||||
|
||||
# Note: When a StockItem is deleted, a pre_delete signal handles the parent/child relationship
|
||||
# Note: When a StockItem is deleted, child items are re-linked to its parent (see delete())
|
||||
parent = models.ForeignKey(
|
||||
'stock.StockItem',
|
||||
verbose_name=_('Parent Stock Item'),
|
||||
|
|
|
|||
|
|
@ -351,6 +351,71 @@ class StockTest(StockTestBase):
|
|||
stock.splitStock(stock.quantity, None, self.user)
|
||||
self.assertEqual(StockItem.objects.filter(part=3).count(), n + 1)
|
||||
|
||||
def test_delete_reparents_children(self):
|
||||
"""Test that deleting an intermediate item re-links children to the grandparent."""
|
||||
grandparent = StockItem.objects.get(id=1234)
|
||||
|
||||
parent = grandparent.splitStock(200, None, self.user)
|
||||
child_a = parent.splitStock(50, None, self.user)
|
||||
child_b = parent.splitStock(50, None, self.user)
|
||||
|
||||
self.assertEqual(parent.parent, grandparent)
|
||||
self.assertEqual(child_a.parent, parent)
|
||||
self.assertEqual(child_b.parent, parent)
|
||||
|
||||
# Deleting the intermediate item grafts its children onto the grandparent
|
||||
parent.delete()
|
||||
|
||||
child_a.refresh_from_db()
|
||||
child_b.refresh_from_db()
|
||||
|
||||
self.assertEqual(child_a.parent, grandparent)
|
||||
self.assertEqual(child_b.parent, grandparent)
|
||||
|
||||
# Deleting a top-level item leaves its children with no parent
|
||||
grandparent.delete()
|
||||
|
||||
child_a.refresh_from_db()
|
||||
child_b.refresh_from_db()
|
||||
|
||||
self.assertIsNone(child_a.parent)
|
||||
self.assertIsNone(child_b.parent)
|
||||
|
||||
def test_implicit_delete_reparents_children(self):
|
||||
"""Test child re-linking when items are deleted by depletion or merging."""
|
||||
grandparent = StockItem.objects.get(id=1234)
|
||||
|
||||
# An item depleted to zero with delete_on_deplete set is deleted
|
||||
parent = grandparent.splitStock(200, None, self.user)
|
||||
child = parent.splitStock(50, None, self.user)
|
||||
|
||||
parent.delete_on_deplete = True
|
||||
parent.save()
|
||||
|
||||
self.assertTrue(parent.take_stock(150, self.user, notes='Deplete'))
|
||||
self.assertFalse(StockItem.objects.filter(pk=parent.pk).exists())
|
||||
|
||||
child.refresh_from_db()
|
||||
self.assertEqual(child.parent, grandparent)
|
||||
|
||||
# An item absorbed by a merge is also deleted
|
||||
source = grandparent.splitStock(100, None, self.user)
|
||||
kid = source.splitStock(25, None, self.user)
|
||||
|
||||
target = StockItem.objects.create(
|
||||
part=grandparent.part,
|
||||
supplier_part=grandparent.supplier_part,
|
||||
quantity=10,
|
||||
location=grandparent.location,
|
||||
)
|
||||
|
||||
target.merge_stock_items([source], raise_error=True, user=self.user)
|
||||
|
||||
self.assertFalse(StockItem.objects.filter(pk=source.pk).exists())
|
||||
|
||||
kid.refresh_from_db()
|
||||
self.assertEqual(kid.parent, grandparent)
|
||||
|
||||
def test_over_adjustment_quantities(self):
|
||||
"""Stock adjustments are clamped to the available stock quantity.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue