El método _get_price() del addon OCA ya maneja correctamente los impuestos según la configuración de Odoo. El cálculo adicional con compute_all() estaba duplicando los impuestos cuando price_include estaba activado. Cambios: - Eliminado método _compute_price_with_taxes() - Revertido eskaera_shop() para usar directamente _get_price() - Revertido add_to_eskaera_cart() para usar directamente _get_price() El precio mostrado ahora respeta la configuración de impuestos de Odoo sin duplicación.
425 lines
14 KiB
Python
425 lines
14 KiB
Python
# Copyright 2025 Criptomart
|
|
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
"""
|
|
Test suite for price calculations WITH taxes included.
|
|
|
|
This test verifies that the _compute_price_with_taxes method correctly
|
|
calculates prices including taxes for display in the online shop.
|
|
"""
|
|
|
|
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()
|
|
|
|
# Create test company
|
|
self.company = self.env["res.company"].create(
|
|
{
|
|
"name": "Test Company Tax Included",
|
|
}
|
|
)
|
|
|
|
# Get or create default tax group
|
|
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,
|
|
}
|
|
)
|
|
|
|
# Get default country (Spain)
|
|
country_es = self.env.ref("base.es")
|
|
|
|
# Create tax (21% IVA) - price_include=False (default)
|
|
self.tax_21 = self.env["account.tax"].create(
|
|
{
|
|
"name": "IVA 21%",
|
|
"amount": 21.0,
|
|
"amount_type": "percent",
|
|
"type_tax_use": "sale",
|
|
"price_include": False, # Explicit: tax NOT included in price
|
|
"company_id": self.company.id,
|
|
"country_id": country_es.id,
|
|
"tax_group_id": tax_group.id,
|
|
}
|
|
)
|
|
|
|
# Create tax (10% IVA reducido)
|
|
self.tax_10 = self.env["account.tax"].create(
|
|
{
|
|
"name": "IVA 10%",
|
|
"amount": 10.0,
|
|
"amount_type": "percent",
|
|
"type_tax_use": "sale",
|
|
"price_include": False,
|
|
"company_id": self.company.id,
|
|
"country_id": country_es.id,
|
|
"tax_group_id": tax_group.id,
|
|
}
|
|
)
|
|
|
|
# Create tax with price_include=True for comparison
|
|
self.tax_21_included = self.env["account.tax"].create(
|
|
{
|
|
"name": "IVA 21% Incluido",
|
|
"amount": 21.0,
|
|
"amount_type": "percent",
|
|
"type_tax_use": "sale",
|
|
"price_include": True, # Tax IS included in price
|
|
"company_id": self.company.id,
|
|
"country_id": country_es.id,
|
|
"tax_group_id": tax_group.id,
|
|
}
|
|
)
|
|
|
|
# Create product category
|
|
self.category = self.env["product.category"].create(
|
|
{
|
|
"name": "Test Category Tax Included",
|
|
}
|
|
)
|
|
|
|
# Create test products with different tax configurations
|
|
self.product_21 = self.env["product.product"].create(
|
|
{
|
|
"name": "Product With 21% Tax",
|
|
"list_price": 100.0,
|
|
"categ_id": self.category.id,
|
|
"taxes_id": [(6, 0, [self.tax_21.id])],
|
|
"company_id": self.company.id,
|
|
}
|
|
)
|
|
|
|
self.product_10 = self.env["product.product"].create(
|
|
{
|
|
"name": "Product With 10% Tax",
|
|
"list_price": 100.0,
|
|
"categ_id": self.category.id,
|
|
"taxes_id": [(6, 0, [self.tax_10.id])],
|
|
"company_id": self.company.id,
|
|
}
|
|
)
|
|
|
|
self.product_no_tax = self.env["product.product"].create(
|
|
{
|
|
"name": "Product Without Tax",
|
|
"list_price": 100.0,
|
|
"categ_id": self.category.id,
|
|
"taxes_id": False,
|
|
"company_id": self.company.id,
|
|
}
|
|
)
|
|
|
|
self.product_tax_included = self.env["product.product"].create(
|
|
{
|
|
"name": "Product With Tax Included",
|
|
"list_price": 121.0, # 100 + 21% = 121
|
|
"categ_id": self.category.id,
|
|
"taxes_id": [(6, 0, [self.tax_21_included.id])],
|
|
"company_id": self.company.id,
|
|
}
|
|
)
|
|
|
|
# Create pricelist
|
|
self.pricelist = self.env["product.pricelist"].create(
|
|
{
|
|
"name": "Test Pricelist",
|
|
"company_id": self.company.id,
|
|
}
|
|
)
|
|
|
|
def test_price_with_21_percent_tax(self):
|
|
"""Test that 21% tax is correctly added to base price."""
|
|
# Base price: 100.0
|
|
# Expected with 21% tax: 121.0
|
|
|
|
taxes = self.product_21.taxes_id.filtered(
|
|
lambda t: t.company_id == self.company
|
|
)
|
|
|
|
base_price = 100.0
|
|
tax_result = taxes.compute_all(
|
|
base_price,
|
|
currency=self.env.company.currency_id,
|
|
quantity=1.0,
|
|
product=self.product_21,
|
|
)
|
|
|
|
price_with_tax = tax_result["total_included"]
|
|
|
|
self.assertAlmostEqual(
|
|
price_with_tax, 121.0, places=2, msg="100 + 21% should equal 121.0"
|
|
)
|
|
|
|
def test_price_with_10_percent_tax(self):
|
|
"""Test that 10% tax is correctly added to base price."""
|
|
# Base price: 100.0
|
|
# Expected with 10% tax: 110.0
|
|
|
|
taxes = self.product_10.taxes_id.filtered(
|
|
lambda t: t.company_id == self.company
|
|
)
|
|
|
|
base_price = 100.0
|
|
tax_result = taxes.compute_all(
|
|
base_price,
|
|
currency=self.env.company.currency_id,
|
|
quantity=1.0,
|
|
product=self.product_10,
|
|
)
|
|
|
|
price_with_tax = tax_result["total_included"]
|
|
|
|
self.assertAlmostEqual(
|
|
price_with_tax, 110.0, places=2, msg="100 + 10% should equal 110.0"
|
|
)
|
|
|
|
def test_price_without_tax(self):
|
|
"""Test that product without tax returns base price unchanged."""
|
|
# Base price: 100.0
|
|
# Expected with no tax: 100.0
|
|
|
|
taxes = self.product_no_tax.taxes_id.filtered(
|
|
lambda t: t.company_id == self.company
|
|
)
|
|
|
|
# No taxes, so tax_result would be empty
|
|
self.assertFalse(taxes, "Product should have no taxes")
|
|
|
|
# Without taxes, price should remain base price
|
|
base_price = 100.0
|
|
expected_price = 100.0
|
|
|
|
self.assertEqual(
|
|
base_price,
|
|
expected_price,
|
|
msg="Product without tax should have unchanged price",
|
|
)
|
|
|
|
def test_oca_get_price_returns_base_without_tax(self):
|
|
"""Test that OCA _get_price returns base price WITHOUT taxes by default."""
|
|
# This verifies our understanding of OCA behavior
|
|
|
|
price_info = self.product_21._get_price(
|
|
qty=1.0,
|
|
pricelist=self.pricelist,
|
|
fposition=False,
|
|
)
|
|
|
|
# OCA should return base price (100.0) WITHOUT tax
|
|
self.assertAlmostEqual(
|
|
price_info["value"],
|
|
100.0,
|
|
places=2,
|
|
msg="OCA _get_price should return base price without tax",
|
|
)
|
|
|
|
# tax_included should be False for price_include=False taxes
|
|
self.assertFalse(
|
|
price_info.get("tax_included", False),
|
|
msg="tax_included should be False when price_include=False",
|
|
)
|
|
|
|
def test_oca_get_price_with_included_tax(self):
|
|
"""Test OCA behavior with price_include=True tax."""
|
|
|
|
price_info = self.product_tax_included._get_price(
|
|
qty=1.0,
|
|
pricelist=self.pricelist,
|
|
fposition=False,
|
|
)
|
|
|
|
# With price_include=True, the price should already include tax
|
|
# list_price is 121.0 (100 + 21%)
|
|
self.assertAlmostEqual(
|
|
price_info["value"],
|
|
121.0,
|
|
places=2,
|
|
msg="Price with included tax should be 121.0",
|
|
)
|
|
|
|
# tax_included should be True
|
|
self.assertTrue(
|
|
price_info.get("tax_included", False),
|
|
msg="tax_included should be True when price_include=True",
|
|
)
|
|
|
|
def test_compute_all_with_multiple_taxes(self):
|
|
"""Test tax calculation with multiple taxes."""
|
|
# Create product with both 21% and 10% 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,
|
|
}
|
|
)
|
|
|
|
taxes = product_multi.taxes_id.filtered(lambda t: t.company_id == self.company)
|
|
|
|
base_price = 100.0
|
|
tax_result = taxes.compute_all(
|
|
base_price,
|
|
currency=self.env.company.currency_id,
|
|
quantity=1.0,
|
|
product=product_multi,
|
|
)
|
|
|
|
price_with_taxes = tax_result["total_included"]
|
|
|
|
# 100 + 21% + 10% = 100 + 21 + 10 = 131.0
|
|
self.assertAlmostEqual(
|
|
price_with_taxes, 131.0, places=2, msg="100 + 21% + 10% should equal 131.0"
|
|
)
|
|
|
|
def test_compute_all_with_fiscal_position(self):
|
|
"""Test tax calculation with fiscal position mapping."""
|
|
# Create fiscal position that maps 21% to 10%
|
|
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,
|
|
}
|
|
)
|
|
|
|
# Get taxes and apply fiscal position
|
|
taxes = self.product_21.taxes_id.filtered(
|
|
lambda t: t.company_id == self.company
|
|
)
|
|
mapped_taxes = fiscal_position.map_tax(taxes)
|
|
|
|
# Should be mapped to 10% tax
|
|
self.assertEqual(len(mapped_taxes), 1)
|
|
self.assertEqual(mapped_taxes[0].id, self.tax_10.id)
|
|
|
|
base_price = 100.0
|
|
tax_result = mapped_taxes.compute_all(
|
|
base_price,
|
|
currency=self.env.company.currency_id,
|
|
quantity=1.0,
|
|
product=self.product_21,
|
|
)
|
|
|
|
price_with_tax = tax_result["total_included"]
|
|
|
|
# Should be 110.0 (10% instead of 21%)
|
|
self.assertAlmostEqual(
|
|
price_with_tax, 110.0, places=2, msg="Fiscal position should map to 10% tax"
|
|
)
|
|
|
|
def test_tax_amount_details(self):
|
|
"""Test that compute_all provides detailed tax breakdown."""
|
|
taxes = self.product_21.taxes_id.filtered(
|
|
lambda t: t.company_id == self.company
|
|
)
|
|
|
|
base_price = 100.0
|
|
tax_result = taxes.compute_all(
|
|
base_price,
|
|
currency=self.env.company.currency_id,
|
|
quantity=1.0,
|
|
product=self.product_21,
|
|
)
|
|
|
|
# Verify structure of tax_result
|
|
self.assertIn("total_included", tax_result)
|
|
self.assertIn("total_excluded", tax_result)
|
|
self.assertIn("taxes", tax_result)
|
|
|
|
# total_excluded should be base price
|
|
self.assertAlmostEqual(tax_result["total_excluded"], 100.0, places=2)
|
|
|
|
# total_included should be base + tax
|
|
self.assertAlmostEqual(tax_result["total_included"], 121.0, places=2)
|
|
|
|
# taxes should contain tax details
|
|
self.assertEqual(len(tax_result["taxes"]), 1)
|
|
tax_detail = tax_result["taxes"][0]
|
|
self.assertAlmostEqual(tax_detail["amount"], 21.0, places=2)
|
|
|
|
def test_zero_price_with_tax(self):
|
|
"""Test tax calculation on free product."""
|
|
free_product = self.env["product.product"].create(
|
|
{
|
|
"name": "Free Product With Tax",
|
|
"list_price": 0.0,
|
|
"categ_id": self.category.id,
|
|
"taxes_id": [(6, 0, [self.tax_21.id])],
|
|
"company_id": self.company.id,
|
|
}
|
|
)
|
|
|
|
taxes = free_product.taxes_id.filtered(lambda t: t.company_id == self.company)
|
|
|
|
base_price = 0.0
|
|
tax_result = taxes.compute_all(
|
|
base_price,
|
|
currency=self.env.company.currency_id,
|
|
quantity=1.0,
|
|
product=free_product,
|
|
)
|
|
|
|
price_with_tax = tax_result["total_included"]
|
|
|
|
# 0 + 21% = 0
|
|
self.assertAlmostEqual(
|
|
price_with_tax,
|
|
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.env["product.product"].create(
|
|
{
|
|
"name": "Precise Price Product",
|
|
"list_price": 99.99,
|
|
"categ_id": self.category.id,
|
|
"taxes_id": [(6, 0, [self.tax_21.id])],
|
|
"company_id": self.company.id,
|
|
}
|
|
)
|
|
|
|
taxes = precise_product.taxes_id.filtered(
|
|
lambda t: t.company_id == self.company
|
|
)
|
|
|
|
base_price = 99.99
|
|
tax_result = taxes.compute_all(
|
|
base_price,
|
|
currency=self.env.company.currency_id,
|
|
quantity=1.0,
|
|
product=precise_product,
|
|
)
|
|
|
|
price_with_tax = tax_result["total_included"]
|
|
|
|
# 99.99 + 21% = 120.9879 ≈ 120.99
|
|
expected = 99.99 * 1.21
|
|
self.assertAlmostEqual(
|
|
price_with_tax,
|
|
expected,
|
|
places=2,
|
|
msg=f"Expected {expected}, got {price_with_tax}",
|
|
)
|