From f1e6ec249dd6e5225a8d25aea432fcc6bb3e9e5a Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 8 Aug 2026 17:58:21 +1000 Subject: [PATCH] [bug] Fix post-delete actions (#12575) - Check that underlying part is still available --- src/backend/InvenTree/company/models.py | 60 +++++++++++++++++-------- src/backend/InvenTree/stock/models.py | 15 +++++-- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/backend/InvenTree/company/models.py b/src/backend/InvenTree/company/models.py index 6e126f558b..0f4453148b 100644 --- a/src/backend/InvenTree/company/models.py +++ b/src/backend/InvenTree/company/models.py @@ -1044,15 +1044,27 @@ class SupplierPriceBreak(common.models.PriceBreak): ) def after_save_supplier_price(sender, instance, created, **kwargs): """Callback function when a SupplierPriceBreak is created or updated.""" - if ( - ( - InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING) - and not InvenTree.ready.isImportingData() - ) - and instance.part - and instance.part.part - ): - instance.part.part.schedule_pricing_update(create=True) + from part.models import Part + + if not InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING): + return + + if InvenTree.ready.isImportingData(): + return + + try: + supplier_part = instance.part + except SupplierPart.DoesNotExist: + # The underlying SupplierPart instance has been deleted + return + + try: + base_part = supplier_part.part + except Part.DoesNotExist: + # The underlying Part instance has been deleted + return + + base_part.schedule_pricing_update(create=True) @receiver( @@ -1062,12 +1074,24 @@ def after_save_supplier_price(sender, instance, created, **kwargs): ) def after_delete_supplier_price(sender, instance, **kwargs): """Callback function when a SupplierPriceBreak is deleted.""" - if ( - ( - InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING) - and not InvenTree.ready.isImportingData() - ) - and instance.part - and instance.part.part - ): - instance.part.part.schedule_pricing_update(create=False) + from part.models import Part + + if not InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING): + return + + if InvenTree.ready.isImportingData(): + return + + try: + supplier_part = instance.part + except SupplierPart.DoesNotExist: + # The underlying SupplierPart instance has been deleted + return + + try: + base_part = supplier_part.part + except Part.DoesNotExist: + # The underlying Part instance has been deleted + return + + base_part.schedule_pricing_update(create=False) diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 03820804ae..1d2fc55748 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -3593,23 +3593,32 @@ class StockItem( def after_delete_stock_item(sender, instance: StockItem, **kwargs): """Function to be executed after a StockItem object is deleted.""" from part import tasks as part_tasks + from part.models import Part if InvenTree.ready.isImportingData(): return + try: + base_part = instance.part + except Part.DoesNotExist: + return + + if not base_part: + # Base part does not exist, or has been deleted + return + if InvenTree.ready.canAppAccessDatabase(allow_test=True): # Run this check in the background InvenTree.tasks.offload_task( part_tasks.notify_low_stock_if_required, - instance.part.pk, + base_part.pk, group='notification', force_async=True, ) if InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING): # Schedule an update on parent part pricing - if instance.part: - instance.part.schedule_pricing_update(create=False) + base_part.schedule_pricing_update(create=False) @receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')