diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index 5e55897a949..2f56f5a8f94 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -469,7 +469,8 @@ class SaleOrderLine(models.Model): if not line.product_uom or not line.product_id: line.price_unit = 0.0 else: - price = line.with_company(line.company_id)._get_display_price() + line = line.with_company(line.company_id) + price = line._get_display_price() line.price_unit = line.product_id._get_tax_included_unit_price( line.company_id or line.env.company, line.order_id.currency_id, diff --git a/addons/website_sale/models/product_product.py b/addons/website_sale/models/product_product.py index 7eec169b934..a85e7acfda6 100644 --- a/addons/website_sale/models/product_product.py +++ b/addons/website_sale/models/product_product.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import api, fields, models, _ +from odoo import _, api, fields, models from odoo.exceptions import ValidationError @@ -109,9 +108,23 @@ class Product(models.Model): def _get_contextual_price_tax_selection(self): self.ensure_one() price = self._get_contextual_price() - website = self.env['website'].get_current_website() - line_tax_type = website.show_line_subtotals_tax_selection - company_taxes = self.taxes_id.filtered(lambda tax: tax.company_id in self.env.company.parent_ids) - if line_tax_type == "tax_included" and company_taxes: - price = company_taxes.compute_all(price, product=self, partner=self.env['res.partner'])['total_included'] + product_taxes = self.sudo().taxes_id.filtered(lambda x: x.company_id in self.env.company.parent_ids) + if product_taxes: + website = self.env['website'].get_current_website() + fiscal_position = website.sudo().fiscal_position_id + + price = self._get_tax_included_unit_price( + website.company_id, + website.currency_id, + fields.Date.context_today(self), + 'sale', + fiscal_position=fiscal_position, + product_price_unit=price, + product_currency=website.currency_id, + ) + line_tax_type = website.show_line_subtotals_tax_selection + tax_display = "total_included" if line_tax_type == "tax_included" else "total_excluded" + + taxes = fiscal_position.map_tax(product_taxes) + price = taxes.compute_all(price, product=self, partner=self.env['res.partner'])[tax_display] return price diff --git a/addons/website_sale/tests/test_website_sale_product.py b/addons/website_sale/tests/test_website_sale_product.py index bb4fdc8f29f..4bdc3891d6b 100644 --- a/addons/website_sale/tests/test_website_sale_product.py +++ b/addons/website_sale/tests/test_website_sale_product.py @@ -1,7 +1,16 @@ -# coding: utf-8 +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import itertools + +from odoo.fields import Command from odoo.tests import tagged + +from odoo.addons.sale.tests.test_sale_product_attribute_value_config import ( + TestSaleProductAttributeValueCommon, +) from odoo.addons.website.tools import MockRequest -from odoo.addons.sale.tests.test_sale_product_attribute_value_config import TestSaleProductAttributeValueCommon +from odoo.addons.website_sale.controllers.main import WebsiteSale + @tagged('post_install', '-at_install') class WebsiteSaleProductTests(TestSaleProductAttributeValueCommon): @@ -12,6 +21,39 @@ class WebsiteSaleProductTests(TestSaleProductAttributeValueCommon): cls.website = cls.env.ref('website.default_website') cls.website.company_id = cls.env.company + cls.tax_5 = cls.env['account.tax'].create({ + 'name': '5% Tax', + 'amount_type': 'percent', + 'amount': 5, + 'price_include': False, + 'include_base_amount': False, + 'type_tax_use': 'sale', + }) + cls.tax_10 = cls.env['account.tax'].create({ + 'name': '10% Tax', + 'amount_type': 'percent', + 'amount': 10, + 'price_include': False, + 'include_base_amount': False, + 'type_tax_use': 'sale', + }) + cls.tax_15 = cls.env['account.tax'].create({ + 'name': '15% Tax', + 'amount_type': 'percent', + 'amount': 15, + 'price_include': False, + 'include_base_amount': False, + 'type_tax_use': 'sale', + }) + cls.fiscal_country = cls.env['res.country'].create({ + 'name': "Super Fiscal Position", + 'code': 'SFP', + }) + cls.product = cls.env['product.product'].create({ + 'name': 'Super Product', + 'list_price': 100.0, + }) + def test_website_sale_contextual_price(self): contextual_price = self.computer._get_contextual_price() self.assertEqual( @@ -78,3 +120,107 @@ class WebsiteSaleProductTests(TestSaleProductAttributeValueCommon): pricelist.discount_policy = 'without_discount' res = product_tmpl._get_sales_prices(pricelist, self.env['account.fiscal.position']) self.assertEqual(res[product_tmpl.id]['base_price'], 75) + + def test_get_contextual_price_tax_selection(self): + """ + `_get_contextual_price_tax_selection` is used to display the price on the website (e.g. in the carousel). + We test that the contextual price is correctly computed. That is, it is coherent with the price displayed on the product when in the cart. + """ + param_main_product_tax_included = [True, False] + param_show_line_subtotals_tax_selection = ['tax_included', 'tax_excluded'] + param_extra_tax = [False, 'included', 'excluded'] + param_fpos = [False, 'to_tax_excluded', 'to_tax_included'] + parameters = itertools.product(param_main_product_tax_included, param_show_line_subtotals_tax_selection, param_extra_tax, param_fpos) + + self.product.taxes_id = self.tax_15 + fiscal_position = self.env['account.fiscal.position'].create({ + 'name': 'Super Fiscal Position', + 'auto_apply': True, + 'country_id': self.fiscal_country.id, + 'tax_ids': [ + Command.create({ + 'tax_src_id': self.tax_15.id, + 'tax_dest_id': self.tax_10.id, + }) + ] + }) + self.env.user.partner_id.country_id = self.fiscal_country + + self.WebsiteSaleController = WebsiteSale() + for main_product_tax_included, show_line_subtotals_tax_selection, extra_tax, fpos in parameters: + with self.subTest(main_product_tax_included=main_product_tax_included, show_line_subtotals_tax_selection=show_line_subtotals_tax_selection, extra_tax=extra_tax, fpos=fpos): + # set "show_line_subtotals_tax_selection" parameter + self.website.invalidate_recordset(['fiscal_position_id'], flush=False) + config = self.env['res.config.settings'].create({}) + config.show_line_subtotals_tax_selection = show_line_subtotals_tax_selection + config.execute() + + self.assertEqual(self.website.show_line_subtotals_tax_selection, show_line_subtotals_tax_selection) + + tax_ids = [self.tax_15.id] + # set "main_product_tax_included" parameter + if main_product_tax_included: + self.tax_15.price_include = True + self.tax_15.include_base_amount = True + else: + self.tax_15.price_include = False + self.tax_15.include_base_amount = False + + # set "extra_tax" parameter + if extra_tax: + if extra_tax == 'included': + self.tax_5.price_include = True + self.tax_5.include_base_amount = True + else: + self.tax_5.price_include = False + self.tax_5.include_base_amount = False + + tax_ids.append(self.tax_5.id) + + self.product.taxes_id = tax_ids + + # set "fpos" parameter + if fpos: + if fpos == 'to_tax_included': + self.tax_10.price_include = True + self.tax_10.include_base_amount = True + else: + self.tax_10.price_include = False + self.tax_10.include_base_amount = False + + fiscal_position.action_unarchive() + else: + fiscal_position.action_archive() + + with MockRequest(self.env, website=self.website): + self.assertEqual( + self.website.fiscal_position_id, + fpos and fiscal_position or self.env['account.fiscal.position'] + ) + contextual_price = self.product.with_context( + website_id=self.website.id, + )._get_contextual_price_tax_selection() + self.WebsiteSaleController.cart_update(product_id=self.product.id, add_qty=1) + sale_order = self.website.sale_get_order() + + self.assertEqual(sale_order.website_id, self.website) + self.assertEqual(sale_order.company_id, self.website.company_id) + self.assertEqual(sale_order.currency_id, self.website.currency_id) + self.assertFalse(sale_order.pricelist_id) + if fpos: + self.assertEqual(sale_order.fiscal_position_id, fiscal_position) + else: + self.assertFalse(sale_order.fiscal_position_id) + + sol = sale_order.order_line + if fpos: + self.assertEqual(sol.tax_id, fiscal_position.map_tax(self.product.taxes_id)) + else: + self.assertEqual(sol.tax_id, self.product.taxes_id) + + if show_line_subtotals_tax_selection == 'tax_included': + self.assertAlmostEqual(sol.price_reduce_taxinc, contextual_price) + self.assertAlmostEqual(sale_order.amount_total, contextual_price) + else: + self.assertAlmostEqual(sol.price_reduce_taxexcl, contextual_price) + self.assertAlmostEqual(sale_order.amount_untaxed, contextual_price)