summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurent Smet <las@odoo.com>2021-04-13 06:41:01 +0000
committerLaurent Smet <las@odoo.com>2021-04-13 14:42:15 +0000
commitc8c68a07199124091e8d49946a63fb8c705c2417 (patch)
tree876c3ca5396f878473c9888b4e1e23b3433019fe
parent5b56dbfd8eb3284d670280f2e86a35ab10776e92 (diff)
[FIX] account,point_of_sale: Allow mixing 'affect base' price-included/price-excluded taxes
Before this commit, such configuration wasn't allowed. However, in some countries, this is mandatory, when dealing with eco-tax for example: Suppose t1, t2 being taxes where: - t1 is include_base_amount, price_excluded, fixed tax of 10 (eco-tax) - t2 is include_base_amount, price_included, percent of 21% The computation on 1210 should be: price_excluded: 1210 / 1.21 = 1000 price_included: (1000 + 10) * 1.21 = 1222.1 closes odoo/odoo#69184 Signed-off-by: oco-odoo <oco-odoo@users.noreply.github.com>
-rw-r--r--addons/account/models/account_tax.py29
-rw-r--r--addons/account/tests/test_tax.py28
-rw-r--r--addons/point_of_sale/static/src/js/models.js29
3 files changed, 50 insertions, 36 deletions
diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py
index 109123d2ce8..59790f1b435 100644
--- a/addons/account/models/account_tax.py
+++ b/addons/account/models/account_tax.py
@@ -370,22 +370,10 @@ class AccountTax(models.Model):
# 1) Flatten the taxes.
taxes, groups_map = self.flatten_taxes_hierarchy(create_map=True)
- # 2) Avoid mixing taxes having price_include=False && include_base_amount=True
- # with taxes having price_include=True. This use case is not supported as the
- # computation of the total_excluded would be impossible.
- base_excluded_flag = False # price_include=False && include_base_amount=True
- included_flag = False # price_include=True
- for tax in taxes:
- if tax.price_include:
- included_flag = True
- elif tax.include_base_amount:
- base_excluded_flag = True
- if base_excluded_flag and included_flag:
- raise UserError(_('Unable to mix any taxes being price included with taxes affecting the base amount but not included in price.'))
-
- # 3) Deal with the rounding methods
+ # 2) Deal with the rounding methods
if not currency:
currency = company.currency_id
+
# By default, for each tax, tax amount will first be computed
# and rounded at the 'Account' decimal precision for each
# PO/SO/invoice line and then these rounded amounts will be
@@ -408,7 +396,7 @@ class AccountTax(models.Model):
if not round_tax:
prec *= 1e-5
- # 4) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation.
+ # 3) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation.
# tax | base | amount |
# /\ ----------------------------
# || tax_1 | XXXX | | <- we are looking for that, it's the total_excluded
@@ -515,10 +503,15 @@ class AccountTax(models.Model):
total_excluded = currency.round(recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount))
- # 5) Iterate the taxes in the sequence order to compute missing tax amounts.
+ # 4) Iterate the taxes in the sequence order to compute missing tax amounts.
# Start the computation of accumulated amounts at the total_excluded value.
base = total_included = total_void = total_excluded
+ # Flag indicating the checkpoint used in price_include to avoid rounding issue must be skipped since the base
+ # amount has changed because we are currently mixing price-included and price-excluded include_base_amount
+ # taxes.
+ skip_checkpoint = False
+
taxes_vals = []
i = 0
cumulated_tax_included_amount = 0
@@ -529,7 +522,7 @@ class AccountTax(models.Model):
price_include = self._context.get('force_price_include', tax.price_include)
#compute the tax_amount
- if price_include and total_included_checkpoints.get(i):
+ if not skip_checkpoint and price_include and total_included_checkpoints.get(i):
# We know the total to reach for that tax, so we make a substraction to avoid any rounding issues
tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount)
cumulated_tax_included_amount = 0
@@ -594,6 +587,8 @@ class AccountTax(models.Model):
# Affect subsequent taxes
if tax.include_base_amount:
base += factorized_tax_amount
+ if not price_include:
+ skip_checkpoint = True
total_included += factorized_tax_amount
i += 1
diff --git a/addons/account/tests/test_tax.py b/addons/account/tests/test_tax.py
index 532ca7eb0c3..068031ceac0 100644
--- a/addons/account/tests/test_tax.py
+++ b/addons/account/tests/test_tax.py
@@ -1045,3 +1045,31 @@ class TestTax(TestTaxCommon):
],
res3
)
+
+ def test_mixing_price_included_excluded_with_affect_base(self):
+ tax_10_fix = self.env['account.tax'].create({
+ 'name': "tax_10_fix",
+ 'amount_type': 'fixed',
+ 'amount': 10.0,
+ 'include_base_amount': True,
+ })
+ tax_21 = self.env['account.tax'].create({
+ 'name': "tax_21",
+ 'amount_type': 'percent',
+ 'amount': 21.0,
+ 'price_include': True,
+ 'include_base_amount': True,
+ })
+
+ self._check_compute_all_results(
+ 1222.1, # 'total_included'
+ 1000.0, # 'total_excluded'
+ [
+ # base , amount
+ # ---------------
+ (1000.0, 10.0),
+ (1010.0, 212.1),
+ # ---------------
+ ],
+ (tax_10_fix + tax_21).compute_all(1210),
+ )
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 4423904b722..3c9aa1ed5c8 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -2153,21 +2153,7 @@ exports.Orderline = Backbone.Model.extend({
taxes = collect_taxes(taxes);
- // 2) Avoid dealing with taxes mixing price_include=False && include_base_amount=True
- // with price_include=True
-
- var base_excluded_flag = false; // price_include=False && include_base_amount=True
- var included_flag = false; // price_include=True
- _(taxes).each(function(tax){
- if(tax.price_include)
- included_flag = true;
- else if(tax.include_base_amount)
- base_excluded_flag = true;
- if(base_excluded_flag && included_flag)
- throw new Error('Unable to mix any taxes being price included with taxes affecting the base amount but not included in price.');
- });
-
- // 3) Deal with the rounding methods
+ // 2) Deal with the rounding methods
var round_tax = this.pos.company.tax_calculation_rounding_method != 'round_globally';
@@ -2175,7 +2161,7 @@ exports.Orderline = Backbone.Model.extend({
if(!round_tax)
currency_rounding = currency_rounding * 0.00001;
- // 4) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation.
+ // 3) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation.
var recompute_base = function(base_amount, fixed_amount, percent_amount, division_amount){
return (base_amount - fixed_amount) / (1.0 + percent_amount / 100.0) * (100 - division_amount) / 100;
}
@@ -2230,15 +2216,17 @@ exports.Orderline = Backbone.Model.extend({
var total_excluded = round_pr(recompute_base(base, incl_fixed_amount, incl_percent_amount, incl_division_amount), initial_currency_rounding);
var total_included = total_excluded;
- // 5) Iterate the taxes in the sequence order to fill missing base/amount values.
+ // 4) Iterate the taxes in the sequence order to fill missing base/amount values.
base = total_excluded;
+ var skip_checkpoint = false;
+
var taxes_vals = [];
i = 0;
var cumulated_tax_included_amount = 0;
_(taxes.reverse()).each(function(tax){
- if(tax.price_include && total_included_checkpoints[i] !== undefined){
+ if(!skip_checkpoint && tax.price_include && total_included_checkpoints[i] !== undefined){
var tax_amount = total_included_checkpoints[i] - (base + cumulated_tax_included_amount);
cumulated_tax_included_amount = 0;
}else
@@ -2256,8 +2244,11 @@ exports.Orderline = Backbone.Model.extend({
'base': sign * round_pr(base, currency_rounding),
});
- if(tax.include_base_amount)
+ if(tax.include_base_amount){
base += tax_amount;
+ if(!tax.price_include)
+ skip_checkpoint = true;
+ }
total_included += tax_amount;
i += 1;