Skip to content
Snippets Groups Projects
Commit 83c927f3 authored by Nasreddin Boulif (bon)'s avatar Nasreddin Boulif (bon)
Browse files

[FIX] sale,website_sale: Display extra prices with taxes included


Steps to reproduce the issue:

  - Install eCommerce and Inventory module
  - Go to Settings
  - Ensure `Product Prices` is set to 'Tax included'
  - Ensure `Variant Grid Entry` is activated
  - Create a new Product as storable
  - Set `Sales Price` to $1.0
  - Set `Customer Taxes` to 10.00 %
  - Under `Variants` tab, set 'Sales Variant Selection' to 'Order Grid Entry'
  - Under `Variants` tab add an Attribute with 2 values
  - Set "Price Extra" to $2.0 to one of the variants
  - Go to the Shop and select the product

Issue:

  The extra price badge does not include the taxes
  ($2.0 instead of $2.2), however the final price does.

Cause:

  The price_extra field from ptav (used in the badge) does not include
  the taxes.
  However, when calculating the final price, we do first the sum of all
  prices (including the extra prices) and then apply the taxes.

Solution:

  In the template, for each 'variants', we call `_get_combination_info`
  to get the variant.price_extra with taxes included/excluded
  depending the `Product Prices` setting.

opw-2669871

closes odoo/odoo#80460

Signed-off-by: default avatarYannick Tivisse (yti) <yti@odoo.com>
parent ca0fc6cd
Branches
Tags
No related merge requests found
......@@ -239,8 +239,11 @@ class ProductTemplate(models.Model):
price = product.price if pricelist else list_price
display_image = bool(product.image_1920)
display_name = product.display_name
price_extra = (product.price_extra or 0.0 ) + (sum(no_variant_attributes_price_extra) or 0.0)
else:
product_template = product_template.with_context(current_attributes_price_extra=[v.price_extra or 0.0 for v in combination])
current_attributes_price_extra = [v.price_extra or 0.0 for v in combination]
product_template = product_template.with_context(current_attributes_price_extra=current_attributes_price_extra)
price_extra = sum(current_attributes_price_extra)
list_price = product_template.price_compute('list_price')[product_template.id]
price = product_template.price if pricelist else list_price
display_image = bool(product_template.image_1920)
......@@ -254,6 +257,10 @@ class ProductTemplate(models.Model):
list_price, pricelist.currency_id, product_template._get_current_company(pricelist=pricelist),
fields.Date.today()
)
price_extra = product_template.currency_id._convert(
price_extra, pricelist.currency_id, product_template._get_current_company(pricelist=pricelist),
fields.Date.today()
)
price_without_discount = list_price if pricelist and pricelist.discount_policy == 'without_discount' else price
has_discounted_price = (pricelist or product_template).currency_id.compare_amounts(price_without_discount, price) == 1
......@@ -265,6 +272,7 @@ class ProductTemplate(models.Model):
'display_image': display_image,
'price': price,
'list_price': list_price,
'price_extra': price_extra,
'has_discounted_price': has_discounted_price,
}
......
......@@ -208,6 +208,7 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
self.assertEqual(res['display_name'], "Super Computer (256 GB, 8 GB, 1 To)")
self.assertEqual(res['price'], 2222)
self.assertEqual(res['list_price'], 2222)
self.assertEqual(res['price_extra'], 222)
# CASE: no combination, product given
res = self.computer._get_combination_info(self.env['product.template.attribute.value'], computer_variant.id)
......@@ -216,6 +217,7 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
self.assertEqual(res['display_name'], "Super Computer (256 GB, 8 GB, 1 To)")
self.assertEqual(res['price'], 2222)
self.assertEqual(res['list_price'], 2222)
self.assertEqual(res['price_extra'], 222)
# CASE: using pricelist, quantity rule
pricelist, pricelist_item, currency_ratio, discount_ratio = self._setup_pricelist()
......@@ -226,6 +228,7 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
self.assertEqual(res['display_name'], "Super Computer (256 GB, 8 GB, 1 To)")
self.assertEqual(res['price'], 2222 * currency_ratio * discount_ratio)
self.assertEqual(res['list_price'], 2222 * currency_ratio)
self.assertEqual(res['price_extra'], 222 * currency_ratio)
# CASE: no_variant combination, it's another variant now
......@@ -246,6 +249,7 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
self.assertEqual(res['display_name'], "Super Computer (8 GB, 1 To)")
self.assertEqual(res['price'], 2222 * currency_ratio * discount_ratio)
self.assertEqual(res['list_price'], 2222 * currency_ratio)
self.assertEqual(res['price_extra'], 222 * currency_ratio)
# CASE: dynamic combination, but the variant already exists
self.computer_hdd_attribute_lines.unlink()
......@@ -265,6 +269,7 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
self.assertEqual(res['display_name'], "Super Computer (8 GB, 1 To)")
self.assertEqual(res['price'], 2222 * currency_ratio * discount_ratio)
self.assertEqual(res['list_price'], 2222 * currency_ratio)
self.assertEqual(res['price_extra'], 222 * currency_ratio)
# CASE: dynamic combination, no variant existing
# Test invalidate_cache on product.template _create_variant_ids
......@@ -276,6 +281,7 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
self.assertEqual(res['display_name'], "Super Computer (8 GB, 1 To, Excluded)")
self.assertEqual(res['price'], (2222 - 5) * currency_ratio * discount_ratio)
self.assertEqual(res['list_price'], (2222 - 5) * currency_ratio)
self.assertEqual(res['price_extra'], (222 - 5) * currency_ratio)
# CASE: pricelist set value to 0, no variant
# Test invalidate_cache on product.pricelist write
......@@ -286,6 +292,7 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
self.assertEqual(res['display_name'], "Super Computer (8 GB, 1 To, Excluded)")
self.assertEqual(res['price'], 0)
self.assertEqual(res['list_price'], (2222 - 5) * currency_ratio)
self.assertEqual(res['price_extra'], (222 - 5) * currency_ratio)
def test_03_get_combination_info_discount_policy(self):
computer_ssd_256 = self._get_product_template_attribute_value(self.ssd_256)
......@@ -301,12 +308,14 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
res = self.computer._get_combination_info(combination, add_qty=1, pricelist=pricelist)
self.assertEqual(res['price'], 2222 * currency_ratio)
self.assertEqual(res['list_price'], 2222 * currency_ratio)
self.assertEqual(res['price_extra'], 222 * currency_ratio)
self.assertEqual(res['has_discounted_price'], False)
# CASE: discount, setting with_discount
res = self.computer._get_combination_info(combination, add_qty=2, pricelist=pricelist)
self.assertEqual(res['price'], 2222 * currency_ratio * discount_ratio)
self.assertEqual(res['list_price'], 2222 * currency_ratio)
self.assertEqual(res['price_extra'], 222 * currency_ratio)
self.assertEqual(res['has_discounted_price'], False)
# CASE: no discount, setting without_discount
......@@ -314,12 +323,14 @@ class TestSaleProductAttributeValueConfig(TestSaleProductAttributeValueSetup):
res = self.computer._get_combination_info(combination, add_qty=1, pricelist=pricelist)
self.assertEqual(res['price'], 2222 * currency_ratio)
self.assertEqual(res['list_price'], 2222 * currency_ratio)
self.assertEqual(res['price_extra'], 222 * currency_ratio)
self.assertEqual(res['has_discounted_price'], False)
# CASE: discount, setting without_discount
res = self.computer._get_combination_info(combination, add_qty=2, pricelist=pricelist)
self.assertEqual(res['price'], 2222 * currency_ratio * discount_ratio)
self.assertEqual(res['list_price'], 2222 * currency_ratio)
self.assertEqual(res['price_extra'], 222 * currency_ratio)
self.assertEqual(res['has_discounted_price'], True)
def test_04_create_product_variant_non_dynamic(self):
......
......@@ -20,6 +20,7 @@
t-attf-class="form-control js_variant_change #{ptal.attribute_id.create_variant} #{'d-none' if single_and_custom else ''}"
t-att-name="'ptal-%s' % ptal.id">
<t t-foreach="ptal.product_template_value_ids._only_active()" t-as="ptav">
<t t-set="combination_info_variant" t-value="product._get_combination_info(ptav, pricelist=pricelist)"/>
<option t-att-value="ptav.id"
t-att-data-value_id="ptav.id"
t-att-data-value_name="ptav.name"
......@@ -29,7 +30,7 @@
t-att-data-is_single="single"
t-att-data-is_single_and_custom="single_and_custom">
<span t-field="ptav.name"/>
<span t-if="ptav.price_extra" class="badge badge-pill badge-secondary">
<span t-if="combination_info_variant['price_extra']" class="badge badge-pill badge-secondary">
<!--
price_extra is displayed as catalog price instead of
price after pricelist because it is impossible to
......@@ -39,11 +40,10 @@
attribute is therefore variable and it's not very
accurate to display it.
-->
<t t-esc="ptav.price_extra > 0 and '+' or '-'"/>
<span t-esc="abs(ptav.price_extra)" class="variant_price_extra" style="white-space: nowrap;"
<t t-esc="combination_info_variant['price_extra'] > 0 and '+' or '-'"/>
<span t-esc="abs(combination_info_variant['price_extra'])" class="variant_price_extra" style="white-space: nowrap;"
t-options='{
"widget": "monetary",
"from_currency": product.currency_id,
"display_currency": (pricelist or product).currency_id
}'/>
</span>
......@@ -55,6 +55,7 @@
<t t-if="ptal.attribute_id.display_type == 'radio'">
<ul t-att-data-attribute_id="ptal.attribute_id.id" t-attf-class="list-unstyled #{'d-none' if single_and_custom else ''}">
<t t-foreach="ptal.product_template_value_ids._only_active()" t-as="ptav">
<t t-set="combination_info_variant" t-value="product._get_combination_info(ptav, pricelist=pricelist)"/>
<li class="form-group js_attribute_value" style="margin: 0;">
<label class="col-form-label">
<div>
......@@ -71,13 +72,12 @@
t-att-data-is_single_and_custom="single_and_custom" />
<div class="radio_input_value">
<span t-field="ptav.name"/>
<span class="badge badge-pill badge-secondary" t-if="ptav.price_extra">
<span class="badge badge-pill badge-secondary" t-if="combination_info_variant['price_extra']">
<!-- see note above about price_extra -->
<t t-esc="ptav.price_extra > 0 and '+' or '-'"/>
<span t-esc="abs(ptav.price_extra)" class="variant_price_extra" style="white-space: nowrap;"
<t t-esc="combination_info_variant['price_extra'] > 0 and '+' or '-'"/>
<span t-esc="abs(combination_info_variant['price_extra'])" class="variant_price_extra" style="white-space: nowrap;"
t-options='{
"widget": "monetary",
"from_currency": product.currency_id,
"display_currency": (pricelist or product).currency_id
}'/>
</span>
......
......@@ -304,11 +304,14 @@ class ProductTemplate(models.Model):
list_price = taxes.compute_all(combination_info['list_price'], pricelist.currency_id, quantity_1, product, partner)[tax_display]
else:
list_price = price
combination_info['price_extra'] = self.env['account.tax']._fix_tax_included_price_company(combination_info['price_extra'], product.sudo().taxes_id, taxes, company_id)
price_extra = taxes.compute_all(combination_info['price_extra'], pricelist.currency_id, quantity_1, product, partner)[tax_display]
has_discounted_price = pricelist.currency_id.compare_amounts(list_price, price) == 1
combination_info.update(
price=price,
list_price=list_price,
price_extra=price_extra,
has_discounted_price=has_discounted_price,
)
......
......@@ -44,6 +44,7 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueSe
combination_info = self.computer._get_combination_info()
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio)
self.assertEqual(combination_info['list_price'], 2222 * discount_rate * currency_ratio)
self.assertEqual(combination_info['price_extra'], 222 * currency_ratio)
self.assertEqual(combination_info['has_discounted_price'], False)
# CASE: B2C setting
......@@ -53,6 +54,7 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueSe
combination_info = self.computer._get_combination_info()
self.assertEqual(combination_info['price'], 2222 * discount_rate * currency_ratio * tax_ratio)
self.assertEqual(combination_info['list_price'], 2222 * discount_rate * currency_ratio * tax_ratio)
self.assertEqual(combination_info['price_extra'], round(222 * currency_ratio * tax_ratio, 2))
self.assertEqual(combination_info['has_discounted_price'], False)
# CASE: pricelist 'without_discount'
......@@ -63,6 +65,7 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueSe
combination_info = self.computer._get_combination_info()
self.assertEqual(pricelist.currency_id.compare_amounts(combination_info['price'], 2222 * discount_rate * currency_ratio * tax_ratio), 0)
self.assertEqual(pricelist.currency_id.compare_amounts(combination_info['list_price'], 2222 * currency_ratio * tax_ratio), 0)
self.assertEqual(pricelist.currency_id.compare_amounts(combination_info['price_extra'], 222 * currency_ratio * tax_ratio), 0)
self.assertEqual(combination_info['has_discounted_price'], True)
def test_get_combination_info_with_fpos(self):
......@@ -76,6 +79,14 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueSe
'price': 2000,
}).with_context(website_id=current_website.id)
computer_ssd_attribute_lines = self.env['product.template.attribute.line'].create({
'product_tmpl_id': test_product.id,
'attribute_id': self.ssd_attribute.id,
'value_ids': [(6, 0, [self.ssd_256.id])],
})
computer_ssd_attribute_lines.product_template_value_ids[0].price_extra = 200
combination = computer_ssd_attribute_lines.product_template_value_ids[0]
# Add fixed price for pricelist
pricelist.item_ids = self.env['product.pricelist.item'].create({
'applied_on': "1_product",
......@@ -107,30 +118,34 @@ class TestWebsiteSaleProductAttributeValueConfig(TestSaleProductAttributeValueSe
'tax_dest_id': tax0.id,
})
combination_info = test_product._get_combination_info()
combination_info = test_product._get_combination_info(combination)
self.assertEqual(combination_info['price'], 575, "500$ + 15% tax")
self.assertEqual(combination_info['list_price'], 575, "500$ + 15% tax (2)")
self.assertEqual(combination_info['price_extra'], 230, "200$ + 15% tax")
# Now with fiscal position, taxes should be mapped
self.env.user.partner_id.country_id = self.env.ref('base.be').id
combination_info = test_product._get_combination_info()
combination_info = test_product._get_combination_info(combination)
self.assertEqual(combination_info['price'], 500, "500% + 0% tax (mapped from fp 15% -> 0% for BE)")
self.assertEqual(combination_info['list_price'], 500, "500% + 0% tax (mapped from fp 15% -> 0% for BE) (2)")
self.assertEqual(combination_info['price_extra'], 200, "200% + 0% tax (mapped from fp 15% -> 0% for BE)")
# Try same flow with tax included
tax15.write({'price_include': True})
# Reset / Safety check
self.env.user.partner_id.country_id = None
combination_info = test_product._get_combination_info()
combination_info = test_product._get_combination_info(combination)
self.assertEqual(combination_info['price'], 500, "434.78$ + 15% tax")
self.assertEqual(combination_info['list_price'], 500, "434.78$ + 15% tax (2)")
self.assertEqual(combination_info['price_extra'], 200, "173.91$ + 15% tax")
# Now with fiscal position, taxes should be mapped
self.env.user.partner_id.country_id = self.env.ref('base.be').id
combination_info = test_product._get_combination_info()
combination_info = test_product._get_combination_info(combination)
self.assertEqual(round(combination_info['price'], 2), 434.78, "434.78$ + 0% tax (mapped from fp 15% -> 0% for BE)")
self.assertEqual(round(combination_info['list_price'], 2), 434.78, "434.78$ + 0% tax (mapped from fp 15% -> 0% for BE)")
self.assertEqual(combination_info['price_extra'], 173.91, "173.91$ + 0% tax (mapped from fp 15% -> 0% for BE)")
@tagged('post_install', '-at_install')
class TestWebsiteSaleProductPricelist(TestSaleProductAttributeValueSetup):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment