[ADD] website_sale_aplicoop: restore the test coverage lost with the dead code

The dead-code cleanup dropped 11 test files that were never wired into
tests/__init__.py. Reviewing what they covered turned up real holes, so the
worthwhile ones come back, rewritten against the current schema.

Blacklists were the serious gap: product, supplier and category exclusions
have absolute priority over product discovery and nothing exercised them.
The old file had two separate defects. Its supplier fixtures wrote
`main_seller_id` directly, but product_main_seller computes that field from
`variant_seller_ids`, so the compute reset it to False and the blacklist had
nothing to exclude; they now create real supplierinfo records. Worse, four
whole classes asserted against `group_order.product_ids` -- the m2m *input* --
instead of the discovery result, so they set `category_ids` and then checked a
field they never touched. Those go through `_get_products_for_group_order`
now, and three tests that had no assertions at all got some.

The remaining three failures were test bugs too, all Odoo 17->18 leftovers:

* Date cases assumed `pickup_date` derives from `start_date`. The chain is
  cutoff -> pickup -> delivery, and a recurring order whose start date has
  passed rolls forward to the current cycle, so a 2024 order has no 2024
  pickup. The new file anchors on future dates and finds the next 29 February
  dynamically, with a class documenting the roll-forward itself.
* `/eskaera/labels` is `type="json"`; the old test hit it with a plain GET and
  read the resulting 400 as a bug. It is called over JSON-RPC now, and a test
  pins the 400 so nobody repeats it. Also `uom.uom.categ` -> `uom.category`.
* `price_include` is computed in 18.0, so fixtures must set
  `price_include_override`. On top of that `_get_price` filters taxes by
  company and defaults to `env.company`, not the fixture's, which left the
  tax list empty -- `tax_included` was False for the wrong reason.

Two of the portal tests were passing for the wrong reason as well: the access
guard bounced them to /eskaera, which also answers 200. Membership has to be
set from the member side with `is_group`, and a new test checks the final URL
rather than the status alone. Each fixture that can silently build the wrong
thing now carries a guard test.

Left out on purpose: three files were unimplemented placeholders, and
test_draft_persistence still deserves recovering (see the notes file).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
GitHub Copilot 2026-08-17 17:24:31 +02:00
parent 3eb79e2431
commit 3e4bd5e5db
6 changed files with 1817 additions and 0 deletions

View file

@ -0,0 +1,310 @@
# Copyright 2025 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
"""Price calculations around included/excluded taxes.
Checks how `account.tax.compute_all` and the OCA helper
`product.product._get_price` (from `product_get_price_helper`) behave for the
shop, including the tax breakdown and fiscal-position mapping.
Odoo 18 note: `account.tax.price_include` is a *computed* boolean derived from
`price_include_override` (and the company default). Tax fixtures must set
`price_include_override` writing `price_include` directly is discarded.
"""
from odoo.tests import tagged
from odoo.tests.common import TransactionCase
@tagged("post_install", "-at_install")
class TestPriceWithTaxesIncluded(TransactionCase):
"""Test that prices displayed include taxes."""
def setUp(self):
super().setUp()
self.company = self.env["res.company"].create(
{
"name": "Test Company Tax Included",
}
)
tax_group = self.env["account.tax.group"].search(
[("company_id", "=", self.company.id)], limit=1
)
if not tax_group:
tax_group = self.env["account.tax.group"].create(
{
"name": "IVA",
"company_id": self.company.id,
}
)
country_es = self.env.ref("base.es")
self.tax_21 = self._create_tax("IVA 21%", 21.0, tax_group, country_es)
self.tax_10 = self._create_tax("IVA 10%", 10.0, tax_group, country_es)
self.tax_21_included = self._create_tax(
"IVA 21% Incluido", 21.0, tax_group, country_es, included=True
)
self.category = self.env["product.category"].create(
{
"name": "Test Category Tax Included",
}
)
self.product_21 = self._create_product("Product With 21% Tax", self.tax_21)
self.product_10 = self._create_product("Product With 10% Tax", self.tax_10)
self.product_no_tax = self._create_product("Product Without Tax", None)
self.product_tax_included = self._create_product(
# 100 + 21% = 121, already carrying the tax.
"Product With Tax Included",
self.tax_21_included,
list_price=121.0,
)
self.pricelist = self.env["product.pricelist"].create(
{
"name": "Test Pricelist",
"company_id": self.company.id,
}
)
def _create_tax(self, name, amount, tax_group, country, included=False):
"""Create a sale tax, choosing included/excluded via the override."""
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": country.id,
"tax_group_id": tax_group.id,
}
)
def _create_product(self, name, tax, list_price=100.0):
return self.env["product.product"].create(
{
"name": name,
"list_price": list_price,
"categ_id": self.category.id,
"taxes_id": [(6, 0, [tax.id])] if tax else False,
"company_id": self.company.id,
}
)
def _company_taxes(self, product):
return product.taxes_id.filtered(lambda t: t.company_id == self.company)
def _get_price(self, product, fposition=False):
"""Call the OCA helper for the fixture company.
`_get_price` defaults `company` to `self.env.company` and filters the
product taxes by it, so omitting it here would silently drop every tax
of the test company and make `tax_included` always False.
"""
return product._get_price(
qty=1.0,
pricelist=self.pricelist,
fposition=fposition,
company=self.company,
)
def _compute_all(self, product, base_price=100.0, taxes=None):
taxes = self._company_taxes(product) if taxes is None else taxes
return taxes.compute_all(
base_price,
currency=self.env.company.currency_id,
quantity=1.0,
product=product,
)
def test_price_include_is_driven_by_the_override(self):
"""Guard: the fixtures really are included/excluded as intended.
`price_include` is computed in Odoo 18, so a fixture that sets the
wrong field silently produces excluded taxes everywhere.
"""
self.assertFalse(self.tax_21.price_include)
self.assertFalse(self.tax_10.price_include)
self.assertTrue(self.tax_21_included.price_include)
def test_price_with_21_percent_tax(self):
"""Test that 21% tax is correctly added to base price."""
result = self._compute_all(self.product_21)
self.assertAlmostEqual(
result["total_included"], 121.0, places=2, msg="100 + 21% should be 121.0"
)
def test_price_with_10_percent_tax(self):
"""Test that 10% tax is correctly added to base price."""
result = self._compute_all(self.product_10)
self.assertAlmostEqual(
result["total_included"], 110.0, places=2, msg="100 + 10% should be 110.0"
)
def test_price_without_tax(self):
"""A product with no taxes keeps its base price."""
self.assertFalse(
self._company_taxes(self.product_no_tax), "Product should have no taxes"
)
price_info = self._get_price(self.product_no_tax)
self.assertAlmostEqual(price_info["value"], 100.0, places=2)
self.assertFalse(price_info["tax_included"])
def test_oca_get_price_returns_base_without_tax(self):
"""OCA `_get_price` returns the base price for tax-excluded products."""
price_info = self._get_price(self.product_21)
self.assertAlmostEqual(
price_info["value"],
100.0,
places=2,
msg="OCA _get_price should return base price without tax",
)
self.assertFalse(
price_info["tax_included"],
msg="tax_included should be False for a tax-excluded tax",
)
def test_oca_get_price_with_included_tax(self):
"""OCA `_get_price` flags tax_included for a tax-included product."""
price_info = self._get_price(self.product_tax_included)
self.assertAlmostEqual(
price_info["value"],
121.0,
places=2,
msg="Price with included tax should stay at 121.0",
)
self.assertTrue(
price_info["tax_included"],
msg="tax_included should be True for a tax-included tax",
)
def test_compute_all_with_multiple_taxes(self):
"""Test tax calculation with multiple taxes."""
product_multi = self.env["product.product"].create(
{
"name": "Product With Multiple Taxes",
"list_price": 100.0,
"categ_id": self.category.id,
"taxes_id": [(6, 0, [self.tax_21.id, self.tax_10.id])],
"company_id": self.company.id,
}
)
result = self._compute_all(product_multi)
# 100 + 21 + 10 = 131.0
self.assertAlmostEqual(
result["total_included"], 131.0, places=2, msg="100 + 21% + 10% = 131.0"
)
def test_compute_all_with_fiscal_position(self):
"""A fiscal position remapping 21% to 10% changes the total."""
fiscal_position = self.env["account.fiscal.position"].create(
{
"name": "Test Fiscal Position",
"company_id": self.company.id,
}
)
self.env["account.fiscal.position.tax"].create(
{
"position_id": fiscal_position.id,
"tax_src_id": self.tax_21.id,
"tax_dest_id": self.tax_10.id,
}
)
mapped_taxes = fiscal_position.map_tax(self._company_taxes(self.product_21))
self.assertEqual(mapped_taxes, self.tax_10)
result = self._compute_all(self.product_21, taxes=mapped_taxes)
self.assertAlmostEqual(
result["total_included"],
110.0,
places=2,
msg="Fiscal position should map to the 10% tax",
)
def test_get_price_applies_fiscal_position(self):
"""`_get_price` honours the fiscal position it is handed."""
fiscal_position = self.env["account.fiscal.position"].create(
{
"name": "Map To Included Tax",
"company_id": self.company.id,
}
)
self.env["account.fiscal.position.tax"].create(
{
"position_id": fiscal_position.id,
"tax_src_id": self.tax_21.id,
"tax_dest_id": self.tax_21_included.id,
}
)
price_info = self._get_price(self.product_21, fposition=fiscal_position)
# The mapped tax is tax-included, so the helper must say so.
self.assertTrue(price_info["tax_included"])
def test_tax_amount_details(self):
"""Test that compute_all provides a detailed tax breakdown."""
result = self._compute_all(self.product_21)
self.assertIn("total_included", result)
self.assertIn("total_excluded", result)
self.assertIn("taxes", result)
self.assertAlmostEqual(result["total_excluded"], 100.0, places=2)
self.assertAlmostEqual(result["total_included"], 121.0, places=2)
self.assertEqual(len(result["taxes"]), 1)
self.assertAlmostEqual(result["taxes"][0]["amount"], 21.0, places=2)
def test_included_tax_breakdown_keeps_the_gross_price(self):
"""For an included tax, the gross price is the one the member sees."""
result = self._compute_all(self.product_tax_included, base_price=121.0)
self.assertAlmostEqual(result["total_included"], 121.0, places=2)
self.assertAlmostEqual(result["total_excluded"], 100.0, places=2)
self.assertAlmostEqual(result["taxes"][0]["amount"], 21.0, places=2)
def test_zero_price_with_tax(self):
"""Test tax calculation on a free product."""
free_product = self._create_product(
"Free Product With Tax", self.tax_21, list_price=0.0
)
result = self._compute_all(free_product, base_price=0.0)
self.assertAlmostEqual(
result["total_included"],
0.0,
places=2,
msg="Free product with tax should still be free",
)
def test_high_precision_price_with_tax(self):
"""Test tax calculation with high precision prices."""
precise_product = self._create_product(
"Precise Price Product", self.tax_21, list_price=99.99
)
result = self._compute_all(precise_product, base_price=99.99)
# 99.99 + 21% = 120.9879 -> 120.99
self.assertAlmostEqual(result["total_included"], 99.99 * 1.21, places=2)