[REF] website_sale_aplicoop: price through website_sale instead of privately

Eskaera carried its own pricing path: a `website_sale_aplicoop.pricelist_id`
setting resolved inside the Eskaera controllers, plus a local reimplementation
of the tax and discount maths. The pricelist part meant /shop and Eskaera could
quote different prices on the same website, and the maths part had drifted from
the original it was copied from.

Pricing is now the standard one. The setting is gone (a migration drops the
parameter, which would otherwise linger looking like live configuration) and
`_resolve_pricelist` is `request.website.pricelist_id`. Scoping a pricelist to
a website is already core's job through `product.pricelist.website_id`, so
running the plain shop on one website and the co-op on another needs no code
here.

Taxes are applied by `product.template._apply_taxes_to_price`, which fixes a
real defect: it calls `_get_tax_included_unit_price_from_price` first, and this
module did not. With a fiscal position remapping a tax-included tax, a product
at 121 (100 + 21%) was displayed at 121 instead of 110, because the price was
handed to the mapped tax as if it were already that tax's gross amount. The
helper is a no-op without a remapping, so ordinary pricing is untouched. It
also means the website's `show_line_subtotals_tax_selection` is respected
rather than overridden with a hardcoded tax-included display.

Listing a page now costs one `_compute_price_rule` call instead of one per
product, which matters on the lazy-loading path.

Two smaller things found on the way. `_get_product_price_rule` was being passed
`target_currency=`, which is not one of its arguments: it fell into **kwargs
and was ignored, so the conversion never happened. And `_compute_price_info`
resolved `product.product_variant_ids[0]`, but it receives variants, so a
multi-variant template was priced from its first variant rather than the one
asked for.

The delivery display price loses its hardcoded 5.74 fallback and reuses the
same helpers as everything else.

Kept local, because none of it is pricing: the /Kg and /L suffixes, the 0.1
quantity step for bulk goods, the base unit price and the supplier name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-08-17 17:28:51 +02:00
parent 3e4bd5e5db
commit 817ff31d39
10 changed files with 496 additions and 202 deletions

View file

@ -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",

View file

@ -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(
{

View file

@ -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.",

View file

@ -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()

View file

@ -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",

View file

@ -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

View file

@ -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,

View file

@ -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)

View file

@ -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)

View file

@ -6,23 +6,6 @@
<field name="inherit_id" ref="website.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//block[@id='website_info_settings']" position="after">
<h2>Aplicoop Settings</h2>
<div class="row mt16 o_settings_container" id="aplicoop_settings">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<label for="aplicoop_pricelist_id" string="Aplicoop Pricelist"/>
<div class="text-muted">
Pricelist used for Aplicoop group orders
</div>
<div class="content-group">
<div class="mt16">
<field name="aplicoop_pricelist_id" class="oe_inline"/>
</div>
</div>
</div>
</div>
</div>
<h2>Shop Performance</h2>
<div class="row mt16 o_settings_container" id="eskaera_shop_settings">
<div class="col-12 col-lg-6 o_setting_box">