mirror of
https://github.com/inventree/InvenTree.git
synced 2026-08-21 20:45:13 +00:00
fix: trigger pricing recalculation when SupplierPart pack_quantity changes (#12421)
* fix: trigger pricing recalculation when SupplierPart is saved or deleted When a SupplierPart's pack_quantity is updated after price breaks have already been created, the Part's pricing (and BOM cost rollups for any assemblies using that part) was not recalculated. This is because there was no post_save or post_delete signal handler for the SupplierPart model to trigger schedule_pricing_update on the linked Part. Added post_save and post_delete signal handlers for SupplierPart that mirror the existing SupplierPriceBreak signal handlers. The pricing cascade (via update_assemblies) ensures BOM line costs in parent assemblies are also updated. Fixes #12285 * style: fix ruff format issues * style: apply ruff format with --preview flag (matching project config) * fix: resolve PartPricing.DoesNotExist in pack_quantity test The test captured self.part.pricing before any PartPricing row existed, yielding an unsaved instance; the later refresh_from_db() then raised DoesNotExist. Re-fetch self.part.pricing after the price break creates the row, matching the pattern in test_supplier_part_pricing. --------- Co-authored-by: Oliver <oliver.henry.walters@gmail.com> Co-authored-by: Aman Jain <jainamn@amazon.com>
This commit is contained in:
co-authored by
Oliver
Aman Jain
parent
a48ba58ee9
commit
be9faff766
@@ -1039,6 +1039,36 @@ class SupplierPriceBreak(common.models.PriceBreak):
|
||||
)
|
||||
|
||||
|
||||
@receiver(post_save, sender=SupplierPart, dispatch_uid='post_save_supplier_part')
|
||||
def after_save_supplier_part(sender, instance, created, **kwargs):
|
||||
"""Callback function when a SupplierPart is created or updated.
|
||||
|
||||
Triggers a pricing update for the linked Part, so that changes to
|
||||
pack_quantity are reflected in Part pricing and BOM cost rollups.
|
||||
"""
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING)
|
||||
and not InvenTree.ready.isImportingData()
|
||||
and instance.part
|
||||
):
|
||||
instance.part.schedule_pricing_update(create=True)
|
||||
|
||||
|
||||
@receiver(post_delete, sender=SupplierPart, dispatch_uid='post_delete_supplier_part')
|
||||
def after_delete_supplier_part(sender, instance, **kwargs):
|
||||
"""Callback function when a SupplierPart is deleted.
|
||||
|
||||
Triggers a pricing update for the linked Part, so that removal of a
|
||||
supplier part is reflected in Part pricing and BOM cost rollups.
|
||||
"""
|
||||
if (
|
||||
InvenTree.ready.canAppAccessDatabase(allow_test=settings.TESTING_PRICING)
|
||||
and not InvenTree.ready.isImportingData()
|
||||
and instance.part
|
||||
):
|
||||
instance.part.schedule_pricing_update(create=False)
|
||||
|
||||
|
||||
@receiver(
|
||||
post_save, sender=SupplierPriceBreak, dispatch_uid='post_save_supplier_price_break'
|
||||
)
|
||||
|
||||
@@ -175,6 +175,39 @@ class PartPricingTests(InvenTreeTestCase):
|
||||
self.assertIsNone(pricing.supplier_price_min)
|
||||
self.assertIsNone(pricing.supplier_price_max)
|
||||
|
||||
@override_settings(TESTING_PRICING=True)
|
||||
def test_supplier_part_pack_quantity_update(self):
|
||||
"""Test that changing pack_quantity on a SupplierPart triggers pricing recalculation."""
|
||||
supplier = company.models.Company.objects.create(
|
||||
name='Pack Test Supplier', is_supplier=True
|
||||
)
|
||||
|
||||
sp = company.models.SupplierPart.objects.create(
|
||||
supplier=supplier, part=self.part, SKU='PACK_TEST', pack_quantity='1'
|
||||
)
|
||||
|
||||
company.models.SupplierPriceBreak.objects.create(
|
||||
part=sp, quantity=1, price=50, price_currency='USD'
|
||||
)
|
||||
|
||||
# Re-fetch pricing (the price break creation triggers the PartPricing row)
|
||||
pricing = self.part.pricing
|
||||
pricing.refresh_from_db()
|
||||
|
||||
# Price per unit should be $50 / 1 = $50
|
||||
self.assertEqual(pricing.supplier_price_min, Money(50, 'USD'))
|
||||
|
||||
# Now update pack_quantity to 100 (i.e. 100 units per pack)
|
||||
sp.pack_quantity = '100'
|
||||
sp.save()
|
||||
|
||||
pricing = self.part.pricing
|
||||
pricing.refresh_from_db()
|
||||
|
||||
# Price per unit should now be $50 / 100 = $0.50
|
||||
self.assertEqual(pricing.supplier_price_min, Money('0.5', 'USD'))
|
||||
self.assertEqual(pricing.supplier_price_max, Money('0.5', 'USD'))
|
||||
|
||||
@override_settings(TESTING_PRICING=True)
|
||||
def test_internal_pricing(self):
|
||||
"""Tests for internal price breaks."""
|
||||
|
||||
Reference in New Issue
Block a user