[FIX] account,sale: fiscal position map tax incl. to incl.

Create a 21% tax included in price
Create a 6% tax included in price
Create a fiscal position that will replace the 21% tax included by the
6% tax included
Create a customer and assign this fiscal position
Create a product :
Price = 121€
Tax = 21% tax included
Create a SO with this customer and this product
Create an invoice (from scratch) with this customer and this product

SO : the unit price is 100,00€
Invoice : the unit price is 106,00€

Fix by creating a common method to calculate the price_unit

opw-2699793

closes odoo/odoo#86514

X-original-commit: 8ab56af6827a640a3f41a87905778432b3786c00
Signed-off-by: Laurent Smet <las@odoo.com>
Signed-off-by: Grazioso Andrea (agr) <agr@odoo.com>
This commit is contained in:
Andrea Grazioso (agr-odoo)
2022-03-18 16:07:22 +01:00
parent 73db2d735f
commit 1fc6f4261b
4 changed files with 151 additions and 60 deletions
+11 -57
View File
@@ -3821,67 +3821,21 @@ class AccountMoveLine(models.Model):
if not self.product_id:
return 0.0
company = self.move_id.company_id
currency = self.move_id.currency_id
company_currency = company.currency_id
product_uom = self.product_id.uom_id
fiscal_position = self.move_id.fiscal_position_id
is_refund_document = self.move_id.move_type in ('out_refund', 'in_refund')
move_date = self.move_id.date or fields.Date.context_today(self)
if self.move_id.is_sale_document(include_receipts=True):
product_price_unit = self.product_id.lst_price
product_taxes = self.product_id.taxes_id
document_type = 'sale'
elif self.move_id.is_purchase_document(include_receipts=True):
product_price_unit = self.product_id.standard_price
product_taxes = self.product_id.supplier_taxes_id
document_type = 'purchase'
else:
return 0.0
product_taxes = product_taxes.filtered(lambda tax: tax.company_id == company)
document_type = 'other'
# Apply unit of measure.
if self.product_uom_id and self.product_uom_id != product_uom:
product_price_unit = product_uom._compute_price(product_price_unit, self.product_uom_id)
# Apply fiscal position.
if product_taxes and fiscal_position:
product_taxes_after_fp = fiscal_position.map_tax(product_taxes)
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=company_currency,
product=self.product_id,
partner=self.partner_id,
is_refund=is_refund_document,
)
product_price_unit = company_currency.round(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,
quantity=1.0,
currency=company_currency,
product=self.product_id,
partner=self.partner_id,
is_refund=is_refund_document,
handle_price_include=False,
)
for tax_res in taxes_res['taxes']:
tax = self.env['account.tax'].browse(tax_res['id'])
if tax.price_include:
product_price_unit += tax_res['amount']
# Apply currency rate.
if currency and currency != company_currency:
product_price_unit = company_currency._convert(product_price_unit, currency, company, move_date)
return product_price_unit
return self.product_id._get_tax_included_unit_price(
self.move_id.company_id,
self.move_id.currency_id,
self.move_id.date,
document_type,
fiscal_position=self.move_id.fiscal_position_id,
product_uom=self.product_uom_id
)
def _get_computed_account(self):
self.ensure_one()
+73
View File
@@ -83,3 +83,76 @@ class ProductProduct(models.Model):
def _get_product_accounts(self):
return self.product_tmpl_id._get_product_accounts()
@api.model
def _get_tax_included_unit_price(self, company, currency, document_date, document_type,
is_refund_document=False, product_uom=None, product_currency=None,
product_price_unit=None, product_taxes=None, fiscal_position=None
):
""" Helper to get the price unit from different models.
This is needed to compute the same unit price in different models (sale order, account move, etc.) with same parameters.
"""
product = self
assert document_type
if product_uom is None:
product_uom = product.uom_id
if not product_currency:
if document_type == 'sale':
product_currency = product.currency_id
elif document_type == 'purchase':
product_currency = company.currency_id
if product_price_unit is None:
if document_type == 'sale':
product_price_unit = product.with_company(company).lst_price
elif document_type == 'purchase':
product_price_unit = product.with_company(company).standard_price
else:
return 0.0
if product_taxes is None:
if document_type == 'sale':
product_taxes = product.taxes_id.filtered(lambda x: x.company_id == company)
elif document_type == 'purchase':
product_taxes = product.supplier_taxes_id.filtered(lambda x: x.company_id == company)
# Apply unit of measure.
if product_uom and product.uom_id != product_uom:
product_price_unit = product.uom_id._compute_price(product_price_unit, product_uom)
# Apply fiscal position.
if product_taxes and fiscal_position:
product_taxes_after_fp = fiscal_position.map_tax(product_taxes)
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']
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,
quantity=1.0,
currency=currency,
product=product,
is_refund=is_refund_document,
handle_price_include=False,
)
for tax_res in taxes_res['taxes']:
tax = self.env['account.tax'].browse(tax_res['id'])
if tax.price_include:
product_price_unit += tax_res['amount']
# Apply currency rate.
if currency != product_currency:
product_price_unit = product_currency._convert(product_price_unit, currency, company, document_date)
return product_price_unit
+10 -2
View File
@@ -410,8 +410,16 @@ class SaleOrderLine(models.Model):
line.price_unit = 0.0
else:
price = line._get_display_price()
line.price_unit = self.env['account.tax']._fix_tax_included_price_company(
price, line.product_id.taxes_id, line.tax_id, line.company_id)
line.price_unit = line.product_id._get_tax_included_unit_price(
line.company_id,
line.order_id.currency_id,
line.order_id.date_order,
'sale',
fiscal_position=line.order_id.fiscal_position_id,
product_price_unit=price,
product_currency=line.currency_id
)
def _get_display_price(self):
self.ensure_one()
+57 -1
View File
@@ -3,7 +3,7 @@
from .common import TestSaleCommon
from odoo.fields import Command
from odoo.tests import tagged
from odoo.tests import tagged, Form
@tagged('post_install', '-at_install')
@@ -148,6 +148,62 @@ class TestSaleOrder(TestSaleCommon):
self.assertEqual(line.discount, 0.0, 'Pricelist of SO should not be applied on an order line')
self.assertEqual(line.price_unit, line.product_id.list_price, 'Unit price of order line should be a sale price as the pricelist not applied on the other category\'s product')
def test_fiscalposition_application(self):
"""Test application of a fiscal position mapping
price included to price included tax
"""
uom = self.env['uom.uom'].search([('name', '=', 'Units')])
pricelist = self.env['product.pricelist'].search([('name', '=', 'Public Pricelist')])
partner = self.env['res.partner'].create({
'name': "George"
})
tax_include_src = self.env['account.tax'].create({
'name': "Include tax",
'amount': 21.00,
'price_include': True,
})
tax_include_dst = self.env['account.tax'].create({
'name': "Exclude tax",
'amount': 6.00,
'price_include': True,
})
product_tmpl = 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
fpos = self.env['account.fiscal.position'].create({
'name': "fiscal position",
'sequence': 1
})
self.env['account.fiscal.position.tax'].create({
'position_id' :fpos.id,
'tax_src_id': tax_include_src.id,
'tax_dest_id': tax_include_dst.id
})
# Create the SO with one SO line and apply a pricelist and fiscal position on it
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
with order_form.order_line.new() as line:
line.name = product_product.name
line.product_id = product_product
line.product_uom_qty = 1.0
line.product_uom = uom
sale_order = order_form.save()
# Check the unit price of SO line
self.assertRecordValues(sale_order.order_line, [{'price_unit': 106}])
def test_pricelist_application(self):
""" Test different prices are correctly applied based on dates """
support_product = self.env['product.product'].create({