fix history when merging

This commit is contained in:
Neil Beukes 2026-05-26 17:30:17 +00:00
parent 1007913a93
commit 9f01acaa66
3 changed files with 25 additions and 17 deletions

View File

@ -2185,7 +2185,8 @@ class StockItem(
return None return None
candidates = list( candidates = list(
StockItem.objects.filter(part=self.part, location=location) StockItem.objects
.filter(part=self.part, location=location)
.exclude(pk=self.pk) .exclude(pk=self.pk)
.order_by('pk') .order_by('pk')
) )
@ -2214,8 +2215,11 @@ class StockItem(
*This* stock item subsumes the other, which is essentially deleted: *This* stock item subsumes the other, which is essentially deleted:
- The quantity of this StockItem is increased - The quantity of this StockItem is increased
- Tracking history for the *other* item is copied to this item - Tracking history for the *other* item is copied to this item (unless copy_history=False)
- Any allocations (build order, sales order) are moved to this StockItem - Any allocations (build order, sales order) are moved to this StockItem
kwargs:
copy_history: If True (default), copy tracking from merged items. Set False for merge-on-transfer.
""" """
if isinstance(other_items, StockItem): if isinstance(other_items, StockItem):
other_items = [other_items] other_items = [other_items]
@ -2229,6 +2233,7 @@ class StockItem(
user = kwargs.get('user') user = kwargs.get('user')
location = kwargs.get('location', self.location) location = kwargs.get('location', self.location)
notes = kwargs.get('notes') or '' notes = kwargs.get('notes') or ''
copy_history = kwargs.pop('copy_history', True)
parent_id = self.parent.pk if self.parent else None parent_id = self.parent.pk if self.parent else None
@ -2270,17 +2275,15 @@ class StockItem(
self.parent = None self.parent = None
self.save() self.save()
self.copyHistoryFrom(other) if copy_history:
self.copyHistoryFrom(other)
if other.location: if other.location:
location_note = _('Transferred from %(location)s') % { location_note = _('Transferred from %(location)s') % {
'location': other.location.pathstring 'location': other.location.pathstring
} }
if notes: notes = f'{notes}\n{location_note}' if notes else location_note
notes = f'{notes}\n{location_note}'
else:
notes = location_note
other.delete() other.delete()

View File

@ -1883,6 +1883,7 @@ class StockTransferSerializer(StockAdjustmentSerializer):
'location': location, 'location': location,
'notes': notes, 'notes': notes,
'user': request.user, 'user': request.user,
'copy_history': False,
**kwargs, **kwargs,
} }
@ -1897,9 +1898,7 @@ class StockTransferSerializer(StockAdjustmentSerializer):
) )
target.merge_stock_items([piece], **merge_kwargs) target.merge_stock_items([piece], **merge_kwargs)
else: else:
target.merge_stock_items( target.merge_stock_items([stock_item], **merge_kwargs)
[stock_item], **merge_kwargs
)
continue continue

View File

@ -2166,6 +2166,9 @@ class StockTransferMergeTest(StockAPITestCase):
self.source_loc = StockLocation.objects.get(pk=5) self.source_loc = StockLocation.objects.get(pk=5)
self.url = reverse('api-stock-transfer') self.url = reverse('api-stock-transfer')
# Remove fixture stock at the destination so merge targets are deterministic
StockItem.objects.filter(part=self.part, location=self.dest).delete()
def test_transfer_without_merge_creates_separate_lot(self): def test_transfer_without_merge_creates_separate_lot(self):
"""Transfer without merge leaves multiple stock rows at destination.""" """Transfer without merge leaves multiple stock rows at destination."""
existing = StockItem.objects.create( existing = StockItem.objects.create(
@ -2219,8 +2222,8 @@ class StockTransferMergeTest(StockAPITestCase):
self.assertEqual(existing.quantity, 150) self.assertEqual(existing.quantity, 150)
self.assertFalse(StockItem.objects.filter(pk=incoming.pk).exists()) self.assertFalse(StockItem.objects.filter(pk=incoming.pk).exists())
def test_transfer_merge_preserves_tracking(self): def test_transfer_merge_does_not_copy_source_tracking(self):
"""Transfer merge copies tracking history onto the surviving stock item.""" """Transfer merge keeps destination history and adds a single merge entry."""
existing = StockItem.objects.create( existing = StockItem.objects.create(
part=self.part, location=self.dest, quantity=100 part=self.part, location=self.dest, quantity=100
) )
@ -2229,9 +2232,7 @@ class StockTransferMergeTest(StockAPITestCase):
) )
incoming.add_tracking_entry( incoming.add_tracking_entry(
StockHistoryCode.STOCK_UPDATE, StockHistoryCode.STOCK_UPDATE, self.user, notes='Source tracking entry'
self.user,
notes='Source tracking entry',
) )
tracking_count = existing.tracking_info.count() tracking_count = existing.tracking_info.count()
@ -2248,10 +2249,15 @@ class StockTransferMergeTest(StockAPITestCase):
existing.refresh_from_db() existing.refresh_from_db()
self.assertTrue( self.assertFalse(
existing.tracking_info.filter(notes='Source tracking entry').exists() existing.tracking_info.filter(notes='Source tracking entry').exists()
) )
self.assertGreater(existing.tracking_info.count(), tracking_count) self.assertEqual(existing.tracking_info.count(), tracking_count + 1)
self.assertTrue(
existing.tracking_info.filter(
tracking_type=StockHistoryCode.MERGED_STOCK_ITEMS
).exists()
)
class StockItemDeletionTest(StockAPITestCase): class StockItemDeletionTest(StockAPITestCase):