diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 5a7ad534da7..775f9fd9a66 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -682,6 +682,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 @@ -695,11 +696,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.multi @@ -733,13 +734,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 @@ -764,50 +810,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, @@ -815,10 +904,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.model diff --git a/addons/account/models/account_journal_dashboard.py b/addons/account/models/account_journal_dashboard.py index 3b44fa44eb9..431cdca66c7 100644 --- a/addons/account/models/account_journal_dashboard.py +++ b/addons/account/models/account_journal_dashboard.py @@ -162,7 +162,7 @@ class account_journal(models.Model): # optimization to read sum of balance from account_move_line account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id])) if account_ids: - amount_field = 'balance' if not self.currency_id else 'amount_currency' + amount_field = 'balance' if (not self.currency_id or self.currency_id == self.company_id.currency_id) else 'amount_currency' query = """SELECT sum(%s) FROM account_move_line WHERE account_id in %%s AND date <= %%s;""" % (amount_field,) self.env.cr.execute(query, (account_ids, fields.Date.today(),)) query_results = self.env.cr.dictfetchall() diff --git a/addons/account/tests/test_tax.py b/addons/account/tests/test_tax.py index 84aa784fad3..5ea51d3077d 100644 --- a/addons/account/tests/test_tax.py +++ b/addons/account/tests/test_tax.py @@ -1,6 +1,7 @@ +# -*- coding: utf-8 -*- + from odoo.addons.account.tests.account_test_users import AccountTestUsers -from odoo.tests.common import TransactionCase -from odoo.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/mass_mailing/static/src/js/mass_mailing_editor.js b/addons/mass_mailing/static/src/js/mass_mailing_editor.js index 5d475808589..cc0b9d2dd08 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_editor.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_editor.js @@ -402,4 +402,15 @@ odoo_top[callback+"_updown"] = function (value, fields_values, field_name) { if ($editable_area.html().indexOf('on_change_model_and_list') !== -1) { $editable_area.empty(); } +// Adding compatibility for the outlook compliance of mailings. +// Commit of such compatibility : a14f89c8663c9cafecb1cc26918055e023ecbe42 +options.registry.background.include({ + start: function() { + this._super(); + var $table_target = this.$target.find('table:first'); + if ($table_target) { + this.$target = $table_target; + } + } +}); }); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 336e3b32848..27281342cde 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; @@ -1400,72 +1400,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) }; 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 */ diff --git a/addons/web_editor/static/src/js/rte.js b/addons/web_editor/static/src/js/rte.js index b0366583aea..c7dc268251d 100644 --- a/addons/web_editor/static/src/js/rte.js +++ b/addons/web_editor/static/src/js/rte.js @@ -429,7 +429,7 @@ var RTE = Widget.extend({ saveElement: function ($el, context) { // remove multi edition if ($el.data('oe-model')) { - var key = $el.data('oe-model')+":"+$el.data('oe-id')+":"+$el.data('oe-field')+":"+$el.data('oe-type')+":"+$el.data('oe-expression'); + var key = $el.data('oe-model')+":"+$el.data('oe-id')+":"+$el.data('oe-field')+":"+$el.data('oe-type')+":"+$el.data('oe-expression')+":"+$el.data('oe-xpath'); if (this.__saved[key]) return $.when(); this.__saved[key] = true; } diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js index 4ab1e7bed06..a3bf079aa30 100644 --- a/addons/web_editor/static/src/js/rte.summernote.js +++ b/addons/web_editor/static/src/js/rte.summernote.js @@ -454,7 +454,7 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) { if (($node.is('[data-oe-type="html"]') || $node.is('[data-oe-field="arch"]')) && $node.hasClass("o_editable") && !$node[0].children.length && - "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small pre th td".toUpperCase().indexOf($node[0].nodeName) === -1) { + "h1 h2 h3 h4 h5 h6 p b bold i u code sup strong small pre th td label".toUpperCase().indexOf($node[0].nodeName) === -1) { var p = $('


')[0]; $node.append( p ); range.createFromNode(p.firstChild).select(); diff --git a/addons/website_forum/views/forum.xml b/addons/website_forum/views/forum.xml index 76e269cd105..f236790b8ad 100644 --- a/addons/website_forum/views/forum.xml +++ b/addons/website_forum/views/forum.xml @@ -69,6 +69,7 @@ + diff --git a/odoo/tools/translate.py b/odoo/tools/translate.py index ebc3dd616f7..454d6459219 100644 --- a/odoo/tools/translate.py +++ b/odoo/tools/translate.py @@ -935,7 +935,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