import logging from odoo import fields from odoo.http import request _logger = logging.getLogger(__name__) def _prepare_product_display_info(self, product, product_price_info, request_obj=None): price_data = product_price_info.get(product.id, {}) price = ( price_data.get("price", product.list_price) if price_data else product.list_price ) price_safe = float(price) if price else 0.0 uom_category_name = "" quantity_step = 1 price_unit_suffix = "" bulk_unit_suffixes = { "uom.product_uom_categ_kgm": "/Kg", "uom.product_uom_categ_vol": "/L", } if product.uom_id: uom = product.uom_id.sudo() if uom.category_id: uom_category_name = uom.category_id.sudo().name or "" try: try: req = request_obj or request ir_model_data = req.env["ir.model.data"].sudo() except RuntimeError: ir_model_data = product.env["ir.model.data"].sudo() external_id = ir_model_data.search( [ ("model", "=", "uom.category"), ("res_id", "=", uom.category_id.id), ], limit=1, ) if external_id: fractional_categories = [ "uom.product_uom_categ_kgm", "uom.product_uom_categ_vol", "uom.uom_categ_length", "uom.uom_categ_surface", ] full_xmlid = f"{external_id.module}.{external_id.name}" if full_xmlid in fractional_categories: quantity_step = 0.1 price_unit_suffix = bulk_unit_suffixes.get(full_xmlid, "") except Exception as e: _logger.warning( "_prepare_product_display_info: Error detecting UoM category XML ID for product %s: %s", product.id, str(e), ) base_unit_price = 0.0 if product.base_unit_count and price_safe: base_unit_price = price_safe / product.base_unit_count try: req = request_obj or request tr_env = req.env except RuntimeError: tr_env = product.env out_of_stock_label = tr_env._("Out of stock") add_to_cart_label = tr_env._( "Add %(product_name)s to cart", product_name=product.name ) return { "display_price": price_safe, "safe_uom_category": uom_category_name, "quantity_step": quantity_step, "price_unit_suffix": price_unit_suffix, "base_unit_price": base_unit_price, "out_of_stock_label": out_of_stock_label, "add_to_cart_label": add_to_cart_label, } def _pricing_context(record, request_obj=None): """Return (env, website), both under an HTTP request and from the cron.""" try: req = request_obj or request return req.env, req.website except RuntimeError: env = record.env return env, env["website"].get_current_website() def _pricing_company(product, website, env): website_company = ( website.company_id if website and getattr(website, "company_id", False) else False ) return website_company or product.company_id or env.company def _display_taxes(product, website, company): """Product taxes, and those same taxes after the website fiscal position.""" product_taxes = product.sudo().taxes_id._filter_taxes_by_company(company) if not product_taxes: return product_taxes, product_taxes fiscal_position = ( website.fiscal_position_id.sudo() if website and getattr(website, "fiscal_position_id", False) else product.env["account.fiscal.position"].sudo() ) return product_taxes, fiscal_position.map_tax(product_taxes) def _display_price(product, price, currency, product_taxes, taxes, website): """Price as the shop displays it, computed by website_sale itself. `product.template._apply_taxes_to_price` rebases the price onto the taxes the fiscal position mapped to (which matters for tax-included taxes) and then honours the website's `show_line_subtotals_tax_selection`. """ return product.product_tmpl_id._apply_taxes_to_price( price, currency, product_taxes, taxes, product, website=website ) def _build_pricing_info( product, price, pricelist_item, currency, quantity, website, company ): """Assemble the pricing dict from an already resolved pricelist price.""" price_before_discount = price if pricelist_item and pricelist_item._show_discount_on_shop(): price_before_discount = pricelist_item._compute_price_before_discount( product=product, quantity=quantity or 1.0, date=fields.Date.context_today(product), uom=product.uom_id, currency=currency, ) product_taxes, taxes = _display_taxes(product, website, company) display_price = _display_price( product, price, currency, product_taxes, taxes, website ) display_list_price = _display_price( product, price_before_discount, currency, product_taxes, taxes, website ) return { "price_unit": price, "price": display_price, "list_price": display_list_price, "has_discounted_price": price_before_discount > price, "discount": display_list_price - display_price, "tax_included": _tax_included(website), } def _tax_included(website): """Whether displayed prices carry tax, per the website setting.""" if not website: return True return website.show_line_subtotals_tax_selection != "tax_excluded" def _get_pricing_info( self, product, pricelist, quantity=1.0, request_obj=None, ): """Price one product. No partner argument: website_sale bills the tax display to the current user's partner itself, which is the same partner every caller here passed. """ env, website = _pricing_context(product, request_obj) currency = pricelist.currency_id # No currency kwarg: _compute_price_rule already defaults to the # pricelist's own currency, which is what we want here. price, rule_id = pricelist._get_product_price_rule(product, quantity) return _build_pricing_info( product, price, env["product.pricelist.item"].sudo().browse(rule_id), currency, quantity, website, _pricing_company(product, website, env), ) def _pricing_variant(product): """The variant to price: the record itself when it already is one.""" if product._name == "product.product": return product return product.product_variant_ids[:1] def _fallback_pricing(product, website): """Plain list price, used when there is no pricelist or pricing failed.""" price = product.list_price return { "price_unit": price, "price": price, "list_price": price, "has_discounted_price": False, "discount": 0.0, "tax_included": _tax_included(website), } def _compute_price_info(self, products, pricelist, request_obj=None): """Price a whole page of products with a single pricelist resolution.""" if not products: return {} env, website = _pricing_context(products, request_obj) currency = pricelist.currency_id if pricelist else None variants = {product.id: _pricing_variant(product) for product in products} priceable = env["product.product"].browse() for variant in variants.values(): priceable |= variant # One pricelist resolution for the whole page rather than one per product. rules = ( pricelist._compute_price_rule(priceable, 1.0) if pricelist and priceable else {} ) product_price_info = {} for product in products: variant = variants[product.id] if not variant or variant.id not in rules: product_price_info[product.id] = _fallback_pricing(product, website) continue try: price, rule_id = rules[variant.id] product_price_info[product.id] = _build_pricing_info( variant, price, env["product.pricelist.item"].sudo().browse(rule_id), currency, 1.0, website, _pricing_company(variant, website, env), ) except Exception as e: _logger.warning( "_compute_price_info: Error getting price for product %s (id=%s): %s. Using list_price fallback.", product.name, product.id, str(e), ) product_price_info[product.id] = _fallback_pricing(product, website) return product_price_info def _get_product_supplier_info(self, products): product_supplier_info = {} for product in products: supplier_name = "" if product.seller_ids: partner = product.seller_ids[0].partner_id.sudo() supplier_name = partner.comercial or partner.name or "" if partner.city: supplier_name += f" ({partner.city})" product_supplier_info[product.id] = supplier_name return product_supplier_info def _get_delivery_product_display_price( self, delivery_product, pricelist=None, request_obj=None ): if not delivery_product: return 0.0 try: base_price = float(delivery_product.list_price or 0.0) env, website = _pricing_context(delivery_product, request_obj) company = _pricing_company(delivery_product, website, env) product_taxes, taxes = _display_taxes(delivery_product, website, company) if not taxes: return base_price return float( _display_price( delivery_product, base_price, website.currency_id, product_taxes, taxes, website, ) or 0.0 ) except Exception as e: _logger.warning( "_get_delivery_product_display_price: Error computing delivery display price for product %s (id=%s): %s. Using list_price fallback.", delivery_product.name, delivery_product.id, str(e), ) return float(delivery_product.list_price or 0.0)