From 8f1d889e50e18d81db7d4e8bb0555513a2b1019c Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Fri, 13 Oct 2017 10:28:04 +0200 Subject: [PATCH 1/8] [FIX] tools: correctly export the non-module translations 8ac39287 introdcude a way to export the source terms not located inside an addons module (e.g. error messages in openerp/service/models.py) 0529a7f9 fixed a bug in the get_module_from_path with addons path with similar names. The above commit introduced a regression making files located outside of an addons path to be wrongly considered as a module e.g. openerp/service/models.py used not to match any module and was considered correctly as belonging to base module After, 0529a7f9, '~/openerp/service' being different than '~/openerp/service/' the module was considered as 'models.py' (which is incorrect). Compare correctly both parent path to have a correct match Fixes # 19907 --- openerp/tools/translate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openerp/tools/translate.py b/openerp/tools/translate.py index 6929b31f954..886a87860b3 100644 --- a/openerp/tools/translate.py +++ b/openerp/tools/translate.py @@ -958,7 +958,8 @@ def trans_generate(lang, modules, cr): def get_module_from_path(path): for (mp, rec) in path_list: mp = os.path.join(mp, '') - if rec and path.startswith(mp) and os.path.dirname(path) != mp: + dirname = os.path.join(os.path.dirname(path), '') + if rec and path.startswith(mp) and dirname != mp: path = path[len(mp):] return path.split(os.path.sep)[0] return 'base' # files that are not in a module are considered as being in 'base' module From dc5bb0d6b2806e81d1dd32839e6a600c8782bad3 Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Fri, 13 Oct 2017 10:50:24 +0200 Subject: [PATCH 2/8] [FIX] website_forum: add missing karma_answer field in form view This commit closes #19672 --- addons/website_forum/views/forum.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/website_forum/views/forum.xml b/addons/website_forum/views/forum.xml index a8b45e64c04..03dd07cda85 100644 --- a/addons/website_forum/views/forum.xml +++ b/addons/website_forum/views/forum.xml @@ -63,6 +63,7 @@ + From 5afb5ecd04cf49be6bedb655b187b19a65e1e02d Mon Sep 17 00:00:00 2001 From: Laurent Smet Date: Mon, 16 Oct 2017 11:35:08 +0200 Subject: [PATCH 3/8] [FIX] account: fix complex tax computations (multiple price included taxes, etc.) Suppose the correct computation: seq | amount | incl | incl_base | base | amount ---------------------------------------------------- 1 | 10% | t | t | 100.0 | 10.0 2 | 10% | | | 110.0 | 11.0 3 | 10% | t | | 110.0 | 11.0 4 | 10% | | | 110.0 | 11.0 5 | 10% | t | | 110.0 | 11.0 ---------------------------------------------------- with an initial amount of 132. Without this commit, the base amount was computed by: 132 / 1.1 / 1.1 = 109.09 instead of: 132 / 1.2 = 110.0 see task: https://www.odoo.com/web#id=34471&view_type=form&model=project.task&action=333&active_id=967&menu_id=4720 Was PR #18275 --- addons/account/models/account.py | 165 ++++++++++---- addons/account/tests/test_tax.py | 213 ++++++++++++++++--- addons/point_of_sale/static/src/js/models.js | 156 ++++++++++---- 3 files changed, 421 insertions(+), 113 deletions(-) diff --git a/addons/account/models/account.py b/addons/account/models/account.py index c64473778ec..4630ece0eeb 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -619,6 +619,7 @@ class AccountTax(models.Model): price_unit * quantity eventually affected by previous taxes (if tax is include_base_amount XOR price_include) """ self.ensure_one() + price_include = self._context.get('force_price_include', self.price_include) if self.amount_type == 'fixed': # Use copysign to take into account the sign of the base amount which includes the sign # of the quantity and the sign of the price_unit @@ -632,11 +633,11 @@ class AccountTax(models.Model): return math.copysign(quantity, base_amount) * self.amount else: return quantity * self.amount - if (self.amount_type == 'percent' and not self.price_include) or (self.amount_type == 'division' and self.price_include): + if (self.amount_type == 'percent' and not price_include) or (self.amount_type == 'division' and self.price_include): return base_amount * self.amount / 100 - if self.amount_type == 'percent' and self.price_include: + if self.amount_type == 'percent' and price_include: return base_amount - (base_amount / (1 + self.amount / 100)) - if self.amount_type == 'division' and not self.price_include: + if self.amount_type == 'division' and not price_include: return base_amount / (1 - self.amount / 100) - base_amount @api.v8 @@ -659,13 +660,58 @@ class AccountTax(models.Model): 'analytic': boolean, }] } """ + + # 1) Flatten the taxes. + + def collect_taxes(self, all_taxes=None): + # Collect all the taxes recursively ordered by the sequence. + # Example: + # group | seq | sub-group | + # ------------|-----------| + # | 1 | | + # ------------|-----------| + # t | 2 | | seq | | + # | | | 4 | | + # | | | 5 | | + # | | | 6 | | + # | | | + # ------------|-----------| + # | 3 | | + # ------------|-----------| + # Result: 1-4-5-6-3 + if not all_taxes: + all_taxes = self.env['account.tax'] + for tax in self.sorted(key=lambda r: r.sequence): + if tax.amount_type == 'group': + all_taxes = collect_taxes(tax.children_tax_ids, all_taxes) + else: + all_taxes += tax + return all_taxes + + taxes = collect_taxes(self) + + # 2) Avoid dealing with taxes mixing price_include=False && include_base_amount=True + # with price_include=True + + 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 + if len(self) == 0: company_id = self.env.user.company_id else: company_id = self[0].company_id if not currency: currency = company_id.currency_id - taxes = [] + # 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 @@ -690,50 +736,93 @@ class AccountTax(models.Model): if not round_tax: prec += 5 - base_values = self.env.context.get('base_values') - if not base_values: - total_excluded = total_included = base = round(price_unit * quantity, prec) + # 4) 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 + # || tax_2 | | | + # || tax_3 | | | + # || ... | .. | .. | + # ---------------------------- + + def recompute_base(base_amount, fixed_amount, percent_amount): + # Recompute the new base amount based on included fixed/percent amount and the current base amount. + # Example: + # tax | amount | + # ------------------ + # tax_1 | 10% | + # tax_2 | 15 | + # tax_3 | 20% | + # ------------------ + # if base_amount = 145, the new base is computed as: + # (145 - 15) / (1.0 + ((10 + 20) / 100.0)) = 130 / 1.3 = 100 + if fixed_amount == 0.0 and percent_amount == 0.0: + return base_amount + return round((base_amount - fixed_amount) / (1.0 + percent_amount / 100.0), prec) + + base = round(price_unit * quantity, prec) + + # For the computation of move lines, we could have a negative base value. + # In this case, compute all with positive values and negative them at the end. + if base < 0: + base = -base + sign = -1 else: - total_excluded, total_included, base = base_values + sign = 1 - # Sorting key is mandatory in this case. When no key is provided, sorted() will perform a - # search. However, the search method is overridden in account.tax in order to add a domain - # depending on the context. This domain might filter out some taxes from self, e.g. in the - # case of group taxes. - for tax in self.sorted(key=lambda r: r.sequence): - if tax.amount_type == 'group': - children = tax.children_tax_ids.with_context(base_values=(total_excluded, total_included, base)) - ret = children.compute_all(price_unit, currency, quantity, product, partner) - total_excluded = ret['total_excluded'] - base = ret['base'] if tax.include_base_amount else base - total_included = ret['total_included'] - tax_amount = total_included - total_excluded - taxes += ret['taxes'] - continue + # Keep track of the accumulated included fixed/percent amount. + incl_fixed_amount = incl_percent_amount = 0 + for tax in reversed(taxes): + if tax.include_base_amount: + base = recompute_base(base, incl_fixed_amount, incl_percent_amount) + incl_fixed_amount = incl_percent_amount = 0 + if tax.price_include: + if tax.amount_type == 'fixed': + incl_fixed_amount += tax.amount + elif tax.amount_type == 'percent': + incl_percent_amount += tax.amount + # Start the computation of accumulated amounts at the total_excluded value. + total_excluded = total_included = base = recompute_base(base, incl_fixed_amount, incl_percent_amount) - tax_amount = tax._compute_amount(base, price_unit, quantity, product, partner) + # 5) Iterate the taxes in the sequence order to fill missing base/amount values. + # tax | base | amount | + # || ---------------------------- + # || tax_1 | OK | XXXX | + # || tax_2 | XXXX | XXXX | + # || tax_3 | XXXX | XXXX | + # \/ ... | .. | .. | + # ---------------------------- + taxes_vals = [] + for tax in taxes: + # Compute the amount of the tax but don't deal with the price_include because it's already + # took into account on the base amount except for 'division' tax: + # (tax.amount_type == 'percent' && not tax.price_include) + # == (tax.amount_type == 'division' && tax.price_include) + tax_amount = tax.with_context(force_price_include=False)._compute_amount(base, price_unit, quantity, product, partner) if not round_tax: tax_amount = round(tax_amount, prec) else: tax_amount = currency.round(tax_amount) - if tax.price_include: - total_excluded -= tax_amount - base -= tax_amount - else: - total_included += tax_amount - - # Keep base amount used for the current tax + # Suppose: + # seq | amount | incl | incl_base | base | amount + # ----------------------------------------------- + # 1 | 10 % | t | t | 100.0 | 10.0 + # ----------------------------------------------- + # ... the next computation must be done using 100.0 + 10.0 = 110.0 as base but + # the tax base of this tax will be 100.0. tax_base = base - if tax.include_base_amount: base += tax_amount - taxes.append({ + # The total_included amount is computed as the sum of total_excluded with all tax_amount + total_included += tax_amount + + taxes_vals.append({ 'id': tax.id, 'name': tax.with_context(**{'lang': partner.lang} if partner else {}).name, - 'amount': tax_amount, - 'base': tax_base, + 'amount': sign * tax_amount, + 'base': sign * tax_base, 'sequence': tax.sequence, 'account_id': tax.account_id.id, 'refund_account_id': tax.refund_account_id.id, @@ -741,10 +830,10 @@ class AccountTax(models.Model): }) return { - 'taxes': sorted(taxes, key=lambda k: k['sequence']), - 'total_excluded': currency.round(total_excluded) if round_total else total_excluded, - 'total_included': currency.round(total_included) if round_total else total_included, - 'base': base, + 'taxes': taxes_vals, + 'total_excluded': sign * (currency.round(total_excluded) if round_total else total_excluded), + 'total_included': sign * (currency.round(total_included) if round_total else total_included), + 'base': sign * base, } @api.v7 diff --git a/addons/account/tests/test_tax.py b/addons/account/tests/test_tax.py index 02308fa6498..5b6361bf69c 100644 --- a/addons/account/tests/test_tax.py +++ b/addons/account/tests/test_tax.py @@ -1,6 +1,7 @@ +# -*- coding: utf-8 -*- + from openerp.addons.account.tests.account_test_users import AccountTestUsers -from openerp.tests.common import TransactionCase -from openerp.tools import float_compare + import time @@ -67,47 +68,110 @@ class TestTax(AccountTestUsers): self.bank_account = self.bank_journal.default_debit_account_id self.expense_account = self.env['account.account'].search([('user_type_id.type', '=', 'payable')], limit=1) #Should be done by onchange later + def _check_compute_all_results(self, base, total_included, total_excluded, taxes, res): + self.assertAlmostEqual(res['base'], base) + self.assertAlmostEqual(res['total_included'], total_included) + self.assertAlmostEqual(res['total_excluded'], total_excluded) + for i in range(0, len(taxes)): + self.assertAlmostEqual(res['taxes'][i]['base'], taxes[i][0]) + self.assertAlmostEqual(res['taxes'][i]['amount'], taxes[i][1]) + def test_tax_group_of_group_tax(self): self.fixed_tax.include_base_amount = True - self.group_tax.include_base_amount = True - self.group_of_group_tax.include_base_amount = True res = self.group_of_group_tax.compute_all(200.0) - self.assertEquals(res['total_excluded'], 200.0) - # After calculation of first group - # base = 210 - # total_included = 231 - # Base of the first grouped is passed - # Base after the second group (220) is dropped. - # Base of the group of groups is passed out, - # so we obtain base as after first group - self.assertEquals(res['base'], 210.0) - self.assertEquals(res['total_included'], 263.0) + self._check_compute_all_results( + 220, # 'base' + 263, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 10.0), # | 1 | 10 | | t + (210.0, 21.0), # | 3 | 10% | | + (210.0, 10.0), # | 1 | 10 | | t + (220.0, 22.0), # | 3 | 10% | | + # --------------------------------------------------- + ], + res + ) def test_tax_group(self): res = self.group_tax.compute_all(200.0) - self.assertEquals(res['total_excluded'], 200.0) - self.assertEquals(res['total_included'], 230.0) - self.assertEquals(len(res['taxes']), 2) - self.assertEquals(res['taxes'][0]['amount'], 10.0) - self.assertEquals(res['taxes'][1]['amount'], 20.0) + self._check_compute_all_results( + 200, # 'base' + 230, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 10.0), # | 1 | 10 | | + (200.0, 20.0), # | 3 | 10% | | + # --------------------------------------------------- + ], + res + ) def test_tax_percent_division(self): self.division_tax.price_include = True self.division_tax.include_base_amount = True + res_division = self.division_tax.compute_all(200.0) + self._check_compute_all_results( + 220, # 'base' + 220, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 20.0), # | 4 | 10/ | t | t + # --------------------------------------------------- + ], + res_division + ) self.percent_tax.price_include = False self.percent_tax.include_base_amount = False - res_division = self.division_tax.compute_all(200.0) res_percent = self.percent_tax.compute_all(200.0) - self.assertEquals(res_division['taxes'][0]['amount'], 20.0) - self.assertEquals(res_percent['taxes'][0]['amount'], 20.0) + self._check_compute_all_results( + 200, # 'base' + 220, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 20.0), # | 3 | 10% | | + # --------------------------------------------------- + ], + res_percent + ) self.division_tax.price_include = False self.division_tax.include_base_amount = False + res_division = self.division_tax.compute_all(200.0) + self._check_compute_all_results( + 200, # 'base' + 222.22, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 22.22), # | 4 | 10/ | | + # --------------------------------------------------- + ], + res_division + ) self.percent_tax.price_include = True self.percent_tax.include_base_amount = True - res_division = self.division_tax.compute_all(200.0) res_percent = self.percent_tax.compute_all(200.0) - self.assertEquals(res_division['taxes'][0]['amount'], 22.22) - self.assertEquals(res_percent['taxes'][0]['amount'], 18.18) + self._check_compute_all_results( + 200, # 'base' + 200, # 'total_included' + 181.82, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (181.82, 18.18), # | 3 | 10% | t | t + # --------------------------------------------------- + ], + res_percent + ) def test_tax_sequence_normalized_set(self): self.division_tax.sequence = 1 @@ -115,19 +179,53 @@ class TestTax(AccountTestUsers): self.percent_tax.sequence = 3 taxes_set = (self.group_tax | self.division_tax) res = taxes_set.compute_all(200.0) - self.assertEquals(res['taxes'][0]['amount'], 22.22) - self.assertEquals(res['taxes'][1]['amount'], 10.0) - self.assertEquals(res['taxes'][2]['amount'], 20.0) + self._check_compute_all_results( + 200, # 'base' + 252.22, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 22.22), # | 1 | 10/ | | + (200.0, 10.0), # | 2 | 10 | | + (200.0, 20.0), # | 3 | 10% | | + # --------------------------------------------------- + ], + res + ) def test_tax_include_base_amount(self): self.fixed_tax.include_base_amount = True res = self.group_tax.compute_all(200.0) - self.assertEquals(res['total_included'], 231.0) + self._check_compute_all_results( + 210, # 'base' + 231, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 10.0), # | 1 | 10 | | t + (210.0, 21.0), # | 3 | 10% | | + # --------------------------------------------------- + ], + res + ) def test_tax_currency(self): self.division_tax.amount = 15.0 res = self.division_tax.compute_all(200.0, currency=self.env.ref('base.VEF')) - self.assertAlmostEqual(res['total_included'], 235.2941) + self._check_compute_all_results( + 200, # 'base' + 235.2941, # 'total_included' + 200, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (200.0, 35.2941), # | 4 | 15/ | | + # --------------------------------------------------- + ], + res + ) def test_tax_move_lines_creation(self): """ Test that creating a move.line with tax_ids generates the tax move lines and adjust line amount when a tax is price_include """ @@ -172,3 +270,58 @@ class TestTax(AccountTestUsers): aml_with_taxes = move.line_ids.filtered(lambda l: set(l.tax_ids.ids) == set([self.group_tax.id, self.fixed_tax_bis.id])) self.assertEquals(len(aml_with_taxes), 1) self.assertEquals(aml_with_taxes.credit, 190) + + def test_advanced_taxes_computation_0(self): + '''Test more advanced taxes computation (see issue 34471).''' + tax_1 = self.env['account.tax'].create({ + 'name': 'test_advanced_taxes_computation_0_1', + 'amount_type': 'percent', + 'amount': 10, + 'price_include': True, + 'include_base_amount': True, + 'sequence': 1, + }) + tax_2 = self.env['account.tax'].create({ + 'name': 'test_advanced_taxes_computation_0_2', + 'amount_type': 'percent', + 'amount': 10, + 'sequence': 2, + }) + tax_3 = self.env['account.tax'].create({ + 'name': 'test_advanced_taxes_computation_0_3', + 'amount_type': 'percent', + 'amount': 10, + 'price_include': True, + 'sequence': 3, + }) + tax_4 = self.env['account.tax'].create({ + 'name': 'test_advanced_taxes_computation_0_4', + 'amount_type': 'percent', + 'amount': 10, + 'sequence': 4, + }) + tax_5 = self.env['account.tax'].create({ + 'name': 'test_advanced_taxes_computation_0_5', + 'amount_type': 'percent', + 'amount': 10, + 'price_include': True, + 'sequence': 5, + }) + taxes = tax_1 + tax_2 + tax_3 + tax_4 + tax_5 + res = taxes.compute_all(132.0) + self._check_compute_all_results( + 110, # 'base' + 154, # 'total_included' + 100, # 'total_excluded' + [ + # base , amount | seq | amount | incl | incl_base + # --------------------------------------------------- + (100.0, 10.0), # | 1 | 10% | t | t + (110.0, 11.0), # | 3 | 10% | | + (110.0, 11.0), # | 3 | 10% | t | + (110.0, 11.0), # | 3 | 10% | | + (110.0, 11.0), # | 3 | 10% | t | + # --------------------------------------------------- + ], + res + ) diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 814856f4e5a..a18bd707da6 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -202,7 +202,7 @@ exports.PosModel = Backbone.Model.extend({ }, },{ model: 'account.tax', - fields: ['name','amount', 'price_include', 'include_base_amount', 'amount_type', 'children_tax_ids'], + fields: ['name','amount', 'price_include', 'include_base_amount', 'amount_type', 'children_tax_ids', 'sequence'], domain: null, loaded: function(self, taxes){ self.taxes = taxes; @@ -1332,72 +1332,138 @@ exports.Orderline = Backbone.Model.extend({ return tax; }, - _compute_all: function(tax, base_amount, quantity) { + /** + * Mirror JS method of: + * _compute_amount in addons/account/models/account.py + */ + _compute_all: function(tax, base_amount, quantity, price_include) { + if(price_include === undefined) + price_include = tax.price_include; if (tax.amount_type === 'fixed') { var sign_base_amount = base_amount >= 0 ? 1 : -1; return (Math.abs(tax.amount) * sign_base_amount) * quantity; } - if ((tax.amount_type === 'percent' && !tax.price_include) || (tax.amount_type === 'division' && tax.price_include)){ + if ((tax.amount_type === 'percent' && !price_include) || (tax.amount_type === 'division' && tax.price_include)){ return base_amount * tax.amount / 100; } - if (tax.amount_type === 'percent' && tax.price_include){ + if (tax.amount_type === 'percent' && price_include){ return base_amount - (base_amount / (1 + tax.amount / 100)); } - if (tax.amount_type === 'division' && !tax.price_include) { + if (tax.amount_type === 'division' && !price_include) { return base_amount / (1 - tax.amount / 100) - base_amount; } return false; }, + /** + * Mirror JS method of: + * compute_all in addons/account/models/account.py + * + * Read comments in the python side method for more details about each sub-methods. + */ compute_all: function(taxes, price_unit, quantity, currency_rounding, no_map_tax) { var self = this; - var list_taxes = []; + + // 1) Flatten the taxes. + + var _collect_taxes = function(taxes, all_taxes){ + taxes.sort(function (tax1, tax2) { + return tax1.sequence - tax2.sequence; + }); + _(taxes).each(function(tax){ + if(tax.amount_type === 'group') + all_taxes = _collect_taxes(tax.children_tax_ids, all_taxes); + else + all_taxes.push(tax); + }); + return all_taxes; + } + var collect_taxes = function(taxes){ + return _collect_taxes(taxes, []); + } + + 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 + var currency_rounding_bak = currency_rounding; if (this.pos.company.tax_calculation_rounding_method == "round_globally"){ currency_rounding = currency_rounding * 0.00001; } - var total_excluded = round_pr(price_unit * quantity, currency_rounding); - var total_included = total_excluded; - var base = total_excluded; - _(taxes).each(function(tax) { - if (!no_map_tax){ - tax = self._map_tax_fiscal_position(tax); - } - if (!tax){ - return; - } - if (tax.amount_type === 'group'){ - var ret = self.compute_all(tax.children_tax_ids, price_unit, quantity, currency_rounding); - total_excluded = ret.total_excluded; - base = ret.total_excluded; - total_included = ret.total_included; - list_taxes = list_taxes.concat(ret.taxes); - } - else { - var tax_amount = self._compute_all(tax, base, quantity); - tax_amount = round_pr(tax_amount, currency_rounding); - if (tax_amount){ - if (tax.price_include) { - total_excluded -= tax_amount; - base -= tax_amount; - } - else { - total_included += tax_amount; - } - if (tax.include_base_amount) { - base += tax_amount; - } - var data = { - id: tax.id, - amount: tax_amount, - name: tax.name, - }; - list_taxes.push(data); - } + var recompute_base = function(base_amount, fixed_amount, percent_amount){ + if(fixed_amount === 0.0 && percent_amount === 0.0) + return base_amount; + return round_pr((base_amount - fixed_amount) / (1.0 + percent_amount / 100.0), currency_rounding); + } + + // 4) Iterate the taxes in the reversed sequence order to retrieve the initial base of the computation. + + var base = round_pr(price_unit * quantity, currency_rounding); + var sign = 1; + if(base < 0){ + base = -base; + var sign = -1; + } + + var incl_fixed_amount = 0.0; + var incl_percent_amount = 0.0; + _(taxes.reverse()).each(function(tax){ + if(tax.include_base_amount){ + base = recompute_base(base, incl_fixed_amount, incl_percent_amount); + incl_fixed_amount = 0.0; + incl_percent_amount = 0.0; + } + if(tax.price_include){ + if(tax.amount_type === 'fixed') + incl_fixed_amount += tax.amount; + else if(tax.amount_type === 'percent') + incl_percent_amount += tax.amount; } }); + + base = recompute_base(base, incl_fixed_amount, incl_percent_amount); + var total_excluded = base; + var total_included = base; + + // 5) Iterate the taxes in the sequence order to fill missing base/amount values. + + var taxes_vals = []; + _(taxes.reverse()).each(function(tax){ + var tax_amount = self._compute_all(tax, base, quantity, false); + tax_amount = round_pr(tax_amount, currency_rounding); + + var tax_base = base; + + if(tax.include_base_amount) + base += tax_amount; + + total_included += tax_amount; + + var tax_vals = { + id: tax.id, + amount: sign * tax_amount, + name: tax.name, + }; + + taxes_vals.push(tax_vals); + }); + return { - taxes: list_taxes, + taxes: taxes_vals, total_excluded: round_pr(total_excluded, currency_rounding_bak), total_included: round_pr(total_included, currency_rounding_bak) }; From 296498fb588614b8ce64da47954faac71eef1751 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Mon, 16 Oct 2017 11:11:18 +0200 Subject: [PATCH 4/8] [FIX] web_editor: do not break

when pasting a

element This fixes a bug when pasting text in a paragraph, it was always creating a new paragraph for no reason. --- .../static/lib/summernote/src/js/module/Clipboard.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js b/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js index 8b42726ff7a..955b49437a8 100644 --- a/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js +++ b/addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js @@ -206,6 +206,18 @@ define([ r.select(); } + // If only pasting a

element in an unique

element, only paste + // the

element text + var $p = $arch.children('p'); + var onlyAP = ($p.length === 1 && $arch.children().length === 1); + if (onlyAP) { + var $p1 = $(r.sc).closest('p'); + var $p2 = $(r.ec).closest('p'); + if ($p1.length && $p2.length && $p1[0] === $p2[0]) { + $arch.html($p.text()); + } + } + /* insert content */ From eda976e63dcad01f4b65755f1e20fe695ba16a8c Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Mon, 16 Oct 2017 15:15:08 +0200 Subject: [PATCH 5/8] [FIX] web_editor: do not break labels' texts into

When editing the content of a

element was added inside. This behavior is there to automatically add

elements in empty editable

/
elements on edition. This should however not apply on