summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2017-10-17 11:42:05 +0200
committerChristophe Simonis <chs@odoo.com>2017-10-17 11:42:05 +0200
commitcbca27d0052430743fe2c996964d7aca0ca682c9 (patch)
tree09a4e7e6a8e1c0cde24d464e595b786e3ea247ad
parent25725eae578d126f79c69f72a7a4e3b4136b4e5d (diff)
parentd5c0cae60b5cf7834ca9d669f9408a7c55694ff9 (diff)
[MERGE] forward port branch 9.0 up to d5c0cae60b5
-rw-r--r--addons/account/models/account.py169
-rw-r--r--addons/account/models/account_journal_dashboard.py2
-rw-r--r--addons/account/tests/test_tax.py213
-rw-r--r--addons/mass_mailing/static/src/js/mass_mailing_editor.js11
-rw-r--r--addons/point_of_sale/static/src/js/models.js156
-rw-r--r--addons/web_editor/static/lib/summernote/src/js/module/Clipboard.js12
-rw-r--r--addons/web_editor/static/src/js/rte.js2
-rw-r--r--addons/web_editor/static/src/js/rte.summernote.js2
-rw-r--r--addons/website_forum/views/forum.xml1
-rw-r--r--odoo/tools/translate.py3
10 files changed, 452 insertions, 119 deletions
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
-
- # 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
-
- tax_amount = tax._compute_amount(base, price_unit, quantity, product, partner)
+ sign = 1
+
+ # 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)
+
+ # 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);
+
+ 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;
}
- 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);
- }
+ 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 <p/> element in an unique <p/> element, only paste
+ // the <p/> 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 = $('<p><br/></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 @@
<group>
<group>
<field name="karma_ask"/>
+ <field name="karma_answer"/>
<field name="karma_upvote"/>
<field name="karma_downvote"/>
<field name="karma_edit_own"/>
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