[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:
parent
3eb79e2431
commit
3e4bd5e5db
6 changed files with 1817 additions and 0 deletions
310
website_sale_aplicoop/tests/test_price_with_taxes_included.py
Normal file
310
website_sale_aplicoop/tests/test_price_with_taxes_included.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue