diff --git a/website_sale_aplicoop/__manifest__.py b/website_sale_aplicoop/__manifest__.py index ddf73ea..97c2a0e 100644 --- a/website_sale_aplicoop/__manifest__.py +++ b/website_sale_aplicoop/__manifest__.py @@ -3,7 +3,7 @@ { # noqa: B018 "name": "Website Sale - Aplicoop", - "version": "18.0.1.14.0", + "version": "18.0.1.15.0", "category": "Website/Sale", "summary": "Modern replacement of legacy Aplicoop - Collaborative consumption group orders", "author": "Odoo Community Association (OCA), Criptomart", diff --git a/website_sale_aplicoop/controllers/website_sale.py b/website_sale_aplicoop/controllers/website_sale.py index 4670f14..28404b1 100644 --- a/website_sale_aplicoop/controllers/website_sale.py +++ b/website_sale_aplicoop/controllers/website_sale.py @@ -25,6 +25,7 @@ from .exceptions import GroupOrderUnavailable _logger = logging.getLogger(__name__) + class AplicoopWebsiteSale(WebsiteSale): """Controlador personalizado para website_sale de Aplicoop. @@ -54,7 +55,12 @@ class AplicoopWebsiteSale(WebsiteSale): # ========== PHASE 1: HELPER METHODS FOR VALIDATION AND CONFIGURATION ========== def _resolve_pricelist(self): - return _pricing._resolve_pricelist(self, request) + """The pricelist to quote in: whatever website_sale resolved. + + Eskaera used to carry its own pricelist setting; it now prices with the + standard one, so the shop, the cart and these pages cannot drift apart. + """ + return request.website.pricelist_id def _prepare_product_display_info(self, product, product_price_info): return _pricing._prepare_product_display_info( @@ -64,13 +70,12 @@ class AplicoopWebsiteSale(WebsiteSale): request, ) - def _get_pricing_info(self, product, pricelist, quantity=1.0, partner=None): + def _get_pricing_info(self, product, pricelist, quantity=1.0): return _pricing._get_pricing_info( self, product, pricelist, quantity=quantity, - partner=partner, request_obj=request, ) @@ -176,7 +181,6 @@ class AplicoopWebsiteSale(WebsiteSale): """ sale_order_lines = [] pricelist = pricelist or self._resolve_pricelist() - partner = request.env.user.partner_id for item in items: try: @@ -198,7 +202,6 @@ class AplicoopWebsiteSale(WebsiteSale): product, pricelist, quantity=quantity, - partner=partner, ) line_data = { @@ -1657,13 +1660,11 @@ class AplicoopWebsiteSale(WebsiteSale): # Extract items from the draft order items = [] pricelist = self._resolve_pricelist() - partner = current_user.partner_id for line in draft_order.order_line: pricing = self._get_pricing_info( line.product_id, pricelist, quantity=line.product_uom_qty, - partner=partner, ) items.append( { diff --git a/website_sale_aplicoop/controllers/website_sale_pricing.py b/website_sale_aplicoop/controllers/website_sale_pricing.py index 9deef53..66a0fec 100644 --- a/website_sale_aplicoop/controllers/website_sale_pricing.py +++ b/website_sale_aplicoop/controllers/website_sale_pricing.py @@ -6,42 +6,6 @@ from odoo.http import request _logger = logging.getLogger(__name__) -def _resolve_pricelist(self, request_obj=None): - try: - req = request_obj or request - env = req.env - website = req.website - except RuntimeError: - env = getattr(self, "env", None) or self.env - website = env["website"].get_current_website() - pricelist = None - try: - param_value = ( - env["ir.config_parameter"] - .sudo() - .get_param("website_sale_aplicoop.pricelist_id") - ) - if param_value: - pricelist = ( - env["product.pricelist"].browse(int(param_value)).exists() or None - ) - except Exception as e: - _logger.warning("_resolve_pricelist: error reading config param: %s", e) - - if not pricelist: - try: - pricelist = website._get_current_pricelist() - except Exception as e: - _logger.warning( - "_resolve_pricelist: fallback to website pricelist failed: %s", e - ) - - if not pricelist: - pricelist = env["product.pricelist"].sudo().search([], limit=1) - - return pricelist - - def _prepare_product_display_info(self, product, product_price_info, request_obj=None): price_data = product_price_info.get(product.id, {}) price = ( @@ -119,132 +83,182 @@ def _prepare_product_display_info(self, product, product_price_info, request_obj } -def _get_pricing_info( - self, - product, - pricelist, - quantity=1.0, - partner=None, - request_obj=None, -): - req = request_obj or request +def _pricing_context(record, request_obj=None): + """Return (env, website), both under an HTTP request and from the cron.""" try: - env = req.env - website = req.website + req = request_obj or request + return req.env, req.website except RuntimeError: - env = product.env - website = env["website"].get_current_website() + env = record.env + return env, env["website"].get_current_website() - partner = partner or env.user.partner_id - currency = pricelist.currency_id + +def _pricing_company(product, website, env): website_company = ( website.company_id if website and getattr(website, "company_id", False) else False ) - company = website_company or product.company_id or env.company + return website_company or product.company_id or env.company - price, rule_id = pricelist._get_product_price_rule( - product=product, quantity=quantity, target_currency=currency + +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 - pricelist_item = env["product.pricelist.item"].sudo().browse(rule_id) 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(pricelist), + date=fields.Date.context_today(product), uom=product.uom_id, currency=currency, ) - has_discounted_price = price_before_discount > price - - fiscal_position = ( - website.fiscal_position_id.sudo() - if website and getattr(website, "fiscal_position_id", False) - else env["account.fiscal.position"].sudo() + 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 ) - product_taxes = product.sudo().taxes_id._filter_taxes_by_company(company) - taxes = fiscal_position.map_tax(product_taxes) if product_taxes else product_taxes - tax_display = "total_included" - - def compute_display(amount): - if not taxes: - return amount - return taxes.compute_all(amount, currency, 1, product, partner)[tax_display] - - display_price = compute_display(price) - display_list_price = compute_display(price_before_discount) return { "price_unit": price, "price": display_price, "list_price": display_list_price, - "has_discounted_price": has_discounted_price, + "has_discounted_price": price_before_discount > price, "discount": display_list_price - display_price, - "tax_included": tax_display == "total_included", + "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 = {} - - def _tax_included_default(product_record): - try: - req = request_obj or request - return req.website.show_line_subtotals_tax_selection != "tax_excluded" - except RuntimeError: - website = product_record.env["website"].get_current_website() - if not website: - return True - return website.show_line_subtotals_tax_selection != "tax_excluded" - for product in products: - product_variant = ( - product.product_variant_ids[0] if product.product_variant_ids else False - ) - if product_variant and pricelist: - try: - try: - req = request_obj or request - partner = req.env.user.partner_id - except RuntimeError: - partner = product_variant.env.user.partner_id - - pricing = _get_pricing_info( - self, - product_variant, - pricelist, - quantity=1.0, - partner=partner, - request_obj=request_obj, - ) - product_price_info[product.id] = pricing - 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] = { - "price_unit": product.list_price, - "price": product.list_price, - "list_price": product.list_price, - "has_discounted_price": False, - "discount": 0.0, - "tax_included": _tax_included_default(product), - } - else: - product_price_info[product.id] = { - "price_unit": product.list_price, - "price": product.list_price, - "list_price": product.list_price, - "has_discounted_price": False, - "discount": 0.0, - "tax_included": _tax_included_default(product), - } + 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 @@ -265,46 +279,27 @@ def _get_delivery_product_display_price( self, delivery_product, pricelist=None, request_obj=None ): if not delivery_product: - return 5.74 + return 0.0 try: base_price = float(delivery_product.list_price or 0.0) - try: - req = request_obj or request - website = req.website - partner = req.env.user.partner_id - company = ( - website.company_id or delivery_product.company_id or req.env.company - ) - except RuntimeError: - env = delivery_product.env - website = env["website"].get_current_website() - partner = env.user.partner_id - company = website.company_id or delivery_product.company_id or env.company - - product_taxes = delivery_product.sudo().taxes_id._filter_taxes_by_company( - company - ) - fiscal_position = ( - website.fiscal_position_id.sudo() - if website and getattr(website, "fiscal_position_id", False) - else delivery_product.env["account.fiscal.position"] - ) - taxes = ( - fiscal_position.map_tax(product_taxes) if product_taxes else product_taxes - ) + 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 - currency = website.currency_id - totals = taxes.compute_all( - base_price, - currency=currency, - quantity=1.0, - product=delivery_product, - partner=partner, + return float( + _display_price( + delivery_product, + base_price, + website.currency_id, + product_taxes, + taxes, + website, + ) + or 0.0 ) - return float(totals.get("total_included", base_price) 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.", diff --git a/website_sale_aplicoop/migrations/18.0.1.15.0/post-migrate.py b/website_sale_aplicoop/migrations/18.0.1.15.0/post-migrate.py new file mode 100644 index 0000000..12fe7bb --- /dev/null +++ b/website_sale_aplicoop/migrations/18.0.1.15.0/post-migrate.py @@ -0,0 +1,39 @@ +"""Drop the Eskaera-specific pricelist setting. + +Eskaera used to resolve its own pricelist from +`website_sale_aplicoop.pricelist_id`, which let it quote prices the rest of the +shop did not use. Pricing is now the standard website_sale one, so the setting +is gone and its parameter would otherwise linger in the database, looking like +live configuration. +""" + +import logging + +from odoo import SUPERUSER_ID +from odoo import api + +_logger = logging.getLogger(__name__) + +OBSOLETE_PARAM = "website_sale_aplicoop.pricelist_id" + + +def migrate(cr, version): + if not version: + return + + env = api.Environment(cr, SUPERUSER_ID, {}) + param = ( + env["ir.config_parameter"] + .sudo() + .search([("key", "=", OBSOLETE_PARAM)], limit=1) + ) + if not param: + return + + _logger.info( + "Removing obsolete config parameter %s (was pointing at pricelist %s). " + "Eskaera now prices with the website pricelist.", + OBSOLETE_PARAM, + param.value, + ) + param.unlink() diff --git a/website_sale_aplicoop/models/res_config_settings.py b/website_sale_aplicoop/models/res_config_settings.py index 03aaca1..a363a09 100644 --- a/website_sale_aplicoop/models/res_config_settings.py +++ b/website_sale_aplicoop/models/res_config_settings.py @@ -7,12 +7,6 @@ from odoo import models class ResConfigSettings(models.TransientModel): _inherit = "res.config.settings" - aplicoop_pricelist_id = fields.Many2one( - "product.pricelist", - config_parameter="website_sale_aplicoop.pricelist_id", - help="Pricelist to use for Aplicoop group orders. If not set, will use website default.", - ) - eskaera_lazy_loading_enabled = fields.Boolean( string="Enable Lazy Loading", config_parameter="website_sale_aplicoop.lazy_loading_enabled", diff --git a/website_sale_aplicoop/tests/__init__.py b/website_sale_aplicoop/tests/__init__.py index 5afc215..8c6abf2 100644 --- a/website_sale_aplicoop/tests/__init__.py +++ b/website_sale_aplicoop/tests/__init__.py @@ -23,3 +23,4 @@ from . import test_product_discovery # noqa: F401 from . import test_date_edge_cases # noqa: F401 from . import test_portal_routes # noqa: F401 from . import test_price_with_taxes_included # noqa: F401 +from . import test_pricing_delegation # noqa: F401 diff --git a/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py b/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py index 985602d..e3488a6 100644 --- a/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py +++ b/website_sale_aplicoop/tests/test_phase3_confirm_eskaera.py @@ -58,8 +58,14 @@ def _build_request_mock(env, payload=None, website=None): call pricing helpers). """ if website is None: + # `pricelist_id` is what the pricing helpers read now that Eskaera + # prices with the standard website pricelist. Ordered by id on + # purpose: the multilang cases build an env whose language is not + # installed, and the default order is by the translated `name`. website = SimpleNamespace( - _get_current_pricelist=lambda: False, + pricelist_id=env["product.pricelist"] + .sudo() + .search([], limit=1, order="id"), show_line_subtotals_tax_selection="tax_excluded", fiscal_position_id=False, company_id=False, diff --git a/website_sale_aplicoop/tests/test_pricing_delegation.py b/website_sale_aplicoop/tests/test_pricing_delegation.py new file mode 100644 index 0000000..1481511 --- /dev/null +++ b/website_sale_aplicoop/tests/test_pricing_delegation.py @@ -0,0 +1,280 @@ +# Copyright 2025 Criptomart +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) + +"""Pricing that is delegated to website_sale instead of reimplemented. + +Three things this pins down: + +- Eskaera prices with the standard website pricelist. The addon no longer + carries a pricelist setting of its own, nor overrides how the website + resolves one, so `/shop` and Eskaera cannot quote different prices. +- taxes are applied by `product.template._apply_taxes_to_price`, so a fiscal + position that remaps a tax-included tax rebases the price -- the step this + addon used to skip. +- a page of products costs one pricelist resolution, not one per product. +""" + +from types import SimpleNamespace +from unittest.mock import patch + +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + +from ..controllers.website_sale import AplicoopWebsiteSale + +REQUEST_PATCH_TARGET = ( + "odoo.addons.website_sale_aplicoop.controllers.website_sale.request" +) + + +class PricingDelegationCommon: + """A company, a website and a tax-included product to price.""" + + def setUp(self): + super().setUp() + self.controller = AplicoopWebsiteSale() + self.company = self.env["res.company"].create({"name": "Pricing Deleg Co"}) + self.website = self.env.ref("website.default_website").sudo() + self.website.write( + { + "company_id": self.company.id, + "show_line_subtotals_tax_selection": "tax_included", + } + ) + self.tax_group = self.env["account.tax.group"].create( + {"name": "IVA Deleg", "company_id": self.company.id} + ) + self.country_es = self.env.ref("base.es") + + def _create_pricelist(self, name, percent=None): + vals = {"name": name, "company_id": self.company.id} + if percent is not None: + vals["item_ids"] = [ + ( + 0, + 0, + { + "compute_price": "percentage", + "percent_price": percent, + "applied_on": "3_global", + }, + ) + ] + return self.env["product.pricelist"].create(vals) + + def _create_tax(self, name, amount, included=False): + return self.env["account.tax"].create( + { + "name": name, + "amount": amount, + "amount_type": "percent", + "type_tax_use": "sale", + "price_include_override": ( + "tax_included" if included else "tax_excluded" + ), + "company_id": self.company.id, + "country_id": self.country_es.id, + "tax_group_id": self.tax_group.id, + } + ) + + def _create_product(self, name, list_price, tax=None): + return self.env["product.product"].create( + { + "name": name, + "type": "consu", + "list_price": list_price, + "is_published": True, + "sale_ok": True, + "company_id": self.company.id, + "taxes_id": [(6, 0, [tax.id])] if tax else False, + } + ) + + +@tagged("post_install", "-at_install") +class TestStandardPricelistResolution(PricingDelegationCommon, TransactionCase): + """Eskaera prices with the website pricelist, like the rest of the shop.""" + + def test_no_aplicoop_pricelist_setting_is_left(self): + """The old per-addon pricelist setting is gone for good. + + It used to let Eskaera quote a different pricelist from `/shop`, which + is exactly the drift this addon should not introduce. + """ + self.assertNotIn( + "aplicoop_pricelist_id", self.env["res.config.settings"]._fields + ) + + def test_website_resolution_is_left_untouched(self): + """`_get_current_pricelist` is core's; the addon does not override it.""" + website_cls = type(self.env["website"]) + + self.assertNotIn("_get_current_pricelist", website_cls.__dict__) + self.assertEqual( + self.website.pricelist_id, self.website._get_current_pricelist() + ) + + def test_the_controller_quotes_the_website_pricelist(self): + """The Eskaera helper hands back exactly what the website resolved.""" + expected = self._create_pricelist("Website PL", percent=15.0) + request_mock = SimpleNamespace( + env=self.env, + website=SimpleNamespace(pricelist_id=expected), + ) + + with patch(REQUEST_PATCH_TARGET, request_mock): + resolved = self.controller._resolve_pricelist() + + self.assertEqual(resolved, expected) + + +@tagged("post_install", "-at_install") +class TestTaxIncludedFiscalPosition(PricingDelegationCommon, TransactionCase): + """A remapped tax-included tax must rebase the displayed price.""" + + def setUp(self): + super().setUp() + self.pricelist = self._create_pricelist("Plain PL") + self.tax_21_incl = self._create_tax("IVA 21% incl", 21.0, included=True) + self.tax_10_incl = self._create_tax("IVA 10% incl", 10.0, included=True) + # 100 net + 21% already inside the price. + self.product = self._create_product("Tax Incl Product", 121.0, self.tax_21_incl) + + def _fiscal_position(self): + position = self.env["account.fiscal.position"].create( + {"name": "Remap 21->10", "company_id": self.company.id} + ) + self.env["account.fiscal.position.tax"].create( + { + "position_id": position.id, + "tax_src_id": self.tax_21_incl.id, + "tax_dest_id": self.tax_10_incl.id, + } + ) + return position + + def test_price_is_rebased_onto_the_mapped_tax(self): + """121 gross at 21% becomes 110 gross at 10%, not 121. + + Without `_get_tax_included_unit_price_from_price` the 121 was handed + straight to the mapped tax, which read it as already being the 10% + gross price and displayed 121. + """ + self.website.fiscal_position_id = self._fiscal_position() + + pricing = self.controller._get_pricing_info( + self.product, self.pricelist, quantity=1.0 + ) + + self.assertAlmostEqual(pricing["price"], 110.0, places=2) + + def test_price_is_untouched_without_a_fiscal_position(self): + """The rebasing is a no-op in the ordinary case.""" + self.website.fiscal_position_id = False + + pricing = self.controller._get_pricing_info( + self.product, self.pricelist, quantity=1.0 + ) + + self.assertAlmostEqual(pricing["price"], 121.0, places=2) + + +@tagged("post_install", "-at_install") +class TestPricingBatching(PricingDelegationCommon, TransactionCase): + """A product listing resolves the pricelist once for the whole page.""" + + def setUp(self): + super().setUp() + self.pricelist = self._create_pricelist("Batch PL", percent=10.0) + self.tax = self._create_tax("IVA 21%", 21.0) + self.products = self.env["product.product"] + for index in range(5): + self.products |= self._create_product( + f"Batch Product {index}", 100.0 + index, self.tax + ) + + def test_pricelist_is_resolved_once_for_the_page(self): + """One `_compute_price_rule` call, not one per product.""" + calls = [] + original = type(self.pricelist)._compute_price_rule + + def counting_compute_price_rule(pricelist_self, products, *args, **kwargs): + calls.append(len(products)) + return original(pricelist_self, products, *args, **kwargs) + + self.patch( + type(self.pricelist), "_compute_price_rule", counting_compute_price_rule + ) + + self.controller._compute_price_info(self.products, self.pricelist) + + self.assertEqual( + calls, + [len(self.products)], + msg=f"Expected a single batched call, got {calls}", + ) + + def test_batched_prices_match_the_single_product_helper(self): + """Batching must not change any number it produces.""" + batched = self.controller._compute_price_info(self.products, self.pricelist) + + for product in self.products: + single = self.controller._get_pricing_info( + product, self.pricelist, quantity=1.0 + ) + self.assertAlmostEqual( + batched[product.id]["price"], single["price"], places=2 + ) + self.assertAlmostEqual( + batched[product.id]["price_unit"], single["price_unit"], places=2 + ) + + def test_prices_the_given_variant(self): + """A variant is priced as itself, not as its template's first one.""" + attribute = self.env["product.attribute"].create( + { + "name": "Size Deleg", + "value_ids": [ + (0, 0, {"name": "Small"}), + (0, 0, {"name": "Large"}), + ], + } + ) + template = self.env["product.template"].create( + { + "name": "Multi Variant Product", + "type": "consu", + "list_price": 100.0, + "is_published": True, + "sale_ok": True, + "company_id": self.company.id, + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute.id, + "value_ids": [(6, 0, attribute.value_ids.ids)], + }, + ) + ], + } + ) + small, large = template.product_variant_ids[0], template.product_variant_ids[1] + # Make the two variants cost visibly different amounts. + large.write({"list_price": 100.0}) + self.env["product.pricelist.item"].create( + { + "pricelist_id": self.pricelist.id, + "applied_on": "0_product_variant", + "product_id": large.id, + "compute_price": "fixed", + "fixed_price": 500.0, + } + ) + + priced = self.controller._compute_price_info(large, self.pricelist) + + self.assertAlmostEqual(priced[large.id]["price_unit"], 500.0, places=2) + self.assertNotEqual(large.id, small.id) diff --git a/website_sale_aplicoop/tests/test_pricing_with_pricelist.py b/website_sale_aplicoop/tests/test_pricing_with_pricelist.py index 8eb4114..d34bb3f 100644 --- a/website_sale_aplicoop/tests/test_pricing_with_pricelist.py +++ b/website_sale_aplicoop/tests/test_pricing_with_pricelist.py @@ -500,8 +500,8 @@ class TestPricingWithPricelist(TransactionCase): # If it raises, that's also acceptable behavior self.assertTrue(True, "Negative quantity properly rejected") - def test_pricing_helper_uses_config_pricelist_and_taxes(self): - """Pricing helper must apply configured pricelist and include taxes for display.""" + def test_pricing_helper_applies_pricelist_and_taxes(self): + """Pricing helper must apply the given pricelist and include taxes.""" website = self.env.ref("website.default_website").sudo() website.write( @@ -529,17 +529,12 @@ class TestPricingWithPricelist(TransactionCase): } ) - self.env["ir.config_parameter"].sudo().set_param( - "website_sale_aplicoop.pricelist_id", pricelist_discount.id - ) - product = self.product_with_tax # 100€ + 21% pricing = self.controller._get_pricing_info( product, pricelist_discount, quantity=1.0, - partner=self.partner, ) # price_unit uses pricelist (10% discount) diff --git a/website_sale_aplicoop/views/res_config_settings_views.xml b/website_sale_aplicoop/views/res_config_settings_views.xml index 5fa5b66..d49b71c 100644 --- a/website_sale_aplicoop/views/res_config_settings_views.xml +++ b/website_sale_aplicoop/views/res_config_settings_views.xml @@ -6,23 +6,6 @@ - Aplicoop Settings - - - - - - - Pricelist used for Aplicoop group orders - - - - - - - - - Shop Performance