removed the tracking item overwrite, Use existing tracking event from transfer

This commit is contained in:
Neil Beueks 2026-06-01 20:50:28 +00:00
parent ce0d3c8ae3
commit f608f33641
4 changed files with 94 additions and 25 deletions

View File

@ -9,7 +9,7 @@ INVENTREE_API_TEXT = """
v498 -> 2026-06-01 : https://github.com/inventree/InvenTree/pull/12022
- Adds optional "merge" field to each item in the Stock Transfer API endpoint
- When merge is enabled, transferred stock is combined into compatible existing stock at the destination
- Stock merge tracking entries now include an "added" delta field;
- Stock merge tracking entries now include an "added" delta field
v497 -> 2026-05-27 : https://github.com/inventree/InvenTree/pull/12019
- Adds "location" field to StockCount API endpoint

View File

@ -2307,25 +2307,27 @@ class StockItem(
self.parent = None
self.save()
if other.location:
location_note = _('Transferred from %(location)s') % {
'location': other.location.pathstring
}
notes = f'{notes}\n{location_note}' if notes else location_note
other.delete()
transfer_deltas = kwargs.pop('transfer_deltas', None)
tracking_deltas = {
'quantity': float(self.quantity),
'added': float(merged_quantity),
}
if location:
tracking_deltas['location'] = location.pk
if transfer_deltas:
tracking_deltas = {**transfer_deltas, **tracking_deltas}
self.add_tracking_entry(
StockHistoryCode.MERGED_STOCK_ITEMS,
user,
quantity=self.quantity,
notes=notes,
deltas={
'location': location.pk if location else None,
'quantity': float(self.quantity),
'added': float(merged_quantity),
},
deltas=tracking_deltas,
)
# Update the location of the item
@ -2386,6 +2388,8 @@ class StockItem(
status: If provided, override the status (default = existing status)
packaging: If provided, override the packaging (default = existing packaging)
allow_production: If True, allow splitting of stock which is in production (default = False)
record_tracking: If False, skip tracking entries (for merge-on-transfer)
split_transfer_deltas: Optional dict to receive split tracking deltas
Returns:
The new StockItem object
@ -2398,6 +2402,8 @@ class StockItem(
"""
# Run initial checks to test if the stock item can actually be "split"
allow_production = kwargs.get('allow_production', False)
record_tracking = kwargs.pop('record_tracking', True)
split_transfer_deltas = kwargs.pop('split_transfer_deltas', None)
# Cannot split a stock item which is in production
if self.is_building and not allow_production:
@ -2470,15 +2476,23 @@ class StockItem(
new_stock.save(add_note=False)
# Add a stock tracking entry for the newly created item
new_stock.add_tracking_entry(
StockHistoryCode.SPLIT_FROM_PARENT,
user,
quantity=quantity,
notes=notes,
location=location,
deltas=deltas,
)
if split_transfer_deltas is not None:
split_transfer_deltas.clear()
split_transfer_deltas.update(deltas)
if location:
split_transfer_deltas['location'] = location.pk
if record_tracking:
# Add a stock tracking entry for the newly created item
new_stock.add_tracking_entry(
StockHistoryCode.SPLIT_FROM_PARENT,
user,
quantity=quantity,
notes=notes,
location=location,
deltas=deltas,
)
# Copy the test results of this part to the new one
new_stock.copyTestResultsFrom(self)
@ -2491,6 +2505,7 @@ class StockItem(
notes=notes,
location=location,
stockitem=new_stock,
record_tracking=record_tracking,
)
# Rebuild the tree for this parent item
@ -2800,7 +2815,10 @@ class StockItem(
code: The stock history code to use
notes: Optional notes for the stock removal
status: Optionally adjust the stock status
record_tracking: If False, skip creating a tracking entry
"""
record_tracking = kwargs.pop('record_tracking', True)
# Cannot remove items from a serialized part
if self.serialized:
return False
@ -2850,9 +2868,10 @@ class StockItem(
self.save(add_note=False)
self.add_tracking_entry(
code, user, notes=kwargs.get('notes', ''), deltas=deltas
)
if record_tracking:
self.add_tracking_entry(
code, user, notes=kwargs.get('notes', ''), deltas=deltas
)
return True

View File

@ -1907,16 +1907,31 @@ class StockTransferSerializer(StockAdjustmentSerializer):
}
if quantity < stock_item.quantity:
transfer_deltas = {}
piece = stock_item.splitStock(
quantity,
location,
request.user,
notes=notes,
allow_production=True,
record_tracking=False,
split_transfer_deltas=transfer_deltas,
**kwargs,
)
merge_kwargs['transfer_deltas'] = transfer_deltas
target.merge_stock_items([piece], **merge_kwargs)
else:
transfer_deltas = {'stockitem': stock_item.pk}
if location:
transfer_deltas['location'] = location.pk
for field_name in StockItem.optional_transfer_fields():
if field_name in kwargs:
transfer_deltas[field_name] = kwargs[field_name]
merge_kwargs['transfer_deltas'] = transfer_deltas
target.merge_stock_items([stock_item], **merge_kwargs)
continue

View File

@ -2448,6 +2448,7 @@ class StockTransferMergeTest(StockAPITestCase):
StockHistoryCode.STOCK_UPDATE, self.user, notes='Source tracking entry'
)
incoming_pk = incoming.pk
tracking_count = existing.tracking_info.count()
self.post(
@ -2471,6 +2472,40 @@ class StockTransferMergeTest(StockAPITestCase):
self.assertIsNotNone(merge_entry)
self.assertEqual(merge_entry.deltas['added'], 50.0)
self.assertEqual(merge_entry.deltas['quantity'], 150.0)
self.assertEqual(merge_entry.deltas['stockitem'], incoming_pk)
self.assertEqual(merge_entry.deltas['location'], self.dest.pk)
def test_transfer_merge_partial_reuses_split_transfer_deltas(self):
"""Partial merge reuses split transfer deltas on the merge tracking entry."""
existing = StockItem.objects.create(
part=self.part, location=self.dest, quantity=100
)
incoming = StockItem.objects.create(
part=self.part, location=self.source_loc, quantity=100
)
self.post(
self.url,
{
'items': [{'pk': incoming.pk, 'quantity': 30, 'merge': True}],
'location': self.dest.pk,
},
expected_code=201,
)
incoming.refresh_from_db()
self.assertEqual(incoming.quantity, 70)
merge_entry = existing.tracking_info.filter(
tracking_type=StockHistoryCode.MERGED_STOCK_ITEMS
).first()
self.assertEqual(merge_entry.deltas['stockitem'], incoming.pk)
self.assertEqual(merge_entry.deltas['location'], self.dest.pk)
self.assertFalse(
incoming.tracking_info.filter(
tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM
).exists()
)
class StockItemDeletionTest(StockAPITestCase):