diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index 6ad1d83021c..979e17690a8 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -700,7 +700,7 @@ class AccountInvoice(models.Model):
total_currency = 0
for line in invoice_move_lines:
if self.currency_id != company_currency:
- currency = self.currency_id.with_context(date=self.date or self.date_invoice or fields.Date.context_today(self))
+ currency = self.currency_id.with_context(date=self._get_currency_rate_date() or fields.Date.context_today(self))
if not (line.get('currency_id') and line.get('amount_currency')):
line['currency_id'] = currency.id
line['amount_currency'] = currency.round(line['price'])
@@ -848,7 +848,7 @@ class AccountInvoice(models.Model):
if inv.payment_term_id:
totlines = inv.with_context(ctx).payment_term_id.with_context(currency_id=company_currency.id).compute(total, inv.date_invoice)[0]
res_amount_currency = total_currency
- ctx['date'] = inv.date or inv.date_invoice
+ ctx['date'] = inv._get_currency_rate_date()
for i, t in enumerate(totlines):
if inv.currency_id != company_currency:
amount_currency = company_currency.with_context(ctx).compute(t[1], inv.currency_id)
@@ -1035,6 +1035,9 @@ class AccountInvoice(models.Model):
copy_fields = ['company_id', 'user_id', 'fiscal_position_id']
return self._get_refund_common_fields() + self._get_refund_prepare_fields() + copy_fields
+ def _get_currency_rate_date(self):
+ return self.date or self.date_invoice
+
@api.model
def _prepare_refund(self, invoice, date_invoice=None, date=None, description=None, journal_id=None):
""" Prepare the dict of values to create the new refund from the invoice.
@@ -1194,7 +1197,7 @@ class AccountInvoiceLine(models.Model):
@api.one
@api.depends('price_unit', 'discount', 'invoice_line_tax_ids', 'quantity',
'product_id', 'invoice_id.partner_id', 'invoice_id.currency_id', 'invoice_id.company_id',
- 'invoice_id.date_invoice')
+ 'invoice_id.date_invoice', 'invoice_id.date')
def _compute_price(self):
currency = self.invoice_id and self.invoice_id.currency_id or None
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
@@ -1203,7 +1206,7 @@ class AccountInvoiceLine(models.Model):
taxes = self.invoice_line_tax_ids.compute_all(price, currency, self.quantity, product=self.product_id, partner=self.invoice_id.partner_id)
self.price_subtotal = price_subtotal_signed = taxes['total_excluded'] if taxes else self.quantity * price
if self.invoice_id.currency_id and self.invoice_id.company_id and self.invoice_id.currency_id != self.invoice_id.company_id.currency_id:
- price_subtotal_signed = self.invoice_id.currency_id.with_context(date=self.invoice_id.date_invoice).compute(price_subtotal_signed, self.invoice_id.company_id.currency_id)
+ price_subtotal_signed = self.invoice_id.currency_id.with_context(date=self.invoice_id._get_currency_rate_date()).compute(price_subtotal_signed, self.invoice_id.company_id.currency_id)
sign = self.invoice_id.type in ['in_refund', 'out_refund'] and -1 or 1
self.price_subtotal_signed = price_subtotal_signed * sign
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 79c539dcacf..f4d5b75cd86 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -165,6 +165,8 @@ class AccountMove(models.Model):
if not move.journal_id.update_posted:
raise UserError(_('You cannot modify a posted entry of this journal.\nFirst you should set the journal to allow cancelling entries.'))
if self.ids:
+ self.check_access_rights('write')
+ self.check_access_rule('write')
self._check_lock_date()
self._cr.execute('UPDATE account_move '\
'SET state=%s '\
diff --git a/addons/account/views/report_overdue.xml b/addons/account/views/report_overdue.xml
index 3b7d3808100..a413e77d0d3 100644
--- a/addons/account/views/report_overdue.xml
+++ b/addons/account/views/report_overdue.xml
@@ -16,7 +16,7 @@
-
+
diff --git a/addons/account_budget/views/account_budget_views.xml b/addons/account_budget/views/account_budget_views.xml
index 21635db40a4..f94b29bfd43 100644
--- a/addons/account_budget/views/account_budget_views.xml
+++ b/addons/account_budget/views/account_budget_views.xml
@@ -96,7 +96,7 @@
-
+
@@ -110,7 +110,7 @@
-
+
diff --git a/addons/base_import/models/base_import.py b/addons/base_import/models/base_import.py
index 0be10bf6da8..13ce712bb1b 100644
--- a/addons/base_import/models/base_import.py
+++ b/addons/base_import/models/base_import.py
@@ -586,7 +586,7 @@ class Import(models.TransientModel):
# Check that currency exists
currency = self.env['res.currency'].search([('symbol', '=', split_value[currency_index].strip())])
if len(currency):
- return split_value[currency_index + 1 % 2] if not negative else '-' + split_value[currency_index + 1 % 2]
+ return split_value[(currency_index + 1) % 2] if not negative else '-' + split_value[(currency_index + 1) % 2]
# Otherwise it is not a float with a currency symbol
return False
diff --git a/addons/hr_expense/models/account_move_line.py b/addons/hr_expense/models/account_move_line.py
index d9b496f35d7..64baae5a09c 100644
--- a/addons/hr_expense/models/account_move_line.py
+++ b/addons/hr_expense/models/account_move_line.py
@@ -13,6 +13,8 @@ class AccountMoveLine(models.Model):
res = super(AccountMoveLine, self).reconcile(writeoff_acc_id=writeoff_acc_id, writeoff_journal_id=writeoff_journal_id)
account_move_ids = [l.move_id.id for l in self if float_compare(l.move_id.matched_percentage, 1, precision_digits=5) == 0]
if account_move_ids:
- expense_sheets = self.env['hr.expense.sheet'].search([('account_move_id', 'in', account_move_ids)])
+ expense_sheets = self.env['hr.expense.sheet'].search([
+ ('account_move_id', 'in', account_move_ids), ('state', '!=', 'done')
+ ])
expense_sheets.set_to_paid()
return res
diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py
index a584b6f9795..7878bc47b3e 100644
--- a/addons/hr_expense/models/hr_expense.py
+++ b/addons/hr_expense/models/hr_expense.py
@@ -417,18 +417,20 @@ class HrExpenseSheet(models.Model):
@api.multi
def check_consistency(self):
- if any(sheet.employee_id != self[0].employee_id for sheet in self):
- raise UserError(_("Expenses must belong to the same Employee."))
-
- expense_lines = self.mapped('expense_line_ids')
- if expense_lines and any(expense.payment_mode != expense_lines[0].payment_mode for expense in expense_lines):
- raise UserError(_("Expenses must have been paid by the same entity (Company or employee)"))
+ for rec in self:
+ expense_lines = rec.expense_line_ids
+ if not expense_lines:
+ continue
+ if any(expense.employee_id != rec.employee_id for expense in expense_lines):
+ raise UserError(_("Expenses must belong to the same Employee."))
+ if any(expense.payment_mode != expense_lines[0].payment_mode for expense in expense_lines):
+ raise UserError(_("Expenses must have been paid by the same entity (Company or employee)"))
@api.model
def create(self, vals):
self._create_set_followers(vals)
sheet = super(HrExpenseSheet, self).create(vals)
- self.check_consistency()
+ sheet.check_consistency()
return sheet
@api.multi
@@ -588,7 +590,7 @@ class HrExpenseSheet(models.Model):
@api.one
- @api.constrains('expense_line_ids')
+ @api.constrains('expense_line_ids', 'employee_id')
def _check_employee(self):
employee_ids = self.expense_line_ids.mapped('employee_id')
if len(employee_ids) > 1 or (len(employee_ids) == 1 and employee_ids != self.employee_id):
diff --git a/addons/l10n_ch/data/account_fiscal_position_data.xml b/addons/l10n_ch/data/account_fiscal_position_data.xml
index 869ee872407..85d08ad18cb 100644
--- a/addons/l10n_ch/data/account_fiscal_position_data.xml
+++ b/addons/l10n_ch/data/account_fiscal_position_data.xml
@@ -19,6 +19,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -29,6 +39,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -46,13 +66,21 @@
-
+
+
+
+
+
-
+
+
+
+
+
diff --git a/addons/l10n_ch/data/account_vat2011_data.xml b/addons/l10n_ch/data/account_vat2011_data.xml
index 1ce2068f2fb..3eaca4c5371 100644
--- a/addons/l10n_ch/data/account_vat2011_data.xml
+++ b/addons/l10n_ch/data/account_vat2011_data.xml
@@ -32,6 +32,10 @@
Switzerland VAT Form: grid 301 basetaxes
+
+ Switzerland VAT Form: grid 302 base
+ taxes
+ Switzerland VAT Form: grid 311 basetaxes
@@ -40,6 +44,10 @@
Switzerland VAT Form: grid 341 basetaxes
+
+ Switzerland VAT Form: grid 342 base
+ taxes
+ Switzerland VAT Form: grid 381 basetaxes
@@ -48,6 +56,10 @@
Switzerland VAT Form: grid 301 taxtaxes
+
+ Switzerland VAT Form: grid 302 tax
+ taxes
+ Switzerland VAT Form: grid 311 taxtaxes
@@ -56,6 +68,10 @@
Switzerland VAT Form: grid 341 taxtaxes
+
+ Switzerland VAT Form: grid 342 tax
+ taxes
+ Switzerland VAT Form: grid 381 taxtaxes
@@ -160,6 +176,75 @@
+
+ TVA due a 3.7% (TS)
+ 3.7%
+
+ percent
+
+ sale
+
+
+
+
+
+ TVA due à 3.7% (Incl. TS)
+ 3.7% Incl.
+
+
+ percent
+
+ sale
+
+
+
+
+
+ TVA 3.7% sur achat B&S (TS)
+ 3.7% achat
+
+ percent
+
+ purchase
+
+
+
+
+
+ TVA 3.7% sur achat B&S (Incl. TS)
+ 3.7% achat Incl.
+
+
+ percent
+
+ purchase
+
+
+
+
+
+ TVA 3.7% sur invest. et autres ch. (TS)
+ 3.7% invest
+
+ percent
+
+ purchase
+
+
+
+
+
+ TVA 3.7% sur invest. et autres ch. (Incl. TS)
+ 3.7% invest Incl.
+
+
+ percent
+
+ purchase
+
+
+
+ TVA due a 3.8% (TS)3.8%
@@ -229,6 +314,80 @@
+
+
+ TVA due a 7.7% (TN)
+ 7.7%
+
+
+ percent
+
+ sale
+
+
+
+
+
+ TVA due à 7.7% (Incl. TN)
+ 7.7% Incl.
+
+
+
+ percent
+
+ sale
+
+
+
+
+
+ TVA 7.7% sur achat B&S (TN)
+ 7.7% achat
+
+ percent
+
+
+ purchase
+
+
+
+
+
+ TVA 7.7% sur achat B&S (Incl. TN)
+ 7.7% achat Incl.
+
+
+ percent
+
+
+ purchase
+
+
+
+
+
+ TVA 7.7% sur invest. et autres ch. (TN)
+ 7.7% invest.
+
+ percent
+
+ purchase
+
+
+
+
+
+ TVA 7.7% sur invest. et autres ch. (Incl. TN)
+ 7.7% invest. Incl.
+
+
+ percent
+
+ purchase
+
+
+
+ TVA due a 8.0% (TN)8.0%
diff --git a/addons/l10n_fr_pos_cert/models/pos.py b/addons/l10n_fr_pos_cert/models/pos.py
index d6d88604e8c..9ce9694e979 100644
--- a/addons/l10n_fr_pos_cert/models/pos.py
+++ b/addons/l10n_fr_pos_cert/models/pos.py
@@ -1,4 +1,4 @@
-from datetime import datetime
+from datetime import datetime, timedelta
from hashlib import sha256
from json import dumps
import pytz
@@ -41,7 +41,7 @@ class pos_session(models.Model):
self.ensure_one()
date_today = datetime.utcnow()
session_start = Datetime.from_string(self.start_at)
- if not date_today - datetime.timedelta(hours=24) <= session_start:
+ if not date_today - timedelta(hours=24) <= session_start:
raise UserError(_("This session has been opened another day. To comply with the French law, you should close sessions on a daily basis. Please close session %s and open a new one.") % self.name)
return True
diff --git a/addons/l10n_in_hr_payroll/data/l10n_in_hr_payroll_data.xml b/addons/l10n_in_hr_payroll/data/l10n_in_hr_payroll_data.xml
index 17ec10ad01d..948039f1545 100644
--- a/addons/l10n_in_hr_payroll/data/l10n_in_hr_payroll_data.xml
+++ b/addons/l10n_in_hr_payroll/data/l10n_in_hr_payroll_data.xml
@@ -160,7 +160,7 @@ or (contract.wage * 0.10))Arrearscode
- result = inputs.ARS.amount
+ result = inputs.ARS and inputs.ARS.amountGenerally arrears are fully taxable, but employee may claim exemption u/s 89(1).
@@ -185,7 +185,7 @@ Now difference of income tax between payment year and actual year would be allow
nonecode
- result = inputs.LE.amount
+ result = inputs.LE and inputs.LE.amountPayment by way of leave encashment received by Central and State Govt.employees at the time of retirement in respect of the period of earned leave at credit is fully exempt. In case of other employees, the exemption is to be limited to minimum of all below:
1.The actual amount received
@@ -199,7 +199,7 @@ Now difference of income tax between payment year and actual year would be allow
Performance Incentivecode
- result = inputs.PERF.amount
+ result = inputs.PERF and inputs.PERF.amountThis would be fully taxable based on incentive.
@@ -209,7 +209,7 @@ Now difference of income tax between payment year and actual year would be allow
Bonuscode
- result = inputs.BNS.amount
+ result = inputs.BNS and inputs.BNS.amountThis would be fully taxable based on Bonus.
@@ -229,7 +229,7 @@ Now difference of income tax between payment year and actual year would be allow
Medical Reimbursementcode
- result = inputs.MR.amount
+ result = inputs.MR and inputs.MR.amountThis component is on-taxable up to 15000 per year (or Rs 1250 per month) on producing medical bills.
@@ -250,7 +250,7 @@ Now difference of income tax between payment year and actual year would be allow
Periodical Journalscode
- result = inputs.PJ.amount
+ result = inputs.PJ and inputs.PJ.amountSome employers may provide component for buying magazines, journals and books as a part of knowledge enhancement for business growth.This part would become non taxable on providing original bills.
@@ -280,7 +280,7 @@ Now difference of income tax between payment year and actual year would be allow
Telephone Reimbursementcode
- result = inputs.TR.amount
+ result = inputs.TR and inputs.TR.amountIn some of the cases, companies may provide a component for telephone bills.Employees may provide actual phone usage bills to reimburse this component and make it non-taxable.
@@ -300,7 +300,7 @@ Now difference of income tax between payment year and actual year would be allow
Car Expenses Reimbursementcode
- result = inputs.CEI.amount
+ result = inputs.CEI and inputs.CEI.amountIn case company provides component for this and employee use self owned car for official and personal purposes, Rs 1800 per month would be non-taxable on showing bills for fuel or can maintenance. This amount would be Rs 2400 in case car is more capacity than 1600cc.
@@ -310,7 +310,7 @@ Now difference of income tax between payment year and actual year would be allow
Internet Expensecode
- result = inputs.IE.amount
+ result = inputs.IE and inputs.IE.amountEmployer may also provide reimbursement of internet expenses and thus this would become non taxable.
@@ -463,7 +463,7 @@ or - (contract.wage * contract.voluntary_provident_fund / 100))nonecode
- result = - (inputs.LAI.amount)
+ result = inputs.LAI and - (inputs.LAI.amount)
diff --git a/addons/l10n_lu/data/account.account.tag.csv b/addons/l10n_lu/data/account.account.tag.csv
index 93c9c280973..f25178a068e 100644
--- a/addons/l10n_lu/data/account.account.tag.csv
+++ b/addons/l10n_lu/data/account.account.tag.csv
@@ -179,20 +179,17 @@ tag_lu_177,I.B.5. Tabacs fabriqués,taxes
tag_lu_178,Base - Vente Biens Tabacs - Extracommunautaire,taxes
tag_lu_179,Base - Vente Biens Tabacs - Intracommunautaire,taxes
tag_lu_180,Base - Vente Biens Tabacs - Pays,taxes
-tag_lu_181,I.B.6.a) Livraisons subséquentes à des acquisitions intracommunautaires dans
-le cadre d'opérations triangulaires, lorsque le destinataire identifié à la TVA dans l'Etat membre de destination des biens y est le redevable de la taxe (4),taxes
+tag_lu_181,"I.B.6.a) Livraisons subséquentes à des acquisitions intracommunautaires dans le cadre d'opérations triangulaires, lorsque le destinataire identifié à la TVA dans l'Etat membre de destination des biens y est le redevable de la taxe (4)",taxes
tag_lu_182,Base - Vente Biens – Triangulaire Intracommunautaire,taxes
tag_lu_183,I.B.6.d) Autres opérations réalisées (imposables) à l'étranger,taxes
tag_lu_184,Base - Vente Prestations - Extracommunautaire,taxes
tag_lu_185,I.B.6.c) Opérations réalisées dans le cadre du régime particulier de l'article 56sexies,taxes
tag_lu_186,I.B.7. Opérations à l'intérieur du pays pour lesquelles le preneur est le redevable,taxes
-tag_lu_187,I.B.6.b)1) Prestations de services à des identifiés à la TVA dans un autre
-Etat membre non exonérées dans l'Etat membre du preneur redevable (art.17/1/b) (5),taxes
+tag_lu_187,I.B.6.b)1) Prestations de services à des identifiés à la TVA dans un autre Etat membre non exonérées dans l'Etat membre du preneur redevable (art.17/1/b) (5),taxes
tag_lu_188,Base - Vente Prestations - Intracommunautaire,taxes
-tag_lu_189,I.B.6.b)2) Prestations de services à des identifiés à la TVA dans un autre
-Etat membre exonérées dans l'Etat membre du preneur (art.17/1/b),taxes
+tag_lu_189,I.B.6.b)2) Prestations de services à des identifiés à la TVA dans un autre Etat membre exonérées dans l'Etat membre du preneur (art.17/1/b),taxes
tag_lu_190,Base - Vente Prestations - Intracommunautaire exonérées dans l'état membre,taxes
-tag_lu_191,I.B.1. Livraisons intracommunautaires de biens à des personnes identifiées à la TVA dans unautre Etat membre (art.43/1/d e et f) (3),taxes
+tag_lu_191,I.B.1. Livraisons intracommunautaires de biens à des personnes identifiées à la TVA dans un autre Etat membre (art.43/1/d e et f) (3),taxes
tag_lu_192,Base - Vente Biens - Intracommunautaire,taxes
tag_lu_193,I.C. Chiffre d'affaires imposable,taxes
tag_lu_194,II.A. Ventilation du chiffre d'affaires imposable – base,taxes
@@ -598,4 +595,4 @@ tag_lu_594,Taxe - Investissement Prestations 17% - Intracommunautaire (-),taxes
tag_lu_595,Taxe - Investissement Prestations 3% - Intracommunautaire (-),taxes
tag_lu_596,Taxe - Investissement Prestations 6% - Intracommunautaire (-),taxes
tag_lu_597,Taxe - Investissement Prestations 8% - Intracommunautaire (-),taxes
-tag_lu_598,Base - Sans Taxe,taxes
\ No newline at end of file
+tag_lu_598,Base - Sans Taxe,taxes
diff --git a/addons/mail/models/__init__.py b/addons/mail/models/__init__.py
index 486b687eeb2..43caa97b6f9 100644
--- a/addons/mail/models/__init__.py
+++ b/addons/mail/models/__init__.py
@@ -18,4 +18,5 @@ import res_config
import update
import ir_actions
import ir_autovacuum
+import ir_model
import html2text
diff --git a/addons/mail/models/ir_model.py b/addons/mail/models/ir_model.py
new file mode 100644
index 00000000000..314ab79b29d
--- /dev/null
+++ b/addons/mail/models/ir_model.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class IrModel(models.Model):
+ _inherit = 'ir.model'
+
+ def unlink(self):
+ # Delete followers for models that will be unlinked.
+ query = "DELETE FROM mail_followers WHERE res_model IN %s"
+ self.env.cr.execute(query, [tuple(self.mapped('model'))])
+ return super(IrModel, self).unlink()
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 4d155ff169f..59aebe0f4b6 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -2130,7 +2130,7 @@ class MailThread(models.AbstractModel):
# add followers coming from res.users relational fields that are tracked
user_ids = [values[name] for name in user_field_lst if values.get(name)]
- user_pids = [user.partner_id.id for user in self.env['res.users'].sudo().browse(user_ids)]
+ user_pids = [user.partner_id.id for user in self.env['res.users'].sudo().browse(user_ids) if user.partner_id.active]
for partner_id in user_pids:
new_partners.setdefault(partner_id, None)
diff --git a/addons/mrp/views/mrp_workcenter_views.xml b/addons/mrp/views/mrp_workcenter_views.xml
index 3f6d50138a2..a7024745eb2 100644
--- a/addons/mrp/views/mrp_workcenter_views.xml
+++ b/addons/mrp/views/mrp_workcenter_views.xml
@@ -103,9 +103,11 @@
Work Orders Performanceir.actions.act_windowmrp.workorder
- [('state','=','done')]
+ []
+ {'search_default_done': True}formgraph,pivot,tree,form,gantt
+
@@ -567,4 +569,4 @@
groups="group_mrp_routings"
sequence="12"/>
-
\ No newline at end of file
+
diff --git a/addons/mrp/views/mrp_workorder_views.xml b/addons/mrp/views/mrp_workorder_views.xml
index 6f13fdcc485..5e89bed4fa0 100644
--- a/addons/mrp/views/mrp_workorder_views.xml
+++ b/addons/mrp/views/mrp_workorder_views.xml
@@ -201,7 +201,7 @@
-
+
@@ -459,4 +459,4 @@
-
\ No newline at end of file
+
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index c5cf1c77e5c..0f8dbdb801e 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -203,7 +203,7 @@ exports.PosModel = Backbone.Model.extend({
},{
model: 'account.tax',
fields: ['name','amount', 'price_include', 'include_base_amount', 'amount_type', 'children_tax_ids'],
- domain: null,
+ domain: function(self) {return [['company_id', '=', self.company && self.company.id || false]]},
loaded: function(self, taxes){
self.taxes = taxes;
self.taxes_by_id = {};
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 1dcbf9d90fb..4b6b142cac3 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1977,6 +1977,7 @@ var PaymentScreenWidget = ScreenWidget.extend({
}
order.initialize_validation_date();
+ order.finalized = true;
if (order.is_to_invoice()) {
var invoiced = this.pos.push_and_invoice_order(order);
diff --git a/addons/product/models/product_pricelist.py b/addons/product/models/product_pricelist.py
index fd2bfdb9dec..d44c2ba0ebc 100644
--- a/addons/product/models/product_pricelist.py
+++ b/addons/product/models/product_pricelist.py
@@ -106,7 +106,7 @@ class Pricelist(models.Model):
"""
self.ensure_one()
if not date:
- date = self._context.get('date') or fields.Date.today()
+ date = self._context.get('date') or fields.Date.context_today(self)
if not uom_id and self._context.get('uom'):
uom_id = self._context['uom']
if uom_id:
diff --git a/addons/sale_mrp/sale_mrp.py b/addons/sale_mrp/sale_mrp.py
index 076b7d1d4e8..9d17b132c1b 100644
--- a/addons/sale_mrp/sale_mrp.py
+++ b/addons/sale_mrp/sale_mrp.py
@@ -112,7 +112,7 @@ class AccountInvoiceLine(models.Model):
prod_moves = [m for m in moves if m.product_id.id == product_id]
prod_qty_done = factor * qty_done
prod_quantity = factor * quantity
- average_price_unit += self._compute_average_price(prod_qty_done, prod_quantity, prod_moves)
+ average_price_unit += factor * self._compute_average_price(prod_qty_done, prod_quantity, prod_moves)
price_unit = average_price_unit or price_unit
price_unit = self.product_id.uom_id._compute_price(price_unit, self.uom_id)
return price_unit
diff --git a/addons/stock_account/report/stock_history.py b/addons/stock_account/report/stock_history.py
index 4d5d4045dfb..94f2c5f36a3 100644
--- a/addons/stock_account/report/stock_history.py
+++ b/addons/stock_account/report/stock_history.py
@@ -35,8 +35,17 @@ class StockHistory(models.Model):
group_lines.setdefault(str(domain), self.search(domain))
stock_history |= group_lines[str(domain)]
+ # get data of stock_history in one shot to speed things up (the view can be very slow)
+ stock_history_data = {}
+ if stock_history:
+ self._cr.execute("""SELECT id, product_id, price_unit_on_quant, company_id, quantity
+ FROM stock_history WHERE id in %s""", (tuple(stock_history.ids),))
+ stock_history_data = {line['id']: line for line in self._cr.dictfetchall()}
+
histories_dict = {}
- not_real_cost_method_products = stock_history.mapped('product_id').filtered(lambda product: product.cost_method != 'real')
+ not_real_cost_method_products = self.env['product.product'].browse(
+ record['product_id'] for record in stock_history_data.values()
+ ).filtered(lambda product: product.cost_method != 'real')
if not_real_cost_method_products:
self._cr.execute("""SELECT DISTINCT ON (product_id, company_id) product_id, company_id, cost
FROM product_price_history
@@ -48,12 +57,13 @@ class StockHistory(models.Model):
for line in res:
inv_value = 0.0
for stock_history in group_lines.get(str(line.get('__domain', domain))):
- product = stock_history.product_id
- if product.cost_method == 'real':
- price = stock_history.price_unit_on_quant
+ history_data = stock_history_data[stock_history.id]
+ product_id = history_data['product_id']
+ if self.env['product.product'].browse(product_id).cost_method == 'real':
+ price = history_data['price_unit_on_quant']
else:
- price = histories_dict.get((product.id, stock_history.company_id.id), 0.0)
- inv_value += price * stock_history.quantity
+ price = histories_dict.get((product_id, history_data['company_id']), 0.0)
+ inv_value += price * history_data['quantity']
line['inventory_value'] = inv_value
return res
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index ef2f9d444b9..ec55c8eb8a6 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -586,7 +586,7 @@ class WebClient(http.Controller):
def version_info(self):
return odoo.service.common.exp_version()
- @http.route('/web/tests', type='http', auth="none")
+ @http.route('/web/tests', type='http', auth="user")
def index(self, mod=None, **kwargs):
return request.render('web.qunit_suite')
diff --git a/addons/web/static/src/js/action_manager.js b/addons/web/static/src/js/action_manager.js
index 974bfb982d7..a31b0900863 100644
--- a/addons/web/static/src/js/action_manager.js
+++ b/addons/web/static/src/js/action_manager.js
@@ -385,6 +385,10 @@ var ActionManager = Widget.extend({
return this.inner_widget;
},
history_back: function() {
+ if (this.dialog) {
+ this.dialog_stop();
+ return;
+ }
var nb_views = this.inner_action.get_nb_views();
if (nb_views > 1) {
// Stay on this action, but select the previous view
@@ -861,7 +865,7 @@ var ActionManager = Widget.extend({
});
});
},
- ir_actions_act_url: function (action) {
+ ir_actions_act_url: function (action, options) {
var url = action.url;
if (session.debug && url && url.length && url[0] === '/') {
url = $.param.querystring(url, {debug: session.debug});
@@ -872,6 +876,7 @@ var ActionManager = Widget.extend({
return $.Deferred(); // The action is finished only when the redirection is done
} else {
window.open(url, '_blank');
+ options.on_close();
}
return $.when();
},
diff --git a/addons/web/static/src/js/framework/qweb.js b/addons/web/static/src/js/framework/qweb.js
index 50742075e05..253e948d8ac 100644
--- a/addons/web/static/src/js/framework/qweb.js
+++ b/addons/web/static/src/js/framework/qweb.js
@@ -5,7 +5,18 @@ var translation = require('web.translation');
var _t = translation._t;
-function QWeb(debug, default_dict) {
+/**
+ * @param {boolean} debug
+ * @param {Object} default_dict
+ * @param {boolean} [enableTranslation=true] if true (this is the default),
+ * the rendering will translate all strings that are not marked with
+ * t-translation=off. This is useful for the kanban view, which uses a
+ * template which is already translated by the server
+ */
+function QWeb(debug, default_dict, enableTranslation) {
+ if (enableTranslation === undefined) {
+ enableTranslation = true;
+ }
var qweb = new QWeb2.Engine();
qweb.default_dict = _.extend({}, default_dict || {}, {
'_' : _,
@@ -16,7 +27,7 @@ function QWeb(debug, default_dict) {
'csrf_token': odoo.csrf_token,
});
qweb.debug = debug;
- qweb.preprocess_node = preprocess_node;
+ qweb.preprocess_node = enableTranslation ? preprocess_node : function () {};
return qweb;
}
diff --git a/addons/web_editor/static/src/js/base.js b/addons/web_editor/static/src/js/base.js
index 4368f6de274..ac36b706f50 100644
--- a/addons/web_editor/static/src/js/base.js
+++ b/addons/web_editor/static/src/js/base.js
@@ -32,14 +32,11 @@ data.dom_ready = dom_ready;
var all_ready;
data.ready = function () {
if (!all_ready) {
- all_ready = $.when(dom_ready, ajax.loadXML()).then(function () {
- // TODO find a better way to find if we are in the backend or not
- // We don't load translation if we are in the backend since it's already done by the webclient
- if (!$(document).find('.o_web_client').length) {
- return load_translations();
- }
- });
- } else if(all_ready.state() === "resolved") { // can add async template
+ all_ready = $.when(dom_ready, ajax.loadXML());
+ if (!odoo._modules) {
+ all_ready.then(load_translations);
+ }
+ } else { // can add async template
all_ready = $.when(dom_ready, ajax.loadXML());
}
return all_ready;
diff --git a/addons/web_editor/static/src/js/summernote.js b/addons/web_editor/static/src/js/summernote.js
index fe2ff837142..e439f52824a 100644
--- a/addons/web_editor/static/src/js/summernote.js
+++ b/addons/web_editor/static/src/js/summernote.js
@@ -19,6 +19,57 @@ var editor = eventHandler.modules.editor;
var renderer = $.summernote.renderer;
var options = $.summernote.options;
+// Browser-unify execCommand
+var oldJustify = {};
+_.each(['Left', 'Right', 'Full', 'Center'], function (align) {
+ oldJustify[align] = editor['justify' + align];
+ editor['justify' + align] = function ($editable, value) {
+ // Before calling the standard function, check all elements which have
+ // an 'align' attribute and mark them with their value
+ var $align = $editable.find('[align]');
+ _.each($align, function (el) {
+ var $el = $(el);
+ $el.data('__align', $el.attr('align'));
+ });
+
+ // Call the standard function
+ oldJustify[align].apply(this, arguments);
+
+ // Then:
+
+ // Remove the text-align of elements which lost the 'align' attribute
+ var $newAlign = $editable.find('[align]');
+ $align.not($newAlign).css('text-align', '');
+
+ // Transform the 'align' attribute into the 'text-align' css
+ // property for elements which received the 'align' attribute or whose
+ // 'align' attribute changed
+ _.each($newAlign, function (el) {
+ var $el = $(el);
+
+ var oldAlignValue = $align.data('__align');
+ var alignValue = $el.attr('align');
+ if (oldAlignValue === alignValue) {
+ // If the element already had an 'align' attribute and that it
+ // did not changed, do nothing (compatibility)
+ return;
+ }
+
+ $el.removeAttr('align');
+ $el.css('text-align', alignValue);
+
+ // Note the first step (removing the text-align of elemnts which
+ // lost the 'align' attribute) is kinda the same as this one, but
+ // this one handles the elements which have been edited with chrome
+ // or with this new system
+ $el.find('*').css('text-align', '');
+ });
+
+ // Unmark the elements
+ $align.removeData('__align');
+ };
+});
+
//////////////////////////////////////////////////////////////////////////////////////////////////////////
/* Add method to Summernote*/
@@ -2319,26 +2370,6 @@ eventHandler.modules.popover.update = function ($popover, oStyle, isAirMode) {
}
};
-// override summernote clipboard functionality
-eventHandler.modules.clipboard.attach = function(layoutInfo) {
- var $editable = layoutInfo.editable();
- $editable.on('paste', function(e) {
- var clipboardData = ((e.originalEvent || e).clipboardData || window.clipboardData);
- // Change nothing if pasting html (copy from text editor / web / ...) or
- // if clipboardData is not available (IE / ...)
- if (clipboardData && clipboardData.types && clipboardData.types.length === 1 && clipboardData.types[0] === "text/plain") {
- e.preventDefault();
- $editable.data('NoteHistory').recordUndo($editable); // FIXME
- var pastedText = clipboardData.getData("text/plain");
- // Try removing linebreaks which are not really linebreaks (in a PDF,
- // when a sentence goes over the next line, copying it considers it
- // a linebreak for example).
- var formattedText = pastedText.replace(/([\w-])\r?\n([\w-])/g, "$1 $2").trim();
- document.execCommand("insertText", false, formattedText);
- }
- });
-};
-
//////////////////////////////////////////////////////////////////////////////////////////////////////////
var fn_attach = eventHandler.attach;
diff --git a/addons/web_kanban/static/src/js/kanban_view.js b/addons/web_kanban/static/src/js/kanban_view.js
index 3033cb1b00d..7351588422c 100644
--- a/addons/web_kanban/static/src/js/kanban_view.js
+++ b/addons/web_kanban/static/src/js/kanban_view.js
@@ -60,7 +60,7 @@ var KanbanView = View.extend({
init: function () {
this._super.apply(this, arguments);
- this.qweb = new QWeb(session.debug, {_s: session.origin});
+ this.qweb = new QWeb(session.debug, {_s: session.origin}, false);
this.limit = this.options.limit || parseInt(this.fields_view.arch.attrs.limit, 10) || 40;
this.fields = this.fields_view.fields;
diff --git a/addons/web_planner/static/src/js/web_planner_common.js b/addons/web_planner/static/src/js/web_planner_common.js
index 872745aad89..dc7914fc798 100644
--- a/addons/web_planner/static/src/js/web_planner_common.js
+++ b/addons/web_planner/static/src/js/web_planner_common.js
@@ -67,11 +67,18 @@ var PlannerDialog = Dialog.extend({
* Fetch the planner's rendered template
*/
willStart: function() {
+ var context = session.user_context;
+ // fallback context for frontend
+ if(_.isEmpty(context)) {
+ context = {
+ lang: (document.documentElement.getAttribute('lang')||'').replace('-', '_'),
+ };
+ }
var def = (new Model('web.planner')).call('render', [
this.planner.view_id[0],
this.planner.planner_application
], {
- context: session.user_context
+ context: context
}).then((function (template) {
this.$template = $(template);
}).bind(this));
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index b2c6ca238e8..d699c351db9 100644
--- a/addons/website_sale/static/src/js/website_sale.js
+++ b/addons/website_sale/static/src/js/website_sale.js
@@ -168,7 +168,7 @@ odoo.define('website_sale.website_sale', function (require) {
}
else {
$q.parents('li:first').addClass("hidden");
- $('a[href^="/shop/checkout"]').addClass("hidden");
+ $('a[href*="/shop/checkout"]').addClass("hidden");
}
$q.html(data.cart_quantity).hide().fadeIn(600);
diff --git a/doc/cla/corporate/teclib.md b/doc/cla/corporate/teclib.md
index ff9db6dd21c..275a2b41300 100644
--- a/doc/cla/corporate/teclib.md
+++ b/doc/cla/corporate/teclib.md
@@ -12,3 +12,4 @@ List of contributors:
Laurent Destailleur ldestailleur@teclib.com https://github.com/eldy
David Halgand dhalgand@teclib.com https://github.com/halgandd
+Guillaume Masson gmasson@teclib.com https://github.com/metaminux
diff --git a/odoo/addons/base/res/res_users.py b/odoo/addons/base/res/res_users.py
index 5621b99ee04..596620a9e52 100644
--- a/odoo/addons/base/res/res_users.py
+++ b/odoo/addons/base/res/res_users.py
@@ -280,6 +280,13 @@ class Users(models.Model):
if any(user.company_ids and user.company_id not in user.company_ids for user in self):
raise ValidationError(_('The chosen company is not in the allowed companies for this user'))
+ @api.multi
+ @api.constrains('action_id')
+ def _check_action_id(self):
+ action_open_website = self.env.ref('base.action_open_website', raise_if_not_found=False)
+ if action_open_website and any(user.action_id.id == action_open_website.id for user in self):
+ raise ValidationError(_('The "App Switcher" action cannot be selected as home action.'))
+
@api.multi
def read(self, fields=None, load='_classic_read'):
if fields and self == self.env.user: