diff options
| author | Laurent Smet <las@odoo.com> | 2021-04-13 06:41:01 +0000 |
|---|---|---|
| committer | Laurent Smet <las@odoo.com> | 2021-04-13 14:42:15 +0000 |
| commit | c8c68a07199124091e8d49946a63fb8c705c2417 (patch) | |
| tree | 876c3ca5396f878473c9888b4e1e23b3433019fe | |
| parent | 5b56dbfd8eb3284d670280f2e86a35ab10776e92 (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.py | 29 | ||||
| -rw-r--r-- | addons/account/tests/test_tax.py | 28 | ||||
| -rw-r--r-- | addons/point_of_sale/static/src/js/models.js | 29 |
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; |
