summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/account/models/account_move.py98
-rw-r--r--addons/account/tests/test_account_move_out_invoice.py104
2 files changed, 172 insertions, 30 deletions
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 64b17d98d52..1fa1b0e30af 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -3157,23 +3157,75 @@ class AccountMoveLine(models.Model):
return '\n'.join(values)
def _get_computed_price_unit(self):
+ ''' Helper to get the default price unit based on the product by taking care of the taxes
+ set on the product and the fiscal position.
+ :return: The price unit.
+ '''
self.ensure_one()
if not self.product_id:
- return self.price_unit
- elif self.move_id.is_sale_document(include_receipts=True):
- # Out invoice.
- price_unit = self.product_id.lst_price
+ 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
elif self.move_id.is_purchase_document(include_receipts=True):
- # In invoice.
- price_unit = self.product_id.standard_price
+ product_price_unit = self.product_id.standard_price
+ product_taxes = self.product_id.supplier_taxes_id
else:
- return self.price_unit
+ return 0.0
+ product_taxes = product_taxes.filtered(lambda tax: tax.company_id == company)
+
+ # 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, partner=self.partner_id)
+
+ 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']
- if self.product_uom_id != self.product_id.uom_id:
- price_unit = self.product_id.uom_id._compute_price(price_unit, self.product_uom_id)
+ # Apply currency rate.
+ if currency and currency != company_currency:
+ product_price_unit = company_currency._convert(product_price_unit, currency, company, move_date)
- return price_unit
+ return product_price_unit
def _get_computed_account(self):
self.ensure_one()
@@ -3474,33 +3526,21 @@ class AccountMoveLine(models.Model):
line.name = line._get_computed_name()
line.account_id = line._get_computed_account()
- line.tax_ids = line._get_computed_taxes()
+ taxes = line._get_computed_taxes()
+ if taxes and line.move_id.fiscal_position_id:
+ taxes = line.move_id.fiscal_position_id.map_tax(taxes, partner=line.partner_id)
+ line.tax_ids = taxes
line.product_uom_id = line._get_computed_uom()
line.price_unit = line._get_computed_price_unit()
- # price_unit and taxes may need to be adapted following Fiscal Position
- line._set_price_and_tax_after_fpos()
-
- # Convert the unit price to the invoice's currency.
- company = line.move_id.company_id
- line.price_unit = company.currency_id._convert(line.price_unit, line.move_id.currency_id, company, line.move_id.date, round=False)
-
@api.onchange('product_uom_id')
def _onchange_uom_id(self):
''' Recompute the 'price_unit' depending of the unit of measure. '''
- price_unit = self._get_computed_price_unit()
-
- # See '_onchange_product_id' for details.
taxes = self._get_computed_taxes()
if taxes and self.move_id.fiscal_position_id:
- price_subtotal = self._get_price_total_and_subtotal(price_unit=price_unit, taxes=taxes)['price_subtotal']
- accounting_vals = self._get_fields_onchange_subtotal(price_subtotal=price_subtotal, currency=self.move_id.company_currency_id)
- amount_currency = accounting_vals['amount_currency']
- price_unit = self._get_fields_onchange_balance(amount_currency=amount_currency, force_computation=True).get('price_unit', price_unit)
-
- # Convert the unit price to the invoice's currency.
- company = self.move_id.company_id
- self.price_unit = company.currency_id._convert(price_unit, self.move_id.currency_id, company, self.move_id.date, round=False)
+ taxes = self.move_id.fiscal_position_id.map_tax(taxes, partner=self.partner_id)
+ self.tax_ids = taxes
+ self.price_unit = self._get_computed_price_unit()
@api.onchange('account_id')
def _onchange_account_id(self):
diff --git a/addons/account/tests/test_account_move_out_invoice.py b/addons/account/tests/test_account_move_out_invoice.py
index b6dc01a68d6..b2d6ea84dd7 100644
--- a/addons/account/tests/test_account_move_out_invoice.py
+++ b/addons/account/tests/test_account_move_out_invoice.py
@@ -830,7 +830,7 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
'amount_total': 1730.0,
})
- def test_out_invoice_line_onchange_rounding_price_subtotal(self):
+ def test_out_invoice_line_onchange_rounding_price_subtotal_1(self):
''' Seek for rounding issue on the price_subtotal when dealing with a price_unit having more digits than the
foreign currency one.
'''
@@ -904,6 +904,108 @@ class TestAccountMoveOutInvoiceOnchanges(AccountTestInvoicingCommon):
check_invoice_values(invoice_2)
+ def test_out_invoice_line_onchange_rounding_price_subtotal_2(self):
+ """ Ensure the cyclic computations implemented using onchanges are not leading to rounding issues when using
+ price-included taxes.
+ For example:
+ 100 / 1.21 ~= 82.64 but 82.64 * 1.21 ~= 99.99 != 100.0.
+ """
+
+ def check_invoice_values(invoice):
+ self.assertInvoiceValues(invoice, [
+ {
+ 'price_unit': 100.0,
+ 'price_subtotal': 82.64,
+ 'debit': 0.0,
+ 'credit': 82.64,
+ },
+ {
+ 'price_unit': 17.36,
+ 'price_subtotal': 17.36,
+ 'debit': 0.0,
+ 'credit': 17.36,
+ },
+ {
+ 'price_unit': -100.0,
+ 'price_subtotal': -100.0,
+ 'debit': 100.0,
+ 'credit': 0.0,
+ },
+ ], {
+ 'amount_untaxed': 82.64,
+ 'amount_tax': 17.36,
+ 'amount_total': 100.0,
+ })
+
+ tax = self.env['account.tax'].create({
+ 'name': '21%',
+ 'amount': 21.0,
+ 'price_include': True,
+ 'include_base_amount': True,
+ })
+
+ # == Test assigning tax directly ==
+
+ invoice_create = self.env['account.move'].create({
+ 'move_type': 'out_invoice',
+ 'invoice_date': '2017-01-01',
+ 'date': '2017-01-01',
+ 'partner_id': self.partner_a.id,
+ 'invoice_line_ids': [(0, 0, {
+ 'name': 'test line',
+ 'price_unit': 100.0,
+ 'account_id': self.company_data['default_account_revenue'].id,
+ 'tax_ids': [(6, 0, tax.ids)],
+ })],
+ })
+
+ check_invoice_values(invoice_create)
+
+ move_form = Form(self.env['account.move'].with_context(default_move_type='out_invoice'))
+ move_form.invoice_date = fields.Date.from_string('2017-01-01')
+ move_form.partner_id = self.partner_a
+ with move_form.invoice_line_ids.new() as line_form:
+ line_form.name = 'test line'
+ line_form.price_unit = 100.0
+ line_form.account_id = self.company_data['default_account_revenue']
+ line_form.tax_ids.clear()
+ line_form.tax_ids.add(tax)
+ invoice_onchange = move_form.save()
+
+ check_invoice_values(invoice_onchange)
+
+ # == Test when the tax is set on a product ==
+
+ product = self.env['product.product'].create({
+ 'name': 'product',
+ 'lst_price': 100.0,
+ 'property_account_income_id': self.company_data['default_account_revenue'].id,
+ 'taxes_id': [(6, 0, tax.ids)],
+ })
+
+ move_form = Form(self.env['account.move'].with_context(default_move_type='out_invoice'))
+ move_form.invoice_date = fields.Date.from_string('2017-01-01')
+ move_form.partner_id = self.partner_a
+ with move_form.invoice_line_ids.new() as line_form:
+ line_form.product_id = product
+ invoice_onchange = move_form.save()
+
+ check_invoice_values(invoice_onchange)
+
+ # == Test with a fiscal position ==
+
+ fiscal_position = self.env['account.fiscal.position'].create({'name': 'fiscal_position'})
+
+ move_form = Form(self.env['account.move'].with_context(default_move_type='out_invoice'))
+ move_form.invoice_date = fields.Date.from_string('2017-01-01')
+ move_form.partner_id = self.partner_a
+ move_form.fiscal_position_id = fiscal_position
+ with move_form.invoice_line_ids.new() as line_form:
+ line_form.product_id = product
+ invoice_onchange = move_form.save()
+
+ check_invoice_values(invoice_onchange)
+
def test_out_invoice_line_onchange_taxes_2_price_unit_tax_included(self):
''' Seek for rounding issue in the price unit. Suppose a price_unit of 2300 with a 5.5% price-included tax
applied on it.