@@ -9,15 +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- (
13- "3_3_global_product_ancestor_category" ,
14- "Global - Ancestor Product Category" ,
15- ),
1612 ],
1713 ondelete = {
1814 "3_1_global_product_template" : "set default" ,
1915 "3_2_global_product_category" : "set default" ,
20- "3_3_global_product_ancestor_category" : "set default" ,
2116 },
2217 )
2318 global_product_tmpl_id = fields .Many2one (
@@ -31,9 +26,6 @@ class ProductPricelistItem(models.Model):
3126 "Product Category" ,
3227 ondelete = "cascade" ,
3328 )
34- ancestor_product_category_id = fields .Many2one (
35- "product.category" , ondelete = "cascade"
36- )
3729
3830 @api .constrains (
3931 "product_id" ,
@@ -65,16 +57,6 @@ def _check_product_consistency(self):
6557 "for which this global rule should be applied"
6658 )
6759 )
68- elif (
69- item .applied_on == "3_3_global_product_ancestor_category"
70- and not item .ancestor_product_category_id
71- ):
72- raise ValidationError (
73- _ (
74- "Please specify the product ancestor category for which this global"
75- " rule should be applied"
76- )
77- )
7860 return res
7961
8062 @api .depends (
@@ -108,13 +90,6 @@ def _compute_name_and_price(self):
10890 item .name = _ ("Global product: %s" ) % (
10991 item .global_product_tmpl_id .display_name
11092 )
111- elif (
112- item .ancestor_product_category_id
113- and item .applied_on == "3_3_global_product_ancestor_category"
114- ):
115- item .name = _ ("Ancestor product category: %s" ) % (
116- item .ancestor_product_category_id .display_name
117- )
11893 return res
11994
12095 @api .model_create_multi
@@ -130,7 +105,6 @@ def create(self, vals_list):
130105 "product_tmpl_id" : None ,
131106 "categ_id" : None ,
132107 "global_product_tmpl_id" : None ,
133- "ancestor_product_category_id" : None ,
134108 }
135109 )
136110 elif applied_on == "3_1_global_product_template" :
@@ -140,17 +114,6 @@ def create(self, vals_list):
140114 "product_tmpl_id" : None ,
141115 "categ_id" : None ,
142116 "global_categ_id" : None ,
143- "ancestor_product_category_id" : None ,
144- }
145- )
146- elif applied_on == "3_3_global_product_ancestor_category" :
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 ,
154117 }
155118 )
156119 return super ().create (vals_list )
@@ -166,7 +129,6 @@ def write(self, values):
166129 "product_tmpl_id" : None ,
167130 "categ_id" : None ,
168131 "global_product_tmpl_id" : None ,
169- "ancestor_product_category_id" : None ,
170132 }
171133 )
172134 elif applied_on == "3_1_global_product_template" :
@@ -176,17 +138,6 @@ def write(self, values):
176138 "product_tmpl_id" : None ,
177139 "categ_id" : None ,
178140 "global_categ_id" : None ,
179- "ancestor_product_category_id" : None ,
180- }
181- )
182- elif applied_on == "3_3_global_product_ancestor_category" :
183- values .update (
184- {
185- "product_id" : None ,
186- "product_tmpl_id" : None ,
187- "categ_id" : None ,
188- "global_categ_id" : None ,
189- "global_product_tmpl_id" : None ,
190141 }
191142 )
192143 return super ().write (values )
@@ -204,26 +155,19 @@ def _is_applicable_for(self, product, qty_in_product_uom):
204155 :rtype: bool
205156 """
206157 self .ensure_one ()
207- qty_data = (
208- self .env .context .get ("pricelist_global_cummulative_quantity" , {}) or {}
209- )
210- supported = {
158+ qty_data = self .env .context .get ("pricelist_global_cummulative_quantity" , {})
159+ if not qty_data or self .applied_on not in [
211160 "3_1_global_product_template" ,
212161 "3_2_global_product_category" ,
213- "3_3_global_product_ancestor_category" ,
214- }
215- # Fallback to base behavior if not a supported global case or no context
216- if not qty_data or self .applied_on not in supported :
162+ ]:
217163 return super ()._is_applicable_for (product , qty_in_product_uom )
218-
219164 is_applicable = True
220165 if self .applied_on == "3_1_global_product_template" :
221166 total_qty = qty_data ["by_template" ].get (product .product_tmpl_id , 0.0 )
222167 if self .min_quantity and total_qty < self .min_quantity :
223168 is_applicable = False
224169 elif self .global_product_tmpl_id != product .product_tmpl_id :
225170 is_applicable = False
226- # Global Product Category
227171 elif self .applied_on == "3_2_global_product_category" :
228172 total_qty = qty_data ["by_categ" ].get (product .categ_id , 0.0 )
229173 if self .min_quantity and total_qty < self .min_quantity :
@@ -232,85 +176,4 @@ def _is_applicable_for(self, product, qty_in_product_uom):
232176 self .global_categ_id .parent_path
233177 ):
234178 is_applicable = False
235- # Global Product Category
236- elif self .applied_on == "3_3_global_product_ancestor_category" :
237- ancestor_categ = self .ancestor_product_category_id
238- if not ancestor_categ :
239- return False
240-
241- Category = self .env ["product.category" ]
242-
243- # ancestor + all descendants (fast, uses parent_path internally)
244- child_categories = Category .search ([("id" , "child_of" , ancestor_categ .id )])
245-
246- # product's category must belong to this ancestor branch
247- prod_categ = product .categ_id
248- if not prod_categ or prod_categ not in child_categories :
249- return False
250-
251- # Normalize by_categ keys to ids: {id: qty}
252- by_categ_raw = qty_data .get ("by_categ" ) or {}
253- by_categ_id = {
254- (k .id if hasattr (k , "id" ) else int (k )): v
255- for k , v in by_categ_raw .items ()
256- }
257-
258- # Sum total quantity across the whole ancestor branch
259- total_qty = sum (by_categ_id .get (cid , 0.0 ) for cid in child_categories .ids )
260-
261- # Check minimum quantity threshold over the branch total
262- if self .min_quantity and total_qty < self .min_quantity :
263- is_applicable = False
264-
265- if not is_applicable :
266- return False
267- # --------------------------------------------------------------
268- # Ensure current price item has best discount
269- #
270- # By default Odoo will just pick the first applicable rule based on sequence.
271- # That means if two percentage rules apply to the same product
272- # (e.g. Cat A = 10%, Cat C = 20%), the system might pick the 10% rule just
273- # because it comes first. Therefore, we need the code below to check if the
274- # current price item is already higher discount.
275- # --------------------------------------------------------------
276- if self ._has_better_percentage_rule (product , qty_in_product_uom ):
277- is_applicable = False
278179 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- """
286- if (
287- self .compute_price != "percentage"
288- or not self .percent_price
289- or self .env .context .get ("skip_best_percent_check" )
290- ):
291- return False
292-
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
306-
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