@@ -1931,7 +1931,7 @@ def pricing(self):
1931
1931
1932
1932
return pricing
1933
1933
1934
- def schedule_pricing_update (self , create : bool = False , test : bool = False ):
1934
+ def schedule_pricing_update (self , create : bool = False ):
1935
1935
"""Helper function to schedule a pricing update.
1936
1936
1937
1937
Importantly, catches any errors which may occur during deletion of related objects,
@@ -1941,7 +1941,6 @@ def schedule_pricing_update(self, create: bool = False, test: bool = False):
1941
1941
1942
1942
Arguments:
1943
1943
create: Whether or not a new PartPricing object should be created if it does not already exist
1944
- test: Whether or not the pricing update is allowed during unit tests
1945
1944
"""
1946
1945
try :
1947
1946
self .refresh_from_db ()
@@ -1952,7 +1951,7 @@ def schedule_pricing_update(self, create: bool = False, test: bool = False):
1952
1951
pricing = self .pricing
1953
1952
1954
1953
if create or pricing .pk :
1955
- pricing .schedule_for_update (test = test )
1954
+ pricing .schedule_for_update ()
1956
1955
except IntegrityError :
1957
1956
# If this part instance has been deleted,
1958
1957
# some post-delete or post-save signals may still be fired
@@ -2532,7 +2531,8 @@ class PartPricing(common.models.MetaMixin):
2532
2531
- Detailed pricing information is very context specific in any case
2533
2532
"""
2534
2533
2535
- price_modified = False
2534
+ # When calculating assembly pricing, we limit the depth of the calculation
2535
+ MAX_PRICING_DEPTH = 50
2536
2536
2537
2537
@property
2538
2538
def is_valid (self ):
@@ -2561,14 +2561,10 @@ def convert(self, money):
2561
2561
2562
2562
return result
2563
2563
2564
- def schedule_for_update (self , counter : int = 0 , test : bool = False ):
2564
+ def schedule_for_update (self , counter : int = 0 ):
2565
2565
"""Schedule this pricing to be updated."""
2566
2566
import InvenTree .ready
2567
2567
2568
- # If we are running within CI, only schedule the update if the test flag is set
2569
- if settings .TESTING and not test :
2570
- return
2571
-
2572
2568
# If importing data, skip pricing update
2573
2569
if InvenTree .ready .isImportingData ():
2574
2570
return
@@ -2612,7 +2608,7 @@ def schedule_for_update(self, counter: int = 0, test: bool = False):
2612
2608
logger .debug ('Pricing for %s already scheduled for update - skipping' , p )
2613
2609
return
2614
2610
2615
- if counter > 25 :
2611
+ if counter > self . MAX_PRICING_DEPTH :
2616
2612
# Prevent infinite recursion / stack depth issues
2617
2613
logger .debug (
2618
2614
counter , f'Skipping pricing update for { p } - maximum depth exceeded'
@@ -2631,16 +2627,36 @@ def schedule_for_update(self, counter: int = 0, test: bool = False):
2631
2627
2632
2628
import part .tasks as part_tasks
2633
2629
2630
+ # Pricing calculations are performed in the background,
2631
+ # unless the TESTING_PRICING flag is set
2632
+ background = not settings .TESTING or not settings .TESTING_PRICING
2633
+
2634
2634
# Offload task to update the pricing
2635
- # Force async, to prevent running in the foreground
2635
+ # Force async, to prevent running in the foreground (unless in testing mode)
2636
2636
InvenTree .tasks .offload_task (
2637
- part_tasks .update_part_pricing , self , counter = counter , force_async = True
2637
+ part_tasks .update_part_pricing ,
2638
+ self ,
2639
+ counter = counter ,
2640
+ force_async = background ,
2638
2641
)
2639
2642
2640
- def update_pricing (self , counter : int = 0 , cascade : bool = True ):
2641
- """Recalculate all cost data for the referenced Part instance."""
2642
- # If importing data, skip pricing update
2643
+ def update_pricing (
2644
+ self ,
2645
+ counter : int = 0 ,
2646
+ cascade : bool = True ,
2647
+ previous_min = None ,
2648
+ previous_max = None ,
2649
+ ):
2650
+ """Recalculate all cost data for the referenced Part instance.
2651
+
2652
+ Arguments:
2653
+ counter: Recursion counter (used to prevent infinite recursion)
2654
+ cascade: If True, update pricing for all assemblies and templates which use this part
2655
+ previous_min: Previous minimum price (used to prevent further updates if unchanged)
2656
+ previous_max: Previous maximum price (used to prevent further updates if unchanged)
2643
2657
2658
+ """
2659
+ # If importing data, skip pricing update
2644
2660
if InvenTree .ready .isImportingData ():
2645
2661
return
2646
2662
@@ -2671,18 +2687,25 @@ def update_pricing(self, counter: int = 0, cascade: bool = True):
2671
2687
# Background worker processes may try to concurrently update
2672
2688
pass
2673
2689
2690
+ pricing_changed = False
2691
+
2692
+ # Without previous pricing data, we assume that the pricing has changed
2693
+ if previous_min != self .overall_min or previous_max != self .overall_max :
2694
+ pricing_changed = True
2695
+
2674
2696
# Update parent assemblies and templates
2675
- if cascade and self . price_modified :
2697
+ if pricing_changed and cascade :
2676
2698
self .update_assemblies (counter )
2677
2699
self .update_templates (counter )
2678
2700
2679
2701
def update_assemblies (self , counter : int = 0 ):
2680
2702
"""Schedule updates for any assemblies which use this part."""
2681
2703
# If the linked Part is used in any assemblies, schedule a pricing update for those assemblies
2704
+
2682
2705
used_in_parts = self .part .get_used_in ()
2683
2706
2684
2707
for p in used_in_parts :
2685
- p .pricing .schedule_for_update (counter + 1 )
2708
+ p .pricing .schedule_for_update (counter = counter + 1 )
2686
2709
2687
2710
def update_templates (self , counter : int = 0 ):
2688
2711
"""Schedule updates for any template parts above this part."""
@@ -2698,13 +2721,13 @@ def save(self, *args, **kwargs):
2698
2721
2699
2722
try :
2700
2723
self .update_overall_cost ()
2701
- except IntegrityError :
2724
+ except Exception :
2702
2725
# If something has happened to the Part model, might throw an error
2703
2726
pass
2704
2727
2705
2728
try :
2706
2729
super ().save (* args , ** kwargs )
2707
- except IntegrityError :
2730
+ except Exception :
2708
2731
# This error may be thrown if there is already duplicate pricing data
2709
2732
pass
2710
2733
@@ -2772,9 +2795,6 @@ def update_bom_cost(self, save=True):
2772
2795
2773
2796
any_max_elements = True
2774
2797
2775
- old_bom_cost_min = self .bom_cost_min
2776
- old_bom_cost_max = self .bom_cost_max
2777
-
2778
2798
if any_min_elements :
2779
2799
self .bom_cost_min = cumulative_min
2780
2800
else :
@@ -2785,12 +2805,6 @@ def update_bom_cost(self, save=True):
2785
2805
else :
2786
2806
self .bom_cost_max = None
2787
2807
2788
- if (
2789
- old_bom_cost_min != self .bom_cost_min
2790
- or old_bom_cost_max != self .bom_cost_max
2791
- ):
2792
- self .price_modified = True
2793
-
2794
2808
if save :
2795
2809
self .save ()
2796
2810
@@ -2854,12 +2868,6 @@ def update_purchase_cost(self, save=True):
2854
2868
if purchase_max is None or cost > purchase_max :
2855
2869
purchase_max = cost
2856
2870
2857
- if (
2858
- self .purchase_cost_min != purchase_min
2859
- or self .purchase_cost_max != purchase_max
2860
- ):
2861
- self .price_modified = True
2862
-
2863
2871
self .purchase_cost_min = purchase_min
2864
2872
self .purchase_cost_max = purchase_max
2865
2873
@@ -2886,12 +2894,6 @@ def update_internal_cost(self, save=True):
2886
2894
if max_int_cost is None or cost > max_int_cost :
2887
2895
max_int_cost = cost
2888
2896
2889
- if (
2890
- self .internal_cost_min != min_int_cost
2891
- or self .internal_cost_max != max_int_cost
2892
- ):
2893
- self .price_modified = True
2894
-
2895
2897
self .internal_cost_min = min_int_cost
2896
2898
self .internal_cost_max = max_int_cost
2897
2899
@@ -2927,12 +2929,6 @@ def update_supplier_cost(self, save=True):
2927
2929
if max_sup_cost is None or cost > max_sup_cost :
2928
2930
max_sup_cost = cost
2929
2931
2930
- if (
2931
- self .supplier_price_min != min_sup_cost
2932
- or self .supplier_price_max != max_sup_cost
2933
- ):
2934
- self .price_modified = True
2935
-
2936
2932
self .supplier_price_min = min_sup_cost
2937
2933
self .supplier_price_max = max_sup_cost
2938
2934
@@ -2968,9 +2964,6 @@ def update_variant_cost(self, save=True):
2968
2964
if variant_max is None or v_max > variant_max :
2969
2965
variant_max = v_max
2970
2966
2971
- if self .variant_cost_min != variant_min or self .variant_cost_max != variant_max :
2972
- self .price_modified = True
2973
-
2974
2967
self .variant_cost_min = variant_min
2975
2968
self .variant_cost_max = variant_max
2976
2969
@@ -3091,12 +3084,6 @@ def update_sale_cost(self, save=True):
3091
3084
if max_sell_history is None or cost > max_sell_history :
3092
3085
max_sell_history = cost
3093
3086
3094
- if (
3095
- self .sale_history_min != min_sell_history
3096
- or self .sale_history_max != max_sell_history
3097
- ):
3098
- self .price_modified = True
3099
-
3100
3087
self .sale_history_min = min_sell_history
3101
3088
self .sale_history_max = max_sell_history
3102
3089
@@ -4509,7 +4496,10 @@ def update_bom_build_lines(sender, instance, created, **kwargs):
4509
4496
def update_pricing_after_edit (sender , instance , created , ** kwargs ):
4510
4497
"""Callback function when a part price break is created or updated."""
4511
4498
# Update part pricing *unless* we are importing data
4512
- if InvenTree .ready .canAppAccessDatabase () and not InvenTree .ready .isImportingData ():
4499
+ if (
4500
+ InvenTree .ready .canAppAccessDatabase (allow_test = settings .TESTING_PRICING )
4501
+ and not InvenTree .ready .isImportingData ()
4502
+ ):
4513
4503
if instance .part :
4514
4504
instance .part .schedule_pricing_update (create = True )
4515
4505
@@ -4526,7 +4516,10 @@ def update_pricing_after_edit(sender, instance, created, **kwargs):
4526
4516
def update_pricing_after_delete (sender , instance , ** kwargs ):
4527
4517
"""Callback function when a part price break is deleted."""
4528
4518
# Update part pricing *unless* we are importing data
4529
- if InvenTree .ready .canAppAccessDatabase () and not InvenTree .ready .isImportingData ():
4519
+ if (
4520
+ InvenTree .ready .canAppAccessDatabase (allow_test = settings .TESTING_PRICING )
4521
+ and not InvenTree .ready .isImportingData ()
4522
+ ):
4530
4523
if instance .part :
4531
4524
instance .part .schedule_pricing_update (create = False )
4532
4525
0 commit comments