# 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}", )