@@ -9,7 +9,10 @@ class ProductPricelistItem(models.Model):
99 selection_add = [
1010 ("3_1_global_product_template" , "Global - Product template" ),
1111 ("3_2_global_product_category" , "Global - Product category" ),
12- ("3_3_global_product_ancestor_category" , "Global - Ancestor Product Category" ),
12+ (
13+ "3_3_global_product_ancestor_category" ,
14+ "Global - Ancestor Product Category" ,
15+ ),
1316 ],
1417 ondelete = {
1518 "3_1_global_product_template" : "set default" ,
@@ -66,10 +69,12 @@ def _check_product_consistency(self):
6669 item .applied_on == "3_3_global_product_ancestor_category"
6770 and not item .ancestor_product_category_id
6871 ):
69- raise ValidationError (_ (
70- "Please specify the product ancestor category for which this global"
71- " rule should be applied"
72- ))
72+ raise ValidationError (
73+ _ (
74+ "Please specify the product ancestor category for which this global"
75+ " rule should be applied"
76+ )
77+ )
7378 return res
7479
7580 @api .depends (
@@ -108,7 +113,8 @@ def _compute_name_and_price(self):
108113 and item .applied_on == "3_3_global_product_ancestor_category"
109114 ):
110115 item .name = _ ("Ancestor product category: %s" ) % (
111- item .ancestor_product_category_id .display_name )
116+ item .ancestor_product_category_id .display_name
117+ )
112118 return res
113119
114120 @api .model_create_multi
@@ -138,13 +144,15 @@ def create(self, vals_list):
138144 }
139145 )
140146 elif applied_on == "3_3_global_product_ancestor_category" :
141- values .update ({
142- "product_id" : None ,
143- "product_tmpl_id" : None ,
144- "categ_id" : None ,
145- "global_categ_id" : None ,
146- "global_product_tmpl_id" : None ,
147- })
147+ values .update (
148+ {
149+ "product_id" : None ,
150+ "product_tmpl_id" : None ,
151+ "categ_id" : None ,
152+ "global_categ_id" : None ,
153+ "global_product_tmpl_id" : None ,
154+ }
155+ )
148156 return super ().create (vals_list )
149157
150158 def write (self , values ):
@@ -196,7 +204,9 @@ def _is_applicable_for(self, product, qty_in_product_uom):
196204 :rtype: bool
197205 """
198206 self .ensure_one ()
199- qty_data = self .env .context .get ("pricelist_global_cummulative_quantity" , {}) or {}
207+ qty_data = (
208+ self .env .context .get ("pricelist_global_cummulative_quantity" , {}) or {}
209+ )
200210 supported = {
201211 "3_1_global_product_template" ,
202212 "3_2_global_product_category" ,
@@ -222,6 +232,7 @@ def _is_applicable_for(self, product, qty_in_product_uom):
222232 self .global_categ_id .parent_path
223233 ):
224234 is_applicable = False
235+ # Global Product Category
225236 elif self .applied_on == "3_3_global_product_ancestor_category" :
226237 ancestor_categ = self .ancestor_product_category_id
227238 if not ancestor_categ :
@@ -262,38 +273,44 @@ def _is_applicable_for(self, product, qty_in_product_uom):
262273 # because it comes first. Therefore, we need the code below to check if the
263274 # current price item is already higher discount.
264275 # --------------------------------------------------------------
276+ if self ._has_better_percentage_rule (product , qty_in_product_uom ):
277+ is_applicable = False
278+ return is_applicable
279+
280+ def _has_better_percentage_rule (self , product , qty_in_product_uom ):
281+ """
282+ Check if there is another percentage rule that should take precedence.
283+ This prevents a weaker discount (lower percent_price) from overriding
284+ a stronger one when multiple percentage rules apply.
285+ """
265286 if (
266- self .compute_price = = "percentage"
267- and self .percent_price
268- and not self .env .context .get ("skip_best_percent_check" )
287+ self .compute_price ! = "percentage"
288+ or not self .percent_price
289+ or self .env .context .get ("skip_best_percent_check" )
269290 ):
270- ctx = dict (self .env .context or {})
271- # The context flag `skip_best_percent_check` is used to avoid
272- # infinite recursion when we call `_is_applicable_for` on rivals.
273- ctx ["skip_best_percent_check" ] = True
274- # Check all percentage rules in the same pricelist
275- items = self .pricelist_id .item_ids .with_context (ctx ).filtered (
276- lambda it : it .id != self .id
277- and it .compute_price == "percentage"
278- and it .percent_price not in (False , None )
279- )
291+ return False
280292
281- for it in items :
282- # Ask each rival if it applies to THIS product & qty
283- if not it ._is_applicable_for (product , qty_in_product_uom ):
284- continue
293+ # Fetch all other percentage rules from the same pricelist
294+ # context skip_best_percent_check to avoid run forever
295+ items = self .pricelist_id .item_ids .with_context (
296+ skip_best_percent_check = True
297+ ).filtered (
298+ lambda it : it .id != self .id
299+ and it .compute_price == "percentage"
300+ and it .percent_price not in (False , None )
301+ )
302+ for it in items :
303+ # Check if rival rule also applies to this product & quantity
304+ if not it ._is_applicable_for (product , qty_in_product_uom ):
305+ continue
285306
286- # Among percentage rules that are all applicable:
287- # 1. Higher percent_price is always preferred.
288- # 2. If tied, the rule created earlier (smaller create_date)
289- # wins.
290- if (
291- it .percent_price > self .percent_price
292- or (
293- it .percent_price == self .percent_price
294- and it .create_date < self .create_date
295- )
296- ):
297- is_applicable = False
298- break
299- return is_applicable
307+ # Among applicable percentage rules:
308+ # 1. Prefer higher percent_price
309+ # 2. If tied, prefer older rule (smaller create_date)
310+ if it .percent_price > self .percent_price or (
311+ it .percent_price == self .percent_price
312+ and it .create_date < self .create_date
313+ ):
314+ return True
315+
316+ return False
0 commit comments