diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py
index b61fa11273d..565b81c5b53 100644
--- a/addons/account/models/account_invoice.py
+++ b/addons/account/models/account_invoice.py
@@ -717,7 +717,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'])
@@ -865,7 +865,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)
@@ -1052,6 +1052,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 credit note from the invoice.
@@ -1211,7 +1214,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)
@@ -1220,7 +1223,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 d5ed18ace1f..8e12990641e 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 5757f68155f..a05c70691e4 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 194408a9118..2bfedd740c1 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
@@ -572,7 +574,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_ch/models/account_invoice.py b/addons/l10n_ch/models/account_invoice.py
index 51bad251178..1164368e227 100644
--- a/addons/l10n_ch/models/account_invoice.py
+++ b/addons/l10n_ch/models/account_invoice.py
@@ -59,10 +59,11 @@ class AccountInvoice(models.Model):
""" The ISR reference number is 27 characters long. The first 12 of them
contain the postal account number of this ISR's issuer, removing the zeros
at the beginning and filling the empty places with zeros on the right if it is
- too short. The 15 other characters contain an internal reference identifying
+ too short. The next 14 characters contain an internal reference identifying
the invoice. For this, we use the invoice sequence number, removing each
of its non-digit characters, and pad the unused spaces on the left of
- this number with zeros.
+ this number with zeros. The last character of the ISR number is the result
+ of a recursive modulo 10 on its first 26 characters.
"""
def _space_isr_number(isr_number):
to_treat = isr_number
@@ -81,9 +82,9 @@ class AccountInvoice(models.Model):
invoice_ref = re.sub('[^\d]', '', record.number)
#We only keep the last digits of the sequence number if it is too long
invoice_ref = invoice_ref[-l10n_ch_ISR_NUMBER_ISSUER_LENGTH:]
- internal_ref = invoice_ref.zfill(l10n_ch_ISR_NUMBER_LENGTH - l10n_ch_ISR_NUMBER_ISSUER_LENGTH)
+ internal_ref = invoice_ref.zfill(l10n_ch_ISR_NUMBER_LENGTH - l10n_ch_ISR_NUMBER_ISSUER_LENGTH - 1) # -1 for mod10r check character
- record.l10n_ch_isr_number = invoice_issuer_ref + internal_ref
+ record.l10n_ch_isr_number = mod10r(invoice_issuer_ref + internal_ref)
record.l10n_ch_isr_number_spaced = _space_isr_number(record.l10n_ch_isr_number)
@api.depends('currency_id.name', 'amount_total', 'partner_bank_id.bank_id', 'number', 'partner_bank_id.l10n_ch_postal', 'partner_bank_id.bank_id.l10n_ch_postal_eur', 'partner_bank_id.bank_id.l10n_ch_postal_chf')
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/ir_model.py b/addons/mail/models/ir_model.py
index e2231e38f5d..d38e51cebbe 100644
--- a/addons/mail/models/ir_model.py
+++ b/addons/mail/models/ir_model.py
@@ -14,6 +14,12 @@ class IrModel(models.Model):
help="Whether this model supports messages and notifications.",
)
+ 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()
+
@api.multi
def write(self, vals):
if self and 'is_mail_thread' in vals:
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index b5aa6616794..4689b927226 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -2146,7 +2146,7 @@ class MailThread(models.AbstractModel):
new_channels.setdefault(header_follower.channel_id.id, set()).add(new_subtype.id)
# add followers coming from res.users relational fields that are tracked
- to_add_users = self.env['res.users'].sudo().browse([values[name] for name in user_field_lst if values.get(name)])
+ to_add_users = self.env['res.users'].sudo().browse([values[name] for name in user_field_lst if values.get(name)]).filtered(lambda u: u.partner_id.active)
for partner in to_add_users.mapped('partner_id'):
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 0a06fb24536..9ef440a4bc4 100644
--- a/addons/mrp/views/mrp_workcenter_views.xml
+++ b/addons/mrp/views/mrp_workcenter_views.xml
@@ -102,9 +102,11 @@
Work Orders Performanceir.actions.act_windowmrp.workorder
- [('state','=','done')]
+ []
+ {'search_default_done': True}formgraph,pivot,tree,form,gantt
+
@@ -566,4 +568,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 16443c69780..31494ba04ad 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 d4aaf40f664..3f4d72fb98b 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -209,7 +209,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 246028a9fdb..8dade5a00d1 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1988,6 +1988,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 150e3db4806..c9ff2270bb2 100644
--- a/addons/sale_mrp/sale_mrp.py
+++ b/addons/sale_mrp/sale_mrp.py
@@ -97,7 +97,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 fe13a88fcf9..c9da99309c6 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -597,7 +597,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 test_suite(self, mod=None, **kwargs):
return request.render('web.qunit_suite')
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index aa5572f5e7d..76d54cd4baa 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -924,7 +924,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});
@@ -935,6 +935,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/core/qweb.js b/addons/web/static/src/js/core/qweb.js
index 50742075e05..253e948d8ac 100644
--- a/addons/web/static/src/js/core/qweb.js
+++ b/addons/web/static/src/js/core/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/static/src/js/views/kanban/kanban_renderer.js b/addons/web/static/src/js/views/kanban/kanban_renderer.js
index b8c2e3c0bcd..0717931f084 100644
--- a/addons/web/static/src/js/views/kanban/kanban_renderer.js
+++ b/addons/web/static/src/js/views/kanban/kanban_renderer.js
@@ -90,7 +90,7 @@ var KanbanRenderer = BasicRenderer.extend({
this._super.apply(this, arguments);
this.widgets = [];
- this.qweb = new QWeb(session.debug, {_s: session.origin});
+ this.qweb = new QWeb(session.debug, {_s: session.origin}, false);
var templates = findInNode(this.arch, function (n) { return n.tag === 'templates';});
transformQwebTemplate(templates, state.fields);
this.qweb.add_template(utils.json_node_to_xml(templates));
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_planner/static/src/js/web_planner_common.js b/addons/web_planner/static/src/js/web_planner_common.js
index 0e611c89655..2ffce7df71d 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 = rpc.query({
model: 'web.planner',
method: 'render',
args: [this.planner.view_id[0], this.planner.planner_application],
- context: session.user_context,
+ context: context,
})
.then((function (template) {
this.$template = $(template);
diff --git a/addons/website_sale/static/src/js/website_sale.js b/addons/website_sale/static/src/js/website_sale.js
index 7d4d64d3932..9bea2844b34 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 4fcb13f1170..caf652d0545 100644
--- a/odoo/addons/base/res/res_users.py
+++ b/odoo/addons/base/res/res_users.py
@@ -281,6 +281,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: