1+ # Copyright 2025 Akretion (https://www.akretion.com).
2+ # @author Mathieu DELVA <mathieu.delva@akretion.com>
3+ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
4+ from odoo import fields , models
5+ from itertools import chain
6+ from odoo import fields , models , _
7+ from odoo .exceptions import UserError
8+
9+ class ProductPricelistItem (models .Model ):
10+ _inherit = "product.pricelist.item"
11+
12+ packaging_id = fields .Many2one ("product.packaging" )
13+
14+ def _is_applicable_for (self , product , qty_in_product_uom ):
15+ ctx = self .env .context
16+ print ("### _is_applicable_for TRIGGERED" , ctx )
17+ if "packaging" in ctx :
18+ if ctx ["packaging" ] == self .packaging_id :
19+ return super ()._is_applicable_for (product , qty_in_product_uom )
20+ return False
21+
22+ elif "packaging" not in ctx and self .packaging_id :
23+ return False
24+
25+ return super ()._is_applicable_for (product , qty_in_product_uom )
26+
27+ class ProductPricelist (models .Model ):
28+ _inherit = "product.pricelist"
29+
30+ def _compute_price_rule (self , products_qty_partner , date = False , uom_id = False ):
31+ """ Low-level method - Mono pricelist, multi products
32+ Returns: dict{product_id: (price, suitable_rule) for the given pricelist}
33+
34+ Date in context can be a date, datetime, ...
35+
36+ :param products_qty_partner: list of typles products, quantity, partner
37+ :param datetime date: validity date
38+ :param ID uom_id: intermediate unit of measure
39+ """
40+ self .ensure_one ()
41+ if not date :
42+ date = self ._context .get ('date' ) or fields .Datetime .now ()
43+ if not uom_id and self ._context .get ('uom' ):
44+ uom_id = self ._context ['uom' ]
45+ if uom_id :
46+ # rebrowse with uom if given
47+ products = [item [0 ].with_context (uom = uom_id ) for item in products_qty_partner ]
48+ products_qty_partner = [(products [index ], data_struct [1 ], data_struct [2 ]) for index , data_struct in enumerate (products_qty_partner )]
49+ else :
50+ products = [item [0 ] for item in products_qty_partner ]
51+
52+ if not products :
53+ return {}
54+
55+ categ_ids = {}
56+ for p in products :
57+ categ = p .categ_id
58+ while categ :
59+ categ_ids [categ .id ] = True
60+ categ = categ .parent_id
61+ categ_ids = list (categ_ids )
62+
63+ is_product_template = products [0 ]._name == "product.template"
64+ if is_product_template :
65+ prod_tmpl_ids = [tmpl .id for tmpl in products ]
66+ # all variants of all products
67+ prod_ids = [p .id for p in
68+ list (chain .from_iterable ([t .product_variant_ids for t in products ]))]
69+ else :
70+ prod_ids = [product .id for product in products ]
71+ prod_tmpl_ids = [product .product_tmpl_id .id for product in products ]
72+
73+ items = self ._compute_price_rule_get_items (products_qty_partner , date , uom_id , prod_tmpl_ids , prod_ids , categ_ids )
74+ print ("### items TRIGGERED" , items )
75+ results = {}
76+ for product , qty , partner in products_qty_partner :
77+ results [product .id ] = 0.0
78+ suitable_rule = False
79+
80+ # Final unit price is computed according to `qty` in the `qty_uom_id` UoM.
81+ # An intermediary unit price may be computed according to a different UoM, in
82+ # which case the price_uom_id contains that UoM.
83+ # The final price will be converted to match `qty_uom_id`.
84+ qty_uom_id = self ._context .get ('uom' ) or product .uom_id .id
85+ qty_in_product_uom = qty
86+ if qty_uom_id != product .uom_id .id :
87+ try :
88+ qty_in_product_uom = self .env ['uom.uom' ].browse ([self ._context ['uom' ]])._compute_quantity (qty , product .uom_id )
89+ except UserError :
90+ # Ignored - incompatible UoM in context, use default product UoM
91+ pass
92+
93+ # if Public user try to access standard price from website sale, need to call price_compute.
94+ # TDE SURPRISE: product can actually be a template
95+ price = product .price_compute ('list_price' )[product .id ]
96+
97+ price_uom = self .env ['uom.uom' ].browse ([qty_uom_id ])
98+ for rule in items :
99+ print ("### rule TRIGGERED" , rule )
100+ if not rule ._is_applicable_for (product , qty_in_product_uom ):
101+ continue
102+ if rule .base == 'pricelist' and rule .base_pricelist_id :
103+ price = rule .base_pricelist_id ._compute_price_rule ([(product , qty , partner )], date , uom_id )[product .id ][0 ] # TDE: 0 = price, 1 = rule
104+ src_currency = rule .base_pricelist_id .currency_id
105+ else :
106+ # if base option is public price take sale price else cost price of product
107+ # price_compute returns the price in the context UoM, i.e. qty_uom_id
108+ price = product .price_compute (rule .base )[product .id ]
109+ if rule .base == 'standard_price' :
110+ src_currency = product .cost_currency_id
111+ else :
112+ src_currency = product .currency_id
113+
114+ if src_currency != self .currency_id :
115+ price = src_currency ._convert (
116+ price , self .currency_id , self .env .company , date , round = False )
117+
118+ if price is not False :
119+ price = rule ._compute_price (price , price_uom , product , quantity = qty , partner = partner )
120+ suitable_rule = rule
121+ break
122+
123+ if not suitable_rule :
124+ cur = product .currency_id
125+ price = cur ._convert (price , self .currency_id , self .env .company , date , round = False )
126+
127+ results [product .id ] = (price , suitable_rule and suitable_rule .id or False )
128+
129+ return results
0 commit comments