From 8ba79088803da7c5294fcd83a2c6d1e46efd8e15 Mon Sep 17 00:00:00 2001 From: "Andrea Grazioso (agr-odoo)" Date: Wed, 6 Apr 2022 09:25:03 +0000 Subject: [PATCH] [FIX] account,sale: fix tax excl -> tax incl price include MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create a 15% tax [TaxA] *not* included in price Create a 15% tax [TaxB] included in price Create a fiscal position mapping TaxA to TaxB Create a customer and assign this fiscal position Create a product : Price = 100€ Tax = TaxA Create a SO with this customer and this product We have this situation | Price Unit | Tax | Subtotal | |------------|-----|----------| | 115 | TaxB| 100 | While it should be | Price Unit | Tax | Subtotal | |------------|-----|----------| | 100 | TaxB| 86.96 | after tax mapping, as it was before https://github.com/odoo/odoo/commit/625486a8382fde9033b73e2a4b4e7713cf4131c1 opw-2811596 closes odoo/odoo#89361 X-original-commit: 1a5b2b44c63afb302a2d6fc1e841e4ef3af46df5 Signed-off-by: Laurent Smet Signed-off-by: Grazioso Andrea (agr) --- addons/account/models/product.py | 24 ++--- addons/sale/tests/test_sale_pricelist.py | 116 ++++++++++++++++++++--- 2 files changed, 115 insertions(+), 25 deletions(-) diff --git a/addons/account/models/product.py b/addons/account/models/product.py index 459d193cd0e..8a9897478b5 100644 --- a/addons/account/models/product.py +++ b/addons/account/models/product.py @@ -129,20 +129,20 @@ class ProductProduct(models.Model): # Apply fiscal position. if product_taxes and fiscal_position: product_taxes_after_fp = fiscal_position.map_tax(product_taxes) + flattened_taxes_after_fp = product_taxes_after_fp._origin.flatten_taxes_hierarchy() + flattened_taxes_before_fp = product_taxes._origin.flatten_taxes_hierarchy() + taxes_before_included = any(tax.price_include for tax in flattened_taxes_before_fp) - if set(product_taxes.ids) != set(product_taxes_after_fp.ids): - flattened_taxes_before_fp = product_taxes._origin.flatten_taxes_hierarchy() - if any(tax.price_include for tax in flattened_taxes_before_fp): - taxes_res = flattened_taxes_before_fp.compute_all( - product_price_unit, - quantity=1.0, - currency=currency, - product=product, - is_refund=is_refund_document, - ) - product_price_unit = taxes_res['total_excluded'] + if set(product_taxes.ids) != set(product_taxes_after_fp.ids) and taxes_before_included: + taxes_res = flattened_taxes_before_fp.compute_all( + product_price_unit, + quantity=1.0, + currency=currency, + product=product, + is_refund=is_refund_document, + ) + product_price_unit = taxes_res['total_excluded'] - flattened_taxes_after_fp = product_taxes_after_fp._origin.flatten_taxes_hierarchy() if any(tax.price_include for tax in flattened_taxes_after_fp): taxes_res = flattened_taxes_after_fp.compute_all( product_price_unit, diff --git a/addons/sale/tests/test_sale_pricelist.py b/addons/sale/tests/test_sale_pricelist.py index 39dc268ff93..fa810e4b34e 100644 --- a/addons/sale/tests/test_sale_pricelist.py +++ b/addons/sale/tests/test_sale_pricelist.py @@ -172,49 +172,139 @@ class TestSaleOrder(TestSaleCommon): 'name': "George" }) tax_include_src = self.env['account.tax'].create({ - 'name': "Include tax", + 'name': "Include 21%", 'amount': 21.00, + 'amount_type': 'percent', 'price_include': True, }) tax_include_dst = self.env['account.tax'].create({ - 'name': "Exclude tax", + 'name': "Include 6%", 'amount': 6.00, + 'amount_type': 'percent', 'price_include': True, }) - - product_tmpl = self.env['product.template'].create({ + tax_exclude_src = self.env['account.tax'].create({ + 'name': "Exclude 15%", + 'amount': 15.00, + 'amount_type': 'percent', + 'price_include': False, + }) + tax_exclude_dst = self.env['account.tax'].create({ + 'name': "Exclude 21%", + 'amount': 21.00, + 'amount_type': 'percent', + 'price_include': False, + }) + product_tmpl_a = self.env['product.template'].create({ 'name': "Voiture", 'list_price': 121, 'taxes_id': [(6, 0, [tax_include_src.id])] }) - product_product = product_tmpl.product_variant_id + product_tmpl_b = self.env['product.template'].create({ + 'name': "Voiture", + 'list_price': 100, + 'taxes_id': [(6, 0, [tax_exclude_src.id])] + }) - fpos = self.env['account.fiscal.position'].create({ - 'name': "fiscal position", + fpos_incl_incl = self.env['account.fiscal.position'].create({ + 'name': "incl -> incl", 'sequence': 1 }) self.env['account.fiscal.position.tax'].create({ - 'position_id' :fpos.id, + 'position_id' :fpos_incl_incl.id, 'tax_src_id': tax_include_src.id, 'tax_dest_id': tax_include_dst.id }) + fpos_excl_incl = self.env['account.fiscal.position'].create({ + 'name': "excl -> incl", + 'sequence': 2, + }) + + self.env['account.fiscal.position.tax'].create({ + 'position_id' :fpos_excl_incl.id, + 'tax_src_id': tax_exclude_src.id, + 'tax_dest_id': tax_include_dst.id + }) + + fpos_incl_excl = self.env['account.fiscal.position'].create({ + 'name': "incl -> excl", + 'sequence': 3, + }) + + self.env['account.fiscal.position.tax'].create({ + 'position_id' :fpos_incl_excl.id, + 'tax_src_id': tax_include_src.id, + 'tax_dest_id': tax_exclude_dst.id + }) + + fpos_excl_excl = self.env['account.fiscal.position'].create({ + 'name': "excl -> excp", + 'sequence': 4, + }) + + self.env['account.fiscal.position.tax'].create({ + 'position_id' :fpos_excl_excl.id, + 'tax_src_id': tax_exclude_src.id, + 'tax_dest_id': tax_exclude_dst.id + }) + # Create the SO with one SO line and apply a pricelist and fiscal position on it + # Then check if price unit and price subtotal matches the expected values + + # Test Mapping included to included order_form = Form(self.env['sale.order'].with_context(tracking_disable=True)) order_form.partner_id = partner order_form.pricelist_id = pricelist - order_form.fiscal_position_id = fpos + order_form.fiscal_position_id = fpos_incl_incl with order_form.order_line.new() as line: - line.name = product_product.name - line.product_id = product_product + line.name = product_tmpl_a.product_variant_id.name + line.product_id = product_tmpl_a.product_variant_id line.product_uom_qty = 1.0 line.product_uom = uom sale_order = order_form.save() + self.assertRecordValues(sale_order.order_line, [{'price_unit': 106, 'price_subtotal': 100}]) - # Check the unit price of SO line - self.assertRecordValues(sale_order.order_line, [{'price_unit': 106}]) + # Test Mapping excluded to included + order_form = Form(self.env['sale.order'].with_context(tracking_disable=True)) + order_form.partner_id = partner + order_form.pricelist_id = pricelist + order_form.fiscal_position_id = fpos_excl_incl + with order_form.order_line.new() as line: + line.name = product_tmpl_b.product_variant_id.name + line.product_id = product_tmpl_b.product_variant_id + line.product_uom_qty = 1.0 + line.product_uom = uom + sale_order = order_form.save() + self.assertRecordValues(sale_order.order_line, [{'price_unit': 100, 'price_subtotal': 94.34}]) + + # Test Mapping included to excluded + order_form = Form(self.env['sale.order'].with_context(tracking_disable=True)) + order_form.partner_id = partner + order_form.pricelist_id = pricelist + order_form.fiscal_position_id = fpos_incl_excl + with order_form.order_line.new() as line: + line.name = product_tmpl_a.product_variant_id.name + line.product_id = product_tmpl_a.product_variant_id + line.product_uom_qty = 1.0 + line.product_uom = uom + sale_order = order_form.save() + self.assertRecordValues(sale_order.order_line, [{'price_unit': 100, 'price_subtotal': 100}]) + + # Test Mapping excluded to excluded + order_form = Form(self.env['sale.order'].with_context(tracking_disable=True)) + order_form.partner_id = partner + order_form.pricelist_id = pricelist + order_form.fiscal_position_id = fpos_excl_excl + with order_form.order_line.new() as line: + line.name = product_tmpl_b.product_variant_id.name + line.product_id = product_tmpl_b.product_variant_id + line.product_uom_qty = 1.0 + line.product_uom = uom + sale_order = order_form.save() + self.assertRecordValues(sale_order.order_line, [{'price_unit': 100, 'price_subtotal': 100}]) def test_pricelist_application(self): """ Test different prices are correctly applied based on dates """