summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-05-17 14:23:37 +0200
committerChristophe Simonis <chs@odoo.com>2018-05-17 14:23:37 +0200
commit644f0959d05d6467260f685ec3ac369e6cf81457 (patch)
tree84eabeb182621c974afc85355c0ff5cab6e7d7d7
parent87818f7fccc045f6011ab8f4f96c447740d6f41e (diff)
parent16552029241b3a7a3d9a40b8b7399da6ee415be4 (diff)
[MERGE] forward port branch saas-11.2 up to 16552029241
-rw-r--r--addons/account/models/account.py2
-rw-r--r--addons/account/models/account_move.py4
-rw-r--r--addons/account/static/src/js/reconciliation/reconciliation_model.js1
-rw-r--r--addons/account/wizard/wizard_tax_adjustments.py2
-rw-r--r--addons/account_payment/models/payment.py2
-rw-r--r--addons/auth_signup/controllers/main.py1
-rw-r--r--addons/auth_signup/data/auth_signup_data.xml1
-rw-r--r--addons/auth_signup/i18n/af.po1
-rw-r--r--addons/auth_signup/i18n/am.po1
-rw-r--r--addons/auth_signup/i18n/ar.po1
-rw-r--r--addons/auth_signup/i18n/auth_signup.pot1
-rw-r--r--addons/auth_signup/i18n/bg.po1
-rw-r--r--addons/auth_signup/i18n/bs.po1
-rw-r--r--addons/auth_signup/i18n/ca.po1
-rw-r--r--addons/auth_signup/i18n/cs.po1
-rw-r--r--addons/auth_signup/i18n/da.po1
-rw-r--r--addons/auth_signup/i18n/de.po1
-rw-r--r--addons/auth_signup/i18n/el.po2
-rw-r--r--addons/auth_signup/i18n/en_GB.po1
-rw-r--r--addons/auth_signup/i18n/es.po1
-rw-r--r--addons/auth_signup/i18n/es_AR.po1
-rw-r--r--addons/auth_signup/i18n/es_BO.po1
-rw-r--r--addons/auth_signup/i18n/es_CL.po1
-rw-r--r--addons/auth_signup/i18n/es_CO.po1
-rw-r--r--addons/auth_signup/i18n/es_CR.po1
-rw-r--r--addons/auth_signup/i18n/es_DO.po1
-rw-r--r--addons/auth_signup/i18n/es_EC.po1
-rw-r--r--addons/auth_signup/i18n/es_PE.po1
-rw-r--r--addons/auth_signup/i18n/es_PY.po1
-rw-r--r--addons/auth_signup/i18n/es_VE.po1
-rw-r--r--addons/auth_signup/i18n/et.po1
-rw-r--r--addons/auth_signup/i18n/eu.po1
-rw-r--r--addons/auth_signup/i18n/fa.po1
-rw-r--r--addons/auth_signup/i18n/fi.po1
-rw-r--r--addons/auth_signup/i18n/fo.po1
-rw-r--r--addons/auth_signup/i18n/fr.po1
-rw-r--r--addons/auth_signup/i18n/fr_CA.po1
-rw-r--r--addons/auth_signup/i18n/gl.po1
-rw-r--r--addons/auth_signup/i18n/he.po1
-rw-r--r--addons/auth_signup/i18n/hr.po1
-rw-r--r--addons/auth_signup/i18n/hu.po1
-rw-r--r--addons/auth_signup/i18n/id.po1
-rw-r--r--addons/auth_signup/i18n/it.po1
-rw-r--r--addons/auth_signup/i18n/ja.po1
-rw-r--r--addons/auth_signup/i18n/ka.po1
-rw-r--r--addons/auth_signup/i18n/kab.po1
-rw-r--r--addons/auth_signup/i18n/ko.po1
-rw-r--r--addons/auth_signup/i18n/lo.po1
-rw-r--r--addons/auth_signup/i18n/lt.po1
-rw-r--r--addons/auth_signup/i18n/lv.po1
-rw-r--r--addons/auth_signup/i18n/mk.po1
-rw-r--r--addons/auth_signup/i18n/mn.po1
-rw-r--r--addons/auth_signup/i18n/nb.po1
-rw-r--r--addons/auth_signup/i18n/ne.po1
-rw-r--r--addons/auth_signup/i18n/nl.po2
-rw-r--r--addons/auth_signup/i18n/nl_BE.po1
-rw-r--r--addons/auth_signup/i18n/pl.po1
-rw-r--r--addons/auth_signup/i18n/pt.po1
-rw-r--r--addons/auth_signup/i18n/pt_BR.po2
-rw-r--r--addons/auth_signup/i18n/ro.po1
-rw-r--r--addons/auth_signup/i18n/ru.po1
-rw-r--r--addons/auth_signup/i18n/sk.po1
-rw-r--r--addons/auth_signup/i18n/sl.po1
-rw-r--r--addons/auth_signup/i18n/sq.po1
-rw-r--r--addons/auth_signup/i18n/sr.po1
-rw-r--r--addons/auth_signup/i18n/sr@latin.po1
-rw-r--r--addons/auth_signup/i18n/sv.po1
-rw-r--r--addons/auth_signup/i18n/th.po1
-rw-r--r--addons/auth_signup/i18n/tr.po1
-rw-r--r--addons/auth_signup/i18n/uk.po1
-rw-r--r--addons/auth_signup/i18n/vi.po1
-rw-r--r--addons/auth_signup/i18n/zh_CN.po1
-rw-r--r--addons/auth_signup/i18n/zh_TW.po1
-rw-r--r--addons/auth_signup/models/res_partner.py1
-rw-r--r--addons/delivery/models/sale_order.py3
-rw-r--r--addons/event/models/event.py5
-rw-r--r--addons/event/models/event_mail.py50
-rw-r--r--addons/event/security/ir.model.access.csv2
-rw-r--r--addons/event_sale/models/sale_order.py2
-rw-r--r--addons/hr_attendance/models/hr_employee.py3
-rw-r--r--addons/hr_holidays/models/hr_leave.py8
-rw-r--r--addons/hr_timesheet/models/hr_timesheet.py1
-rw-r--r--addons/hr_timesheet/models/project.py4
-rw-r--r--addons/http_routing/geoipresolver.py4
-rw-r--r--addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py2
-rw-r--r--addons/mail/models/mail_message.py11
-rw-r--r--addons/mail/models/mail_thread.py8
-rw-r--r--addons/mail/static/src/js/chatter.js4
-rw-r--r--addons/mail/static/src/js/chatter_composer.js2
-rw-r--r--addons/mail/static/src/js/composer.js256
-rw-r--r--addons/mail/static/src/less/composer.less93
-rw-r--r--addons/mail/static/src/less/extended_chat_window.less8
-rw-r--r--addons/mail/static/src/xml/composer.xml12
-rw-r--r--addons/mail/static/tests/chatter_tests.js19
-rw-r--r--addons/mail/static/tests/discuss_tests.js9
-rw-r--r--addons/mass_mailing/models/mass_mailing.py1
-rw-r--r--addons/payment/models/payment_acquirer.py4
-rw-r--r--addons/payment_sips/models/payment.py11
-rw-r--r--addons/point_of_sale/models/product.py5
-rw-r--r--addons/point_of_sale/static/src/js/models.js5
-rw-r--r--addons/portal/views/portal_templates.xml2
-rw-r--r--addons/project/i18n/project.pot6
-rw-r--r--addons/project/models/project.py20
-rw-r--r--addons/project/views/project_portal_templates.xml2
-rw-r--r--addons/project/views/project_views.xml10
-rw-r--r--addons/purchase/models/account_invoice.py6
-rw-r--r--addons/sale_payment/models/payment.py2
-rw-r--r--addons/stock/models/stock_move.py6
-rw-r--r--addons/stock_account/models/stock.py8
-rw-r--r--addons/stock_account/tests/test_stockvaluation.py33
-rw-r--r--addons/test_mail/data/test_mail_data.py242
-rw-r--r--addons/test_mail/tests/test_mail_gateway.py8
-rw-r--r--addons/web/controllers/main.py2
-rw-r--r--addons/web/static/src/js/views/basic/basic_model.js14
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js37
-rw-r--r--addons/web/static/tests/helpers/test_utils.js48
-rw-r--r--addons/website_event/controllers/main.py8
-rw-r--r--addons/website_event_sale/controllers/main.py2
-rw-r--r--addons/website_sale/views/templates.xml2
-rw-r--r--addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js14
-rw-r--r--odoo/addons/base/models/res_users.py7
-rw-r--r--odoo/addons/base/wizard/base_partner_merge.py5
-rw-r--r--odoo/tests/common.py12
123 files changed, 672 insertions, 439 deletions
diff --git a/addons/account/models/account.py b/addons/account/models/account.py
index 63d96de9aff..2763c3ededd 100644
--- a/addons/account/models/account.py
+++ b/addons/account/models/account.py
@@ -801,7 +801,7 @@ class AccountTax(models.Model):
tag_ids = fields.Many2many('account.account.tag', 'account_tax_account_tag', string='Tags', help="Optional tags you may want to assign for custom reporting")
tax_group_id = fields.Many2one('account.tax.group', string="Tax Group", default=_default_tax_group, required=True)
# Technical field to make the 'tax_exigibility' field invisible if the same named field is set to false in 'res.company' model
- hide_tax_exigibility = fields.Boolean(string='Hide Use Cash Basis Option', related='company_id.tax_exigibility')
+ hide_tax_exigibility = fields.Boolean(string='Hide Use Cash Basis Option', related='company_id.tax_exigibility', readonly=True)
tax_exigibility = fields.Selection(
[('on_invoice', 'Based on Invoice'),
('on_payment', 'Based on Payment'),
diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py
index 631243e3697..215f9d12878 100644
--- a/addons/account/models/account_move.py
+++ b/addons/account/models/account_move.py
@@ -1131,7 +1131,7 @@ class AccountMoveLine(models.Model):
'move_id': self.id,
'user_id': self.invoice_id.user_id.id or self._uid,
'partner_id': self.partner_id.id,
- 'company_id': self.env.user.company_id.id,
+ 'company_id': self.analytic_account_id.company_id.id or self.env.user.company_id.id,
}
def _prepare_analytic_distribution_line(self, distribution):
@@ -1154,7 +1154,7 @@ class AccountMoveLine(models.Model):
'ref': self.ref,
'move_id': self.id,
'user_id': self.invoice_id.user_id.id or self._uid,
- 'company_id': self.env.user.company_id.id
+ 'company_id': distribution.account_id.company_id.id or self.env.user.company_id.id,
}
@api.model
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index b2c2f5324dc..8c433ba2ce2 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -804,6 +804,7 @@ var StatementModel = BasicModel.extend({
model: 'account.tax',
method: 'json_friendly_compute_all',
args: args,
+ context: $.extend(self.context || {}, {'round': true}),
})
.then(function (result) {
_.each(result.taxes, function(tax){
diff --git a/addons/account/wizard/wizard_tax_adjustments.py b/addons/account/wizard/wizard_tax_adjustments.py
index 2f188b1abab..51f1936b93b 100644
--- a/addons/account/wizard/wizard_tax_adjustments.py
+++ b/addons/account/wizard/wizard_tax_adjustments.py
@@ -18,7 +18,7 @@ class TaxAdjustments(models.TransientModel):
debit_account_id = fields.Many2one('account.account', string='Debit account', required=True, domain=[('deprecated', '=', False)])
credit_account_id = fields.Many2one('account.account', string='Credit account', required=True, domain=[('deprecated', '=', False)])
amount = fields.Monetary(currency_field='company_currency_id', required=True)
- adjustment_type = fields.Selection([('debit', 'Adjustment in favor of the Estate'), ('credit', 'Adjustment in your favor')], string="Adjustment Type", store=False, required=True)
+ adjustment_type = fields.Selection([('debit', 'Applied on debit journal item'), ('credit', 'Applied on credit journal item')], string="Adjustment Type", store=False, required=True)
company_currency_id = fields.Many2one('res.currency', readonly=True, default=lambda self: self.env.user.company_id.currency_id)
tax_id = fields.Many2one('account.tax', string='Adjustment Tax', ondelete='restrict', domain=[('type_tax_use', '=', 'adjustment')], required=True)
diff --git a/addons/account_payment/models/payment.py b/addons/account_payment/models/payment.py
index 389cfa206e1..cfc90258289 100644
--- a/addons/account_payment/models/payment.py
+++ b/addons/account_payment/models/payment.py
@@ -159,7 +159,7 @@ class PaymentTransaction(models.Model):
'currency_id': invoice.currency_id.id,
'partner_id': invoice.partner_id.id,
'partner_country_id': invoice.partner_id.country_id.id,
- 'reference': self.get_next_reference(invoice.number),
+ 'reference': self._get_next_reference(invoice.number, acquirer=acquirer),
'account_invoice_id': invoice.id,
}
if add_tx_values:
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index b8bdb9787b3..871847cb7b1 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -42,7 +42,6 @@ class AuthSignupHome(Home):
template.sudo().with_context(
lang=user_sudo.lang,
auth_login=werkzeug.url_encode({'auth_login': user_sudo.email}),
- password=request.params.get('password')
).send_mail(user_sudo.id, force_send=True)
return super(AuthSignupHome, self).web_login(*args, **kw)
except UserError as e:
diff --git a/addons/auth_signup/data/auth_signup_data.xml b/addons/auth_signup/data/auth_signup_data.xml
index 72cb1fa30f0..a58c2d4d9aa 100644
--- a/addons/auth_signup/data/auth_signup_data.xml
+++ b/addons/auth_signup/data/auth_signup_data.xml
@@ -102,7 +102,6 @@
<p>
Your login: ${object.email}
<br/>
- Your password: ${ctx['password']}
</p>
<p>
To gain access to your account, you can use the following link:
diff --git a/addons/auth_signup/i18n/af.po b/addons/auth_signup/i18n/af.po
index e95f0c01e3a..8fe284f2eb6 100644
--- a/addons/auth_signup/i18n/af.po
+++ b/addons/auth_signup/i18n/af.po
@@ -109,7 +109,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/am.po b/addons/auth_signup/i18n/am.po
index 884e96a66f0..7ed08db42cb 100644
--- a/addons/auth_signup/i18n/am.po
+++ b/addons/auth_signup/i18n/am.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/ar.po b/addons/auth_signup/i18n/ar.po
index aec6d4ea4b9..b379535575d 100644
--- a/addons/auth_signup/i18n/ar.po
+++ b/addons/auth_signup/i18n/ar.po
@@ -115,7 +115,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/auth_signup.pot b/addons/auth_signup/i18n/auth_signup.pot
index 3ca1e3ccc53..379775c2600 100644
--- a/addons/auth_signup/i18n/auth_signup.pot
+++ b/addons/auth_signup/i18n/auth_signup.pot
@@ -102,7 +102,6 @@ msgid "<div style=\"padding:0px;width:600px;margin:auto;background: #FFFFFF repe
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/bg.po b/addons/auth_signup/i18n/bg.po
index 5da9afc881c..96d4cbe6673 100644
--- a/addons/auth_signup/i18n/bg.po
+++ b/addons/auth_signup/i18n/bg.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/bs.po b/addons/auth_signup/i18n/bs.po
index e30567817fb..72953b3e2fb 100644
--- a/addons/auth_signup/i18n/bs.po
+++ b/addons/auth_signup/i18n/bs.po
@@ -109,7 +109,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/ca.po b/addons/auth_signup/i18n/ca.po
index 4154a0f8705..0d58beeaac2 100644
--- a/addons/auth_signup/i18n/ca.po
+++ b/addons/auth_signup/i18n/ca.po
@@ -134,7 +134,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/cs.po b/addons/auth_signup/i18n/cs.po
index bc6da8dee7c..48609ed8491 100644
--- a/addons/auth_signup/i18n/cs.po
+++ b/addons/auth_signup/i18n/cs.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/da.po b/addons/auth_signup/i18n/da.po
index 3613885ead3..9af60c059ef 100644
--- a/addons/auth_signup/i18n/da.po
+++ b/addons/auth_signup/i18n/da.po
@@ -111,7 +111,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/de.po b/addons/auth_signup/i18n/de.po
index c3fd7bc24bf..21e05ff6e1a 100644
--- a/addons/auth_signup/i18n/de.po
+++ b/addons/auth_signup/i18n/de.po
@@ -162,7 +162,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/el.po b/addons/auth_signup/i18n/el.po
index 527e38d061c..bdd6ddbfdeb 100644
--- a/addons/auth_signup/i18n/el.po
+++ b/addons/auth_signup/i18n/el.po
@@ -165,7 +165,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
@@ -199,7 +198,6 @@ msgstr ""
" <p>\n"
" Email/Όνομα χρήστη: ${object.email}\n"
" <br/>\n"
-" Κωδικός Πρόσβασης: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" Για να αποκτήσετε πρόσβαση στο λογαριασμό σας, ακολουθήστε τον παρακάτω σύνδεσμο:\n"
diff --git a/addons/auth_signup/i18n/en_GB.po b/addons/auth_signup/i18n/en_GB.po
index c533eb0e2c3..e9d962451fc 100644
--- a/addons/auth_signup/i18n/en_GB.po
+++ b/addons/auth_signup/i18n/en_GB.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es.po b/addons/auth_signup/i18n/es.po
index 774a8396e23..3579dd699f2 100644
--- a/addons/auth_signup/i18n/es.po
+++ b/addons/auth_signup/i18n/es.po
@@ -174,7 +174,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_AR.po b/addons/auth_signup/i18n/es_AR.po
index 8013046ee26..52cff67e109 100644
--- a/addons/auth_signup/i18n/es_AR.po
+++ b/addons/auth_signup/i18n/es_AR.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_BO.po b/addons/auth_signup/i18n/es_BO.po
index 8d0148b3dfe..b1147946e82 100644
--- a/addons/auth_signup/i18n/es_BO.po
+++ b/addons/auth_signup/i18n/es_BO.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_CL.po b/addons/auth_signup/i18n/es_CL.po
index 5a81f205342..b5a6207ef21 100644
--- a/addons/auth_signup/i18n/es_CL.po
+++ b/addons/auth_signup/i18n/es_CL.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_CO.po b/addons/auth_signup/i18n/es_CO.po
index b0585f4fa38..9cdb25ddfcb 100644
--- a/addons/auth_signup/i18n/es_CO.po
+++ b/addons/auth_signup/i18n/es_CO.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_CR.po b/addons/auth_signup/i18n/es_CR.po
index d137efbae8a..2ed09011556 100644
--- a/addons/auth_signup/i18n/es_CR.po
+++ b/addons/auth_signup/i18n/es_CR.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_DO.po b/addons/auth_signup/i18n/es_DO.po
index 77358762009..5972a4bbd7a 100644
--- a/addons/auth_signup/i18n/es_DO.po
+++ b/addons/auth_signup/i18n/es_DO.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_EC.po b/addons/auth_signup/i18n/es_EC.po
index fd7782eadce..c8c9bc51c10 100644
--- a/addons/auth_signup/i18n/es_EC.po
+++ b/addons/auth_signup/i18n/es_EC.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_PE.po b/addons/auth_signup/i18n/es_PE.po
index 9ee0f51c41e..919743b749a 100644
--- a/addons/auth_signup/i18n/es_PE.po
+++ b/addons/auth_signup/i18n/es_PE.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_PY.po b/addons/auth_signup/i18n/es_PY.po
index 0d37ff54c50..c4dfea726cf 100644
--- a/addons/auth_signup/i18n/es_PY.po
+++ b/addons/auth_signup/i18n/es_PY.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/es_VE.po b/addons/auth_signup/i18n/es_VE.po
index efbe4c21be1..96b47c37681 100644
--- a/addons/auth_signup/i18n/es_VE.po
+++ b/addons/auth_signup/i18n/es_VE.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/et.po b/addons/auth_signup/i18n/et.po
index c2d8db1e7dd..e21bd45495d 100644
--- a/addons/auth_signup/i18n/et.po
+++ b/addons/auth_signup/i18n/et.po
@@ -111,7 +111,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/eu.po b/addons/auth_signup/i18n/eu.po
index 9c44dafbd60..035159ebbc5 100644
--- a/addons/auth_signup/i18n/eu.po
+++ b/addons/auth_signup/i18n/eu.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/fa.po b/addons/auth_signup/i18n/fa.po
index b768510a3af..94919a982a4 100644
--- a/addons/auth_signup/i18n/fa.po
+++ b/addons/auth_signup/i18n/fa.po
@@ -111,7 +111,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/fi.po b/addons/auth_signup/i18n/fi.po
index 1c2f0091215..c2367709374 100644
--- a/addons/auth_signup/i18n/fi.po
+++ b/addons/auth_signup/i18n/fi.po
@@ -114,7 +114,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/fo.po b/addons/auth_signup/i18n/fo.po
index c2b513c1339..47a4304b83d 100644
--- a/addons/auth_signup/i18n/fo.po
+++ b/addons/auth_signup/i18n/fo.po
@@ -105,7 +105,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/fr.po b/addons/auth_signup/i18n/fr.po
index 5d19c895961..345b07411ed 100644
--- a/addons/auth_signup/i18n/fr.po
+++ b/addons/auth_signup/i18n/fr.po
@@ -168,7 +168,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/fr_CA.po b/addons/auth_signup/i18n/fr_CA.po
index 469fd480492..516ffa5ceaa 100644
--- a/addons/auth_signup/i18n/fr_CA.po
+++ b/addons/auth_signup/i18n/fr_CA.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/gl.po b/addons/auth_signup/i18n/gl.po
index d3ba0b2ac14..7e3f96ee07e 100644
--- a/addons/auth_signup/i18n/gl.po
+++ b/addons/auth_signup/i18n/gl.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/he.po b/addons/auth_signup/i18n/he.po
index d4adca058c8..5010a56142a 100644
--- a/addons/auth_signup/i18n/he.po
+++ b/addons/auth_signup/i18n/he.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/hr.po b/addons/auth_signup/i18n/hr.po
index 7ea2aa5a4db..5fe6dfa95cf 100644
--- a/addons/auth_signup/i18n/hr.po
+++ b/addons/auth_signup/i18n/hr.po
@@ -144,7 +144,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/hu.po b/addons/auth_signup/i18n/hu.po
index d88f6f7e87f..242590a5d8c 100644
--- a/addons/auth_signup/i18n/hu.po
+++ b/addons/auth_signup/i18n/hu.po
@@ -165,7 +165,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/id.po b/addons/auth_signup/i18n/id.po
index 6a1a3ff1898..3105d982405 100644
--- a/addons/auth_signup/i18n/id.po
+++ b/addons/auth_signup/i18n/id.po
@@ -112,7 +112,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/it.po b/addons/auth_signup/i18n/it.po
index f590d160d73..4c18b8d3b20 100644
--- a/addons/auth_signup/i18n/it.po
+++ b/addons/auth_signup/i18n/it.po
@@ -109,7 +109,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/ja.po b/addons/auth_signup/i18n/ja.po
index 3e28504326f..6824517fdc1 100644
--- a/addons/auth_signup/i18n/ja.po
+++ b/addons/auth_signup/i18n/ja.po
@@ -165,7 +165,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/ka.po b/addons/auth_signup/i18n/ka.po
index c484d9eb5ed..f61eb2cd8fb 100644
--- a/addons/auth_signup/i18n/ka.po
+++ b/addons/auth_signup/i18n/ka.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/kab.po b/addons/auth_signup/i18n/kab.po
index 0dec5bea4da..0e670472029 100644
--- a/addons/auth_signup/i18n/kab.po
+++ b/addons/auth_signup/i18n/kab.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/ko.po b/addons/auth_signup/i18n/ko.po
index d29991121f5..42dba965809 100644
--- a/addons/auth_signup/i18n/ko.po
+++ b/addons/auth_signup/i18n/ko.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/lo.po b/addons/auth_signup/i18n/lo.po
index e7bb5b2769a..8ae06fffbc2 100644
--- a/addons/auth_signup/i18n/lo.po
+++ b/addons/auth_signup/i18n/lo.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/lt.po b/addons/auth_signup/i18n/lt.po
index 31c1bc8ff2a..2ff10d5155a 100644
--- a/addons/auth_signup/i18n/lt.po
+++ b/addons/auth_signup/i18n/lt.po
@@ -112,7 +112,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/lv.po b/addons/auth_signup/i18n/lv.po
index 9b1970fc14b..1f493481075 100644
--- a/addons/auth_signup/i18n/lv.po
+++ b/addons/auth_signup/i18n/lv.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/mk.po b/addons/auth_signup/i18n/mk.po
index 723427f51d1..2bb3dadd211 100644
--- a/addons/auth_signup/i18n/mk.po
+++ b/addons/auth_signup/i18n/mk.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/mn.po b/addons/auth_signup/i18n/mn.po
index 2ac736ff37e..e2ab4e9d82e 100644
--- a/addons/auth_signup/i18n/mn.po
+++ b/addons/auth_signup/i18n/mn.po
@@ -105,7 +105,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/nb.po b/addons/auth_signup/i18n/nb.po
index e16abb8be70..0732fbeafc3 100644
--- a/addons/auth_signup/i18n/nb.po
+++ b/addons/auth_signup/i18n/nb.po
@@ -110,7 +110,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/ne.po b/addons/auth_signup/i18n/ne.po
index 8937826cf50..b05079f39bf 100644
--- a/addons/auth_signup/i18n/ne.po
+++ b/addons/auth_signup/i18n/ne.po
@@ -105,7 +105,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/nl.po b/addons/auth_signup/i18n/nl.po
index 2fae789bf5f..94ba16c4dd4 100644
--- a/addons/auth_signup/i18n/nl.po
+++ b/addons/auth_signup/i18n/nl.po
@@ -167,7 +167,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
@@ -201,7 +200,6 @@ msgstr ""
" <p>\n"
" Uw login: ${object.email}\n"
" <br/>\n"
-" Uw wachtwoord: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" Om toegang te krijgen tot uw account, kan u de volgende link gebruiken::\n"
diff --git a/addons/auth_signup/i18n/nl_BE.po b/addons/auth_signup/i18n/nl_BE.po
index 405e872d815..85eb30d00cd 100644
--- a/addons/auth_signup/i18n/nl_BE.po
+++ b/addons/auth_signup/i18n/nl_BE.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/pl.po b/addons/auth_signup/i18n/pl.po
index fbb0f81860f..314a2b54cb9 100644
--- a/addons/auth_signup/i18n/pl.po
+++ b/addons/auth_signup/i18n/pl.po
@@ -114,7 +114,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/pt.po b/addons/auth_signup/i18n/pt.po
index e1e03417c01..ba4833e46b5 100644
--- a/addons/auth_signup/i18n/pt.po
+++ b/addons/auth_signup/i18n/pt.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/pt_BR.po b/addons/auth_signup/i18n/pt_BR.po
index 3b2b3c0b29b..0375c301797 100644
--- a/addons/auth_signup/i18n/pt_BR.po
+++ b/addons/auth_signup/i18n/pt_BR.po
@@ -167,7 +167,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
@@ -201,7 +200,6 @@ msgstr ""
" <p>\n"
" Seu login: ${object.email}\n"
" <br/>\n"
-" Sua senha: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" Para acessar sua conta, você pode acessar o seguinte link:\n"
diff --git a/addons/auth_signup/i18n/ro.po b/addons/auth_signup/i18n/ro.po
index 70f19bf3768..5178308b08a 100644
--- a/addons/auth_signup/i18n/ro.po
+++ b/addons/auth_signup/i18n/ro.po
@@ -139,7 +139,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/ru.po b/addons/auth_signup/i18n/ru.po
index 64d0fa145bc..8a9cd8d9bae 100644
--- a/addons/auth_signup/i18n/ru.po
+++ b/addons/auth_signup/i18n/ru.po
@@ -173,7 +173,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/sk.po b/addons/auth_signup/i18n/sk.po
index 5eb7f676417..5138b206acf 100644
--- a/addons/auth_signup/i18n/sk.po
+++ b/addons/auth_signup/i18n/sk.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/sl.po b/addons/auth_signup/i18n/sl.po
index e887136c172..ffc11c07444 100644
--- a/addons/auth_signup/i18n/sl.po
+++ b/addons/auth_signup/i18n/sl.po
@@ -105,7 +105,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/sq.po b/addons/auth_signup/i18n/sq.po
index 164fdf95948..0bcff3b0ad8 100644
--- a/addons/auth_signup/i18n/sq.po
+++ b/addons/auth_signup/i18n/sq.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/sr.po b/addons/auth_signup/i18n/sr.po
index 39bcf501d75..6a1f8a51afb 100644
--- a/addons/auth_signup/i18n/sr.po
+++ b/addons/auth_signup/i18n/sr.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/sr@latin.po b/addons/auth_signup/i18n/sr@latin.po
index 0efb9c4cfda..0a9f5db16b1 100644
--- a/addons/auth_signup/i18n/sr@latin.po
+++ b/addons/auth_signup/i18n/sr@latin.po
@@ -109,7 +109,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/sv.po b/addons/auth_signup/i18n/sv.po
index d3b6c33642c..aa14a9b4a39 100644
--- a/addons/auth_signup/i18n/sv.po
+++ b/addons/auth_signup/i18n/sv.po
@@ -109,7 +109,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/th.po b/addons/auth_signup/i18n/th.po
index 16eaf1167ef..46fe1210082 100644
--- a/addons/auth_signup/i18n/th.po
+++ b/addons/auth_signup/i18n/th.po
@@ -108,7 +108,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/tr.po b/addons/auth_signup/i18n/tr.po
index 00fbe962c82..17d13118d7c 100644
--- a/addons/auth_signup/i18n/tr.po
+++ b/addons/auth_signup/i18n/tr.po
@@ -171,7 +171,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/uk.po b/addons/auth_signup/i18n/uk.po
index 51df9435a86..83cfb0e5b94 100644
--- a/addons/auth_signup/i18n/uk.po
+++ b/addons/auth_signup/i18n/uk.po
@@ -167,7 +167,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/vi.po b/addons/auth_signup/i18n/vi.po
index 4fd58b26cfa..84d5bab21b7 100644
--- a/addons/auth_signup/i18n/vi.po
+++ b/addons/auth_signup/i18n/vi.po
@@ -109,7 +109,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/zh_CN.po b/addons/auth_signup/i18n/zh_CN.po
index db90fbcb26e..bd89431a070 100644
--- a/addons/auth_signup/i18n/zh_CN.po
+++ b/addons/auth_signup/i18n/zh_CN.po
@@ -140,7 +140,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/i18n/zh_TW.po b/addons/auth_signup/i18n/zh_TW.po
index 150c5b36fd4..a92edce19b1 100644
--- a/addons/auth_signup/i18n/zh_TW.po
+++ b/addons/auth_signup/i18n/zh_TW.po
@@ -109,7 +109,6 @@ msgid ""
" <p>\n"
" Your login: ${object.email}\n"
" <br/>\n"
-" Your password: ${ctx['password']}\n"
" </p>\n"
" <p>\n"
" To gain access to your account, you can use the following link:\n"
diff --git a/addons/auth_signup/models/res_partner.py b/addons/auth_signup/models/res_partner.py
index 9fbcd27872f..17171c645f6 100644
--- a/addons/auth_signup/models/res_partner.py
+++ b/addons/auth_signup/models/res_partner.py
@@ -32,6 +32,7 @@ class ResPartner(models.Model):
signup_url = fields.Char(compute='_compute_signup_url', string='Signup URL')
@api.multi
+ @api.depends('signup_token', 'signup_expiration')
def _compute_signup_valid(self):
dt = now()
for partner in self:
diff --git a/addons/delivery/models/sale_order.py b/addons/delivery/models/sale_order.py
index f6dd0fbb9d8..b2ffaf67231 100644
--- a/addons/delivery/models/sale_order.py
+++ b/addons/delivery/models/sale_order.py
@@ -96,9 +96,10 @@ class SaleOrder(models.Model):
taxes_ids = self.fiscal_position_id.map_tax(taxes, carrier.product_id, self.partner_id).ids
# Create the sales order line
+ carrier_name = carrier.product_id.with_context(lang=self.partner_id.lang).name_get()[0][1]
values = {
'order_id': self.id,
- 'name': carrier.name,
+ 'name': carrier_name,
'product_uom_qty': 1,
'product_uom': carrier.product_id.uom_id.id,
'product_id': carrier.product_id.id,
diff --git a/addons/event/models/event.py b/addons/event/models/event.py
index 02b1c1a3489..1dc2ccb60d6 100644
--- a/addons/event/models/event.py
+++ b/addons/event/models/event.py
@@ -220,6 +220,11 @@ class EventEvent(models.Model):
else:
self.date_end_located = False
+ @api.onchange('is_online')
+ def _onchange_is_online(self):
+ if self.is_online:
+ self.address_id = False
+
@api.onchange('event_type_id')
def _onchange_type(self):
if self.event_type_id:
diff --git a/addons/event/models/event_mail.py b/addons/event/models/event_mail.py
index 1013f5a083b..680e1d4ba8a 100644
--- a/addons/event/models/event_mail.py
+++ b/addons/event/models/event_mail.py
@@ -4,7 +4,12 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, tools
+from odoo.tools import exception_to_unicode
+from odoo.tools.translate import _
+import random
+import logging
+_logger = logging.getLogger(__name__)
_INTERVALS = {
'hours': lambda interval: relativedelta(hours=interval),
@@ -115,12 +120,51 @@ class EventMailScheduler(models.Model):
return True
@api.model
+ def _warn_template_error(self, scheduler, exception):
+ # We warn ~ once by hour ~ instead of every 10 min if the interval unit is more than 'hours'.
+ if random.random() < 0.1666 or scheduler.interval_unit in ('now', 'hours'):
+ ex_s = exception_to_unicode(exception)
+ try:
+ event, template = scheduler.event_id, scheduler.template_id
+ emails = list(set([event.organizer_id.email, event.user_id.email, template.write_uid.email]))
+ subject = _("WARNING: Event Scheduler Error for event: %s" % event.name)
+ body = _("""Event Scheduler for:
+ - Event: %s (%s)
+ - Scheduled: %s
+ - Template: %s (%s)
+
+ Failed with error:
+ - %s
+
+ You receive this email because you are:
+ - the organizer of the event,
+ - or the responsible of the event,
+ - or the last writer of the template."""
+ % (event.name, event.id, scheduler.scheduled_date, template.name, template.id, ex_s))
+ email = self.env['ir.mail_server'].build_email(
+ email_from=self.env.user.email,
+ email_to=emails,
+ subject=subject, body=body,
+ )
+ self.env['ir.mail_server'].send_email(email)
+ except Exception as e:
+ _logger.error("Exception while sending traceback by email: %s.\n Original Traceback:\n%s", e, exception)
+ pass
+
+ @api.model
def run(self, autocommit=False):
schedulers = self.search([('done', '=', False), ('scheduled_date', '<=', datetime.strftime(fields.datetime.now(), tools.DEFAULT_SERVER_DATETIME_FORMAT))])
for scheduler in schedulers:
- scheduler.execute()
- if autocommit:
- self.env.cr.commit()
+ try:
+ with self.env.cr.savepoint():
+ scheduler.execute()
+ except Exception as e:
+ _logger.exception(e)
+ self.invalidate_cache()
+ self._warn_template_error(scheduler, e)
+ else:
+ if autocommit:
+ self.env.cr.commit()
return True
diff --git a/addons/event/security/ir.model.access.csv b/addons/event/security/ir.model.access.csv
index 49d859ad183..ed7ec0e0cc6 100644
--- a/addons/event/security/ir.model.access.csv
+++ b/addons/event/security/ir.model.access.csv
@@ -5,7 +5,7 @@ access_event_event_portal,event.event.portal,model_event_event,,1,0,0,0
access_event_event_user,event.event.user,model_event_event,event.group_event_user,1,0,0,0
access_event_event_manager,event.event.manager,model_event_event,event.group_event_manager,1,1,1,1
access_event_registration,event.registration,model_event_registration,event.group_event_user,1,1,1,1
-access_event_registration_portal,event.registration,model_event_registration,,1,0,0,0
+access_event_registration_portal,event.registration,model_event_registration,,0,0,0,0
access_event_mail,event.mail,model_event_mail,event.group_event_user,1,0,0,0
access_event_mail_manager,event.mail manager,model_event_mail,event.group_event_manager,1,1,1,1
access_event_mail_registration,event.mail.registration,model_event_mail_registration,event.group_event_user,1,0,0,0
diff --git a/addons/event_sale/models/sale_order.py b/addons/event_sale/models/sale_order.py
index 7a2dcd8a745..6902d8e4743 100644
--- a/addons/event_sale/models/sale_order.py
+++ b/addons/event_sale/models/sale_order.py
@@ -41,7 +41,7 @@ class SaleOrderLine(models.Model):
order line has a product_uom_qty attribute that will be the number of
registrations linked to this line. This method update existing registrations
and create new one for missing one. """
- Registration = self.env['event.registration']
+ Registration = self.env['event.registration'].sudo()
registrations = Registration.search([('sale_order_line_id', 'in', self.ids), ('state', '!=', 'cancel')])
for so_line in self.filtered('event_id'):
existing_registrations = registrations.filtered(lambda self: self.sale_order_line_id.id == so_line.id)
diff --git a/addons/hr_attendance/models/hr_employee.py b/addons/hr_attendance/models/hr_employee.py
index 3f7eee536cf..e0cec9681bc 100644
--- a/addons/hr_attendance/models/hr_employee.py
+++ b/addons/hr_attendance/models/hr_employee.py
@@ -54,7 +54,8 @@ class HrEmployee(models.Model):
@api.depends('last_attendance_id.check_in', 'last_attendance_id.check_out', 'last_attendance_id')
def _compute_attendance_state(self):
for employee in self:
- employee.attendance_state = employee.last_attendance_id and not employee.last_attendance_id.check_out and 'checked_in' or 'checked_out'
+ att = employee.last_attendance_id.sudo()
+ employee.attendance_state = att and not att.check_out and 'checked_in' or 'checked_out'
@api.constrains('pin')
def _verify_pin(self):
diff --git a/addons/hr_holidays/models/hr_leave.py b/addons/hr_holidays/models/hr_leave.py
index 57595b2643e..2bfbd27ed1f 100644
--- a/addons/hr_holidays/models/hr_leave.py
+++ b/addons/hr_holidays/models/hr_leave.py
@@ -114,11 +114,6 @@ class HolidaysRequest(models.Model):
('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0."),
]
- @api.onchange('employee_id')
- def _onchange_employee_id(self):
- self.manager_id = self.employee_id and self.employee_id.parent_id
- self.department_id = self.employee_id.department_id
-
@api.multi
@api.depends('number_of_days_temp')
def _compute_number_of_days(self):
@@ -160,7 +155,8 @@ class HolidaysRequest(models.Model):
self.department_id = None
@api.onchange('employee_id')
- def _onchange_employee(self):
+ def _onchange_employee_id(self):
+ self.manager_id = self.employee_id and self.employee_id.parent_id
if self.holiday_type == 'employee':
self.department_id = self.employee_id.department_id
diff --git a/addons/hr_timesheet/models/hr_timesheet.py b/addons/hr_timesheet/models/hr_timesheet.py
index ee73147ed3f..1e8e3429206 100644
--- a/addons/hr_timesheet/models/hr_timesheet.py
+++ b/addons/hr_timesheet/models/hr_timesheet.py
@@ -73,6 +73,7 @@ class AccountAnalyticLine(models.Model):
if vals.get('project_id') and not vals.get('account_id'):
project = self.env['project.project'].browse(vals.get('project_id'))
vals['account_id'] = project.analytic_account_id.id
+ vals['company_id'] = project.analytic_account_id.company_id.id
if not project.analytic_account_id.active:
raise UserError(_('The project you are timesheeting on is not linked to a active analytic account. Please the project configuration.'))
# employee implies user
diff --git a/addons/hr_timesheet/models/project.py b/addons/hr_timesheet/models/project.py
index 12f742231f7..6276aa166ca 100644
--- a/addons/hr_timesheet/models/project.py
+++ b/addons/hr_timesheet/models/project.py
@@ -154,8 +154,10 @@ class Task(models.Model):
# a timesheet must have an analytic account (and a project)
if self and not project_id:
raise UserError(_('This task must have a project since they are linked to timesheets.'))
+ account_id = self.env['project.project'].browse(project_id).sudo().analytic_account_id
self.sudo().mapped('timesheet_ids').write({
'project_id': project_id,
- 'account_id': self.env['project.project'].browse(project_id).sudo().analytic_account_id.id
+ 'account_id': account_id.id,
+ 'company_id': account_id.company_id.id,
})
return result
diff --git a/addons/http_routing/geoipresolver.py b/addons/http_routing/geoipresolver.py
index e95b9318eb2..24bbe723d6d 100644
--- a/addons/http_routing/geoipresolver.py
+++ b/addons/http_routing/geoipresolver.py
@@ -50,10 +50,10 @@ class GeoIPResolver(object):
# Compatibility with Legacy database.
# Some ips cannot be located to a specific country. Legacy DB used to locate them in
# continent instead of country. Do the same to not change behavior of existing code.
- country = r.country if r.country.geoname_id else r.continent
+ country, attr = (r.country, 'iso_code') if r.country.geoname_id else (r.continent, 'code')
return {
'city': r.city.name,
- 'country_code': country.iso_code,
+ 'country_code': getattr(country, attr),
'country_name': country.name,
'region': r.subdivisions[0].iso_code if r.subdivisions else None,
'time_zone': r.location.time_zone,
diff --git a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py
index cf9901281c5..2ec6e172ee1 100644
--- a/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py
+++ b/addons/l10n_be_hr_payroll/models/l10n_be_hr_payroll.py
@@ -126,7 +126,7 @@ class HrContract(models.Model):
@api.depends('commission_on_target')
def _compute_warrants_cost(self):
for contract in self:
- contract.warrants_cost = contract.commission_on_target * 1.326 * 1.05 * 12.0
+ contract.warrants_cost = contract.commission_on_target * 1.326 / 1.05 * 12.0
contract.warrant_value_employee = contract.commission_on_target * 1.326 * (1.00 - 0.535) * 12.0
@api.depends('wage', 'fuel_card', 'representation_fees', 'transport_employer_cost',
diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py
index 86cd33c67d2..d5df5901608 100644
--- a/addons/mail/models/mail_message.py
+++ b/addons/mail/models/mail_message.py
@@ -416,8 +416,17 @@ class Message(models.Model):
com_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_comment')
note_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
+ # fetch notification status
+ notif_dict = {}
+ notifs = self.env['mail.notification'].sudo().search([('mail_message_id', 'in', list(mid for mid in message_tree)), ('is_read', '=', False)])
+ for notif in notifs:
+ mid = notif.mail_message_id.id
+ if not notif_dict.get(mid):
+ notif_dict[mid] = {'partner_id': list()}
+ notif_dict[mid]['partner_id'].append(notif.res_partner_id.id)
+
for message in message_values:
- message['needaction_partner_ids'] = self.env['mail.notification'].sudo().search([('mail_message_id', '=', message['id']), ('is_read', '=', False)]).mapped('res_partner_id').ids
+ message['needaction_partner_ids'] = notif_dict.get(message['id'], dict()).get('partner_id', [])
message['is_note'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == note_id
message['is_discussion'] = message['subtype_id'] and subtypes_dict[message['subtype_id'][0]]['id'] == com_id
message['is_notification'] = message['is_note'] and not message['model'] and not message['res_id']
diff --git a/addons/mail/models/mail_thread.py b/addons/mail/models/mail_thread.py
index 023077e1859..e32bf8d270d 100644
--- a/addons/mail/models/mail_thread.py
+++ b/addons/mail/models/mail_thread.py
@@ -1354,7 +1354,13 @@ class MailThread(models.AbstractModel):
located in tools. """
if not body:
return body, attachments
- root = lxml.html.fromstring(body)
+ try:
+ root = lxml.html.fromstring(body)
+ except ValueError:
+ # In case the email client sent XHTML, fromstring will fail because 'Unicode strings
+ # with encoding declaration are not supported'.
+ root = lxml.html.fromstring(body.encode('utf-8'))
+
postprocessed = False
to_remove = []
for node in root.iter():
diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js
index dfc041325d9..e48a7c60028 100644
--- a/addons/mail/static/src/js/chatter.js
+++ b/addons/mail/static/src/js/chatter.js
@@ -184,7 +184,7 @@ var Chatter = Widget.extend({
input_baseline: 14,
is_log: options && options.is_log,
record_name: this.record_name,
- default_body: old_composer && old_composer.$input && old_composer.$input.html(),
+ default_body: old_composer && old_composer.$input && old_composer.$input.val(),
default_mention_selections: old_composer && old_composer.mention_get_listener_selections(),
});
this.composer.on('input_focused', this, function () {
@@ -247,7 +247,7 @@ var Chatter = Widget.extend({
}).always(function () {
// disable widgets in create mode, otherwise enable
self.isCreateMode ? self._disableChatter() : self._enableChatter();
- $spinner.remove;
+ $spinner.remove();
});
},
/**
diff --git a/addons/mail/static/src/js/chatter_composer.js b/addons/mail/static/src/js/chatter_composer.js
index ad21a16496f..da85f331863 100644
--- a/addons/mail/static/src/js/chatter_composer.js
+++ b/addons/mail/static/src/js/chatter_composer.js
@@ -212,7 +212,7 @@ var ChatterComposer = composer.BasicComposer.extend({
recipient_done.then(function (partner_ids) {
var context = {
default_parent_id: self.id,
- default_body: utils.get_text2html(self.$input.html()),
+ default_body: utils.get_text2html(self.$input.val()),
default_attachment_ids: _.pluck(self.get('attachment_ids'), 'id'),
default_partner_ids: partner_ids,
default_is_log: self.options.is_log,
diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js
index 8354fe610b5..f89c0382554 100644
--- a/addons/mail/static/src/js/composer.js
+++ b/addons/mail/static/src/js/composer.js
@@ -7,6 +7,7 @@ var utils = require('mail.utils');
var config = require('web.config');
var core = require('web.core');
var data = require('web.data');
+var dom = require('web.dom');
var session = require('web.session');
var Widget = require('web.Widget');
@@ -29,7 +30,7 @@ var MentionManager = Widget.extend({
events: {
"mouseover .o_mention_proposition": "on_hover_mention_proposition",
- "click .o_mention_proposition": "_onClickMentionItem",
+ "click .o_mention_proposition": "on_click_mention_item",
},
init: function (parent, options) {
@@ -52,6 +53,49 @@ var MentionManager = Widget.extend({
this.$('.o_mention_proposition').removeClass('active');
$elem.addClass('active');
},
+ on_click_mention_item: function (event) {
+ event.preventDefault();
+
+ var text_input = this.composer.$input.val();
+ var id = $(event.currentTarget).data('id');
+ var selected_suggestion = _.find(_.flatten(this.get('mention_suggestions')), function (s) {
+ return s.id === id;
+ });
+ var substitution = selected_suggestion.substitution;
+ if (!substitution) { // no substitution string given, so use the mention name instead
+ // replace white spaces with non-breaking spaces to facilitate mentions detection in text
+ selected_suggestion.name = selected_suggestion.name.replace(/ /g, NON_BREAKING_SPACE);
+ substitution = this.active_listener.delimiter + selected_suggestion.name;
+ }
+ var get_mention_index = function (matches, cursor_position) {
+ for (var i=0; i<matches.length; i++) {
+ if (cursor_position <= matches[i].index) {
+ return i;
+ }
+ }
+ return i;
+ };
+
+ // add the selected suggestion to the list
+ if (this.active_listener.selection.length) {
+ // get mention matches (ordered by index in the text)
+ var matches = this._get_match(text_input, this.active_listener);
+ var index = get_mention_index(matches, this._get_selection_positions().start);
+ this.active_listener.selection.splice(index, 0, selected_suggestion);
+ } else {
+ this.active_listener.selection.push(selected_suggestion);
+ }
+
+ // update input text, and reset dropdown
+ var cursor_position = this._get_selection_positions().start;
+ var text_left = text_input.substring(0, cursor_position-(this.mention_word.length+1));
+ var text_right = text_input.substring(cursor_position, text_input.length);
+ var text_input_new = text_left + substitution + ' ' + text_right;
+ this.composer.$input.val(text_input_new);
+ this._set_cursor_position(text_left.length+substitution.length+2);
+ this.set('mention_suggestions', []);
+ this.composer.focus(); // to trigger autoresize
+ },
// Public API
/**
@@ -86,8 +130,8 @@ var MentionManager = Widget.extend({
get_listener_selection: function (delimiter) {
var listener = _.findWhere(this.listeners, {delimiter: delimiter});
if (listener) {
- var inputMentions = this.composer.$input.text().match(new RegExp(delimiter+'[^ ]+(?= |&nbsp;)', 'g'));
- return this._validate_selection(listener.selection, inputMentions);
+ var input_mentions = this.composer.$input.val().match(new RegExp(delimiter+'[^ ]+(?= |&nbsp;)', 'g'));
+ return this._validate_selection(listener.selection, input_mentions);
}
return [];
},
@@ -106,15 +150,15 @@ var MentionManager = Widget.extend({
*/
detect_delimiter: function () {
var self = this;
- var textVal = this.composer.$input.html();
- var cursorPosition = this._getSelectionPositions();
- var leftString = textVal.substring(0, cursorPosition);
+ var text_val = this.composer.$input.val();
+ var cursor_position = this._get_selection_positions().start;
+ var left_string = text_val.substring(0, cursor_position);
function validate_keyword (delimiter, beginning_only) {
- var delimiter_position = leftString.lastIndexOf(delimiter) - 1;
+ var delimiter_position = left_string.lastIndexOf(delimiter) - 1;
if (beginning_only && delimiter_position > 0) {
return false;
}
- var search_str = textVal.substring(delimiter_position, cursorPosition);
+ var search_str = text_val.substring(delimiter_position, cursor_position);
var pattern = "(^"+delimiter+"|(^\\s"+delimiter+"))";
var regex_start = new RegExp(pattern, "g");
search_str = search_str.replace(/^\s\s*|^[\n\r]/g, '');
@@ -203,8 +247,6 @@ var MentionManager = Widget.extend({
/**
* Returns the matches (as RexExp.exec does) for the mention in the input text
- *
- * @private
* @param {String} input_text: the text to search matches
* @param {Object} listener: the listener for which we want to find a match
* @returns {Object[]} matches in the same format as RexExp.exec()
@@ -229,32 +271,6 @@ var MentionManager = Widget.extend({
return result;
},
/**
- * Get cursor position in contenteditable div with it's inner html content.
- *
- * @private
- * @return a current cursor position
- */
- _getSelectionPositions: function () {
- if (window.getSelection && window.getSelection().getRangeAt) {
- var range = window.getSelection().getRangeAt(0);
- var selectedObj = window.getSelection();
- var rangeCount = 0;
- var childNodes = selectedObj.anchorNode.parentNode.childNodes;
- for (var i = 0; i < childNodes.length; i++) {
- if (childNodes[i] === selectedObj.anchorNode) {
- break;
- }
- if (childNodes[i].outerHTML)
- rangeCount += childNodes[i].outerHTML.length;
- else if (childNodes[i].nodeType === 3) {
- rangeCount += childNodes[i].textContent.length;
- }
- }
- return range.startOffset + rangeCount;
- }
- return -1;
- },
- /**
* @private
* @param {integer} keycode
*/
@@ -312,23 +328,9 @@ var MentionManager = Widget.extend({
this.open = false;
}
},
- /*
- * Set cursor position after mention is added.
- * @private
- * @paran {int} node - id of childnode to set cursor position after it.
- */
- _setCursorPosition: function (nodeID) {
- var range = document.createRange();
- var selection = window.getSelection();
- range.setStart(document.getElementById(nodeID).nextSibling, 1);
- range.collapse(true);
- selection.removeAllRanges();
- selection.addRange(range);
- this.composer.$input.focus();
- },
- _validate_selection: function (selection, inputMentions) {
+ _validate_selection: function (selection, input_mentions) {
var validated_selection = [];
- _.each(inputMentions, function (mention) {
+ _.each(input_mentions, function (mention) {
var validated_mention = _.findWhere(selection, {name: mention.slice(1)});
if (validated_mention) {
validated_selection.push(validated_mention);
@@ -337,60 +339,15 @@ var MentionManager = Widget.extend({
return validated_selection;
},
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * after click on mention item it set to a contenteditable div with it's new
- * cursor postion
- *
- * @private
- * @param {KeyboardEvent} event - to get a current selected mention from
- * list and eventlistener.
- */
- _onClickMentionItem: function (event) {
- event.preventDefault();
-
- var textInput = this.composer.$input.html();
- var id = $(event.currentTarget).data('id');
- var selectedSuggestion = _.find(_.flatten(this.get('mention_suggestions')), function (s) {
- return s.id === id;
+ // Cursor position and selection utils
+ _get_selection_positions: function () {
+ var el = this.composer.$input.get(0);
+ return el ? dom.getSelectionRange(el) : {start: 0, end: 0};
+ },
+ _set_cursor_position: function (pos) {
+ this.composer.$input.each(function (index, elem) {
+ dom.setSelectionRange(elem, {start: pos, end: pos});
});
- var substitution = selectedSuggestion.substitution;
- if (!substitution) { // no substitution string given, so use the mention name instead
- // replace white spaces with non-breaking spaces to facilitate mentions detection in text
- selectedSuggestion.name = selectedSuggestion.name.replace(/ /g, NON_BREAKING_SPACE);
- substitution = _.escape(this.active_listener.delimiter + selectedSuggestion.name);
- }
- var getMentionIndex = function (matches, cursorPosition) {
- for (var i=0; i<matches.length; i++) {
- if (cursorPosition <= matches[i].index) {
- return i;
- }
- }
- return i;
- };
-
- // add the selected suggestion to the list
- if (this.active_listener.selection.length) {
- // get mention matches (ordered by index in the text)
- var matches = this._get_match(textInput, this.active_listener);
- var index = getMentionIndex(matches, this._getSelectionPositions());
- this.active_listener.selection.splice(index, 0, selectedSuggestion);
- } else {
- this.active_listener.selection.push(selectedSuggestion);
- }
-
- // update input text, and reset dropdown
- var cursorPosition = this._getSelectionPositions();
- var textLeft = textInput.substring(0, cursorPosition-(this.mention_word.length+1));
- var textRight = textInput.substring(cursorPosition, textInput.length);
- var nodeID = _.uniqueId('node');
- var newTextInput = textLeft + "<a id="+ nodeID+">" + substitution + "</a> " + textRight;
- this.composer.$input.html(newTextInput);
- this._setCursorPosition(nodeID);
- this.set('mention_suggestions', []);
},
});
@@ -398,8 +355,8 @@ var MentionManager = Widget.extend({
var BasicComposer = Widget.extend({
template: "mail.ChatComposer",
events: {
- "keydown .o_composer_input": "_onKeydown",
- "keyup .o_composer_input": "_onKeyup",
+ "keydown .o_composer_input textarea": "on_keydown",
+ "keyup .o_composer_input": "on_keyup",
"change input.o_input_file": "on_attachment_change",
"click .o_composer_button_send": "send_message",
"click .o_composer_button_add_attachment": "on_click_add_attachment",
@@ -410,7 +367,6 @@ var BasicComposer = Widget.extend({
'focusout .o_composer_button_emoji': '_onEmojiButtonFocusout',
'focus .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageFocus',
'click .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageClick',
- 'input .o_composer_input': '_removeMention',
},
// RPCs done to fetch the mention suggestions are throttled with the following value
MENTION_THROTTLE: 200,
@@ -487,12 +443,12 @@ var BasicComposer = Widget.extend({
this.$attachment_button = this.$(".o_composer_button_add_attachment");
this.$attachments_list = this.$('.o_composer_attachments_list');
- this.$input = this.$('.o_composer_input');
+ this.$input = this.$('.o_composer_input textarea');
this.$input.focus(function () {
self.trigger('input_focused');
});
- this.$input.html(this.options.default_body);
- this.$input.css('min-height', this.options.input_min_height);
+ this.$input.val(this.options.default_body);
+ dom.autoresize(this.$input, {parent: this, min_height: this.options.input_min_height});
// Attachments
$(window).on(this.fileupload_id, this.on_attachment_loaded);
@@ -514,7 +470,7 @@ var BasicComposer = Widget.extend({
// behavior for the chatter composer
//Removing unwanted extra spaces from message
- var value = _.escape(this.$input.text()).trim();
+ var value = _.escape(this.$input.val()).trim();
value = value.replace(/(\r|\n){2,}/g, '<br/><br/>');
value = value.replace(/(\r|\n)/g, '<br/>');
@@ -545,7 +501,7 @@ var BasicComposer = Widget.extend({
clear_composer: function() {
// Empty input, selected partners and attachments
- this.$input.html('');
+ this.$input.val('');
this.mention_manager.reset_selections();
this.set('attachment_ids', []);
},
@@ -557,7 +513,7 @@ var BasicComposer = Widget.extend({
getState: function () {
return {
attachments: this.get('attachment_ids'),
- text: this.$input.html(),
+ text: this.$input.val(),
};
},
@@ -572,7 +528,7 @@ var BasicComposer = Widget.extend({
setState: function (state) {
this.set('attachment_ids', state.attachments);
- this.$input.html(state.text);
+ this.$input.val(state.text);
},
/**
@@ -581,18 +537,8 @@ var BasicComposer = Widget.extend({
should_send: function (event) {
return !event.shiftKey;
},
- /**
- * _onKeydown event is triggered when is key is pressed
- * - on UP and DOWN arrow is pressed then event prevents it's default
- * behaviour if mention manager is open else it break it.
- * - on ENTER key is pressed and mention manager is open then event
- * prevents it's default behaviour else check if ControlKey is
- * pressed or not if yes then it send a message
- *
- * @private
- * @param {KeyboardEvent} event
- */
- _onKeydown: function (event) {
+
+ on_keydown: function (event) {
switch(event.which) {
// UP, DOWN, TAB: prevent moving cursor if navigation in mention propositions
case $.ui.keyCode.UP:
@@ -617,16 +563,7 @@ var BasicComposer = Widget.extend({
}
},
- /**
- * _onKeyup event is triggered when key is released.
- * on ESCAP key it close the mention suggestion dropdown menu.
- * on ENTER key it selects that mention and add to a content editable div.
- * on UP or DOWN key it highlights previous or next mention respectively.
- *
- * @private
- * @param {KeyboardEvent} event
- */
- _onKeyup: function (event) {
+ on_keyup: function (event) {
switch(event.which) {
// ESCAPED KEYS: do nothing
case $.ui.keyCode.END:
@@ -743,12 +680,6 @@ var BasicComposer = Widget.extend({
attachments: this.get('attachment_ids'),
}));
},
- // remove mention when user try to edit or remove it.
- _removeMention: function (event) {
- if (window.getSelection().anchorNode.parentNode.tagName === 'A') {
- document.getElementById(window.getSelection().anchorNode.parentNode.id).remove();
- }
- },
// Mention
mention_fetch_throttled: function (model, method, kwargs) {
@@ -834,14 +765,8 @@ var BasicComposer = Widget.extend({
},
// Others
- /**
- * used to check if contenteditable div is empty or not
- *
- * @return - it returns length of attachment and trim content of input box
- * tp check for empty.
- */
is_empty: function () {
- return !this.$input.text().trim() && !this.$('.o_attachments').children().length;
+ return !this.$input.val().trim() && !this.$('.o_attachments').children().length;
},
focus: function () {
this.$input.focus();
@@ -900,7 +825,7 @@ var BasicComposer = Widget.extend({
if (this.$emojisContainer.parent().length) {
this._hideEmojis();
} else {
- this.$emojisContainer.appendTo(this.$('.o_composer_container'));
+ this.$emojisContainer.appendTo(this.$('.o_composer'));
}
},
/**
@@ -925,31 +850,6 @@ var BasicComposer = Widget.extend({
clearTimeout(this._hideEmojisTimeout);
},
/**
- * set cursor position at the end in input box
- *
- * @todo move that kind of code in a utility file. Maybe dom.js
- *
- * @private
- * @param {Element} el
- */
- _placeCaretAtEnd: function (el) {
- el.focus();
- if (typeof window.getSelection !== "undefined"
- && typeof document.createRange !== "undefined") {
- var range = document.createRange();
- range.selectNodeContents(el);
- range.collapse(false);
- var sel = window.getSelection();
- sel.removeAllRanges();
- sel.addRange(range);
- } else if (typeof document.body.createTextRange !== "undefined") {
- var textRange = document.body.createTextRange();
- textRange.moveToElementText(el);
- textRange.collapse(false);
- textRange.select();
- }
- },
- /**
* Called when an emoji is clicked -> adds it in the <input/>, focuses the
* <input/> and closes the emoji panel.
*
@@ -957,8 +857,8 @@ var BasicComposer = Widget.extend({
* @param {Event} ev
*/
_onEmojiImageClick: function (ev) {
- this.$input.html(this.$input.html() + " " + $(ev.currentTarget).data('emoji') + " ");
- this._placeCaretAtEnd(this.$input[0]);
+ this.$input.val(this.$input.val() + " " + $(ev.currentTarget).data('emoji') + " ");
+ this.$input.focus();
this._hideEmojis();
},
});
diff --git a/addons/mail/static/src/less/composer.less b/addons/mail/static/src/less/composer.less
index eb80e9e2691..1686444970d 100644
--- a/addons/mail/static/src/less/composer.less
+++ b/addons/mail/static/src/less/composer.less
@@ -16,6 +16,7 @@
position: relative;
.o-flex(0, 0, auto);
.o-flex-display();
+ margin: 1px 0;
border-top: none;
background: @gray-lighter;
@@ -23,7 +24,7 @@
position: relative;
background: white;
- input {
+ textarea, input {
border: none;
}
}
@@ -40,21 +41,9 @@
}
.o_composer {
- .o_composer_input[contenteditable=true]:empty:before {
- content: attr(placeholder);
- opacity: 0.4;
- }
- .o_composer_input {
- border: none;
- box-shadow: none;
- cursor: text;
- display: inline-block;
- height: 100%;
- white-space: pre-wrap;
- word-break: break-all;
- }
- .o_composer_text_field {
- padding: 0 0 @o-mail-chatter-gap;
+ .o_composer_input > textarea {
+ width: 100%;
+ resize: none;
}
.o_composer_mention_dropdown .o_mention_proposition > a {
.o-text-overflow();
@@ -83,18 +72,19 @@
color: @odoo-brand-optional;
}
}
- }
- .o_mail_emoji_container {
- padding: 0 @o-mail-chatter-gap @o-mail-chatter-gap 0;
- border: 1px solid @gray-lighter-dark;
- background-color: @gray-lighter;
+ .o_mail_emoji_container {
+ padding: 0 @o-mail-chatter-gap @o-mail-chatter-gap 0;
+ border: 1px solid @gray-lighter-dark;
+ border-width: 1px 0 0 0;
+ background-color: @gray-lighter;
- .o_mail_emoji {
- margin: @o-mail-chatter-gap 0 0 @o-mail-chatter-gap;
- cursor: pointer; // only in the popover
+ .o_mail_emoji {
+ margin: @o-mail-chatter-gap 0 0 @o-mail-chatter-gap;
+ cursor: pointer; // only in the popover
min-width: 20px;
min-height: 20px;
+ }
}
}
@@ -120,13 +110,19 @@
.o_composer {
border-radius: 2px;
+ textarea {
+ padding-right: @grid-gutter-width; // avoid overlaps betwenn textarea and o_composer_button_full_composer
+ }
+ .o_mail_emoji_container {
+ margin: 0 -@o-mail-chatter-gap;
+ }
}
.o_composer, .o_composer_subject {
border: 1px solid @gray-lighter-dark;
- input {
+ textarea, input {
padding: 0 0 @o-mail-chatter-gap;
border: none;
}
@@ -138,9 +134,6 @@
.o_chatter_composer_tools {
border-top: 1px solid @gray-lighter-dark;
- @media (max-width: @screen-xs-max) {
- margin-top: 10px;
- }
}
&:not(.o_chat_inline_composer) {
@@ -159,24 +152,26 @@
&.o_chat_inline_composer {
.o_composer_container {
- .o-flex(1, 1, auto);
+ .o-flex(1, 0, auto);
}
.o_composer {
- display: flex;
- min-height: 40px;
+ padding: @o-mail-chatter-gap @o-mail-chatter-gap 0 @o-mail-chatter-gap;
border-radius: 2px 0 0 2px;
border-right-width: 0;
- }
- .o_mail_emoji_container {
- text-align: right;
- border-width: 1px;
+
+ .o_mail_emoji_container {
+ text-align: right;
+ border-right-width: 1px;
+ }
}
.o_composer_input {
+ .o-flex-display();
.o-flex-flow(row, wrap);
}
.o_composer_text_field {
- padding: @o-mail-chatter-gap;
.o-flex(1, 1, 50%);
+ margin: 0;
+ padding: 0;
}
.o_chatter_composer_tools {
.o-flex(0, 1, auto);
@@ -188,8 +183,8 @@
.o_chatter_composer_tools {
border: none;
border-left: 1px solid @gray-lighter-dark;
- padding-top: @o-mail-chatter-gap*0.5;
padding-left: @o-mail-chatter-gap*0.5;
+ .o-transform(translateY(-@o-mail-chatter-gap*0.5));
}
.o_composer_button_send {
@@ -208,26 +203,32 @@
border-top: 1px solid @gray-lighter-dark;
width: 100%;
}
- .o_mail_emoji_container {
- .o-position-absolute(auto, 0, 100%, 0);
- border-bottom-width: 1px;
+ .o_composer {
+ .o_mail_emoji_container {
+ .o-position-absolute(auto, 0, 100%, 0);
+ border-bottom-width: 1px;
+ }
}
.o_composer_input {
- padding: @o-mail-chatter-gap;
+ .o-flex-display();
.o-flex-flow(row, nowrap);
+ textarea.o_composer_text_field {
+ .o-flex(1, 1, auto);
+ }
+
.o_chatter_composer_tools {
.o-flex(0, 0, auto);
}
+ @media (max-width: @screen-xs-max) {
+ .o_composer_button_send {
+ display: inline-block;
+ }
+ }
}
.o_composer_button_send, .o_chatter_avatar {
display: none;
}
- @media (max-width: @screen-xs-max) {
- .o_composer_button_send {
- display: inline-block;
- }
- }
}
}
diff --git a/addons/mail/static/src/less/extended_chat_window.less b/addons/mail/static/src/less/extended_chat_window.less
index fcf92cf052a..3ae73dee861 100644
--- a/addons/mail/static/src/less/extended_chat_window.less
+++ b/addons/mail/static/src/less/extended_chat_window.less
@@ -42,14 +42,10 @@
}
.o_chatter_composer_tools {
.o-position-absolute(@right: 0px, @top: 0px);
-
- @media (max-width: @screen-xs-max) {
- .o-position-absolute(@right: 6px, @top: 0px);
- }
button {
- padding: 8px;
+ padding: 10px;
@media (max-width: @screen-xs-max) {
- padding: 7px 6px 0px 6px;
+ padding: 13px;
}
&.btn-icon {
font-size: 1.3em;
diff --git a/addons/mail/static/src/xml/composer.xml b/addons/mail/static/src/xml/composer.xml
index 90beedc147f..bb9321a749d 100644
--- a/addons/mail/static/src/xml/composer.xml
+++ b/addons/mail/static/src/xml/composer.xml
@@ -8,11 +8,13 @@
<input tabindex="1" type="text" placeholder="Subject" class="o_input"/>
</div>
<div t-attf-class="o_composer #{widget.extended ? 'o_extended_composer' : ''}">
- <div class="o_composer_input o_composer_text_field form-control" contenteditable="true" placeholder="Write Something..."/>
- <div class="o_chatter_composer_tools">
- <button tabindex="4" class="btn btn-sm btn-icon fa fa-smile-o o_composer_button_emoji" type="button" data-toggle="popover"/>
- <button tabindex="5" class="btn btn-sm btn-icon fa fa-paperclip o_composer_button_add_attachment" type="button"/>
- <button t-if="widget.options.isMobile" tabindex="3" class="btn btn-sm btn-icon fa fa-paper-plane-o o_composer_button_send" type="button"/>
+ <div class="o_composer_input">
+ <textarea class="o_input o_composer_text_field" tabindex="2" placeholder="Write something..."/>
+ <div class="o_chatter_composer_tools">
+ <button tabindex="4" class="btn btn-sm btn-icon fa fa-smile-o o_composer_button_emoji" type="button" data-toggle="popover"/>
+ <button tabindex="5" class="btn btn-sm btn-icon fa fa-paperclip o_composer_button_add_attachment" type="button"/>
+ <button t-if="widget.options.isMobile" tabindex="3" class="btn btn-sm btn-icon fa fa-paper-plane-o o_composer_button_send" type="button"/>
+ </div>
</div>
</div>
<div class="o_composer_attachments_list"/>
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index 8c1edc2f622..46de2e22d3f 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -368,9 +368,7 @@ QUnit.test('kanban activity widget with an activity', function (assert) {
QUnit.test('chatter: post, receive and star messages', function (assert) {
var done = assert.async();
- assert.expect(28);
-
- var unpatchWindowGetSelection = testUtils.patchWindowGetSelection();
+ assert.expect(27);
var bus = new Bus();
var BusService = createBusService(bus);
@@ -475,7 +473,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
// send a message
form.$('.o_chatter_button_new_message').click();
assert.ok(!$('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be opened");
- form.$('.oe_chatter .o_composer_text_field:first()').text("My first message");
+ form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
form.$('.oe_chatter .o_composer_button_send').click();
assert.ok($('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be closed");
assert.strictEqual(form.$('.o_thread_message').length, 2, "thread should contain two messages");
@@ -489,7 +487,7 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
// log a note
form.$('.o_chatter_button_log_note').click();
assert.ok(!$('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be opened");
- form.$('.oe_chatter .o_composer_text_field:first()').text("My first note");
+ form.$('.oe_chatter .o_composer_text_field:first()').val("My first note");
form.$('.oe_chatter .o_composer_button_send').click();
assert.ok($('.oe_chatter .o_chat_composer').hasClass('o_hidden'), "chatter should be closed");
assert.strictEqual(form.$('.o_thread_message').length, 3, "thread should contain three messages");
@@ -515,7 +513,10 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
// very basic test of mention
form.$('.o_chatter_button_new_message').click();
var $input = form.$('.oe_chatter .o_composer_text_field:first()');
- $input.text('@');
+ $input.val('@');
+ // the cursor position must be set for the mention manager to detect that we are mentionning
+ $input[0].selectionStart = 1;
+ $input[0].selectionEnd = 1;
$input.trigger('keyup');
assert.strictEqual(getSuggestionsDef.state(), "pending",
@@ -533,14 +534,10 @@ QUnit.test('chatter: post, receive and star messages', function (assert) {
assert.strictEqual(form.$('.o_mention_proposition .o_mention_info').text(), '(test@odoo.com)',
"suggestion should be displayed correctly");
- $(".o_mention_proposition > a").trigger("click");
- assert.strictEqual($(".o_composer_input a").length, 1, "mention is 'green' in edit mode");
-
BasicComposer.prototype.MENTION_THROTTLE = mentionThrottle;
//cleanup
form.destroy();
- unpatchWindowGetSelection();
done();
});
});
@@ -599,7 +596,7 @@ QUnit.test('chatter: post a message and switch in edit mode', function (assert)
// send a message
form.$('.o_chatter_button_new_message').click();
- form.$('.oe_chatter .o_composer_text_field:first()').text("My first message");
+ form.$('.oe_chatter .o_composer_text_field:first()').val("My first message");
form.$('.oe_chatter .o_composer_button_send').click();
assert.strictEqual(form.$('.o_thread_message').length, 1, "thread should contain a message");
assert.ok(form.$('.o_thread_message:first() .o_thread_message_core').text().indexOf('My first message') >= 0,
diff --git a/addons/mail/static/tests/discuss_tests.js b/addons/mail/static/tests/discuss_tests.js
index 8d3ac86824e..8b7c72fd21e 100644
--- a/addons/mail/static/tests/discuss_tests.js
+++ b/addons/mail/static/tests/discuss_tests.js
@@ -12,7 +12,6 @@ var testUtils = require('web.test_utils');
var createBusService = mailTestUtils.createBusService;
var createDiscuss = mailTestUtils.createDiscuss;
-var patchWindowGetSelection = testUtils.patchWindowGetSelection;
QUnit.module('mail', {}, function () {
@@ -156,14 +155,13 @@ QUnit.test('@ mention in channel', function (assert) {
// click on general
$general.click();
- var $input = discuss.$('.o_composer_input').first();
+ var $input = discuss.$('textarea.o_composer_text_field').first();
assert.ok($input.length, "should display a composer input");
// Simulate '@' typed by user with mocked Window.getSelection
// Note: focus is needed in order to trigger rpc 'channel_fetch_listeners'
$input.focus();
- $input.text("@");
- var unpatchWindowGetSelection = patchWindowGetSelection();
+ $input.val("@");
$input.trigger('keyup');
fetchListenersDef
@@ -249,7 +247,7 @@ QUnit.test('@ mention in channel', function (assert) {
$input.trigger($.Event('keyup', {which: $.ui.keyCode.ENTER}));
assert.strictEqual(discuss.$('.o_mention_proposition').length, 0,
"should not have any partner mention proposition after ENTER");
- assert.strictEqual($input.find('a').text() , "@Admin",
+ assert.strictEqual($input.val().trim() , "@Admin",
"should have the correct mention link in the composer input");
// send message
@@ -266,7 +264,6 @@ QUnit.test('@ mention in channel', function (assert) {
"@Admin", "should have correct mention link in the message content");
// Restore window.getSelection
- unpatchWindowGetSelection();
discuss.destroy();
done();
});
diff --git a/addons/mass_mailing/models/mass_mailing.py b/addons/mass_mailing/models/mass_mailing.py
index 83c85a7b0ea..586e8dd3998 100644
--- a/addons/mass_mailing/models/mass_mailing.py
+++ b/addons/mass_mailing/models/mass_mailing.py
@@ -773,6 +773,7 @@ class MassMailing(models.Model):
'mass_mailing_id': mailing.id,
'mailing_list_ids': [(4, l.id) for l in mailing.contact_list_ids],
'no_auto_thread': mailing.reply_to_mode != 'thread',
+ 'template_id': None,
'mail_server_id': mailing.mail_server_id.id,
}
if mailing.reply_to_mode == 'email':
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index 2d07a174a7e..abaf9ba0368 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -687,6 +687,10 @@ class PaymentTransaction(models.Model):
@api.model
def get_next_reference(self, reference):
+ return self._get_next_reference(reference)
+
+ @api.model
+ def _get_next_reference(self, reference, acquirer=None):
ref_suffix = 1
init_ref = reference
while self.env['payment.transaction'].sudo().search_count([('reference', '=', reference)]):
diff --git a/addons/payment_sips/models/payment.py b/addons/payment_sips/models/payment.py
index bbbe64b3feb..874d88508e4 100644
--- a/addons/payment_sips/models/payment.py
+++ b/addons/payment_sips/models/payment.py
@@ -4,6 +4,7 @@
import json
import logging
+import re
from hashlib import sha256
from werkzeug import urls
@@ -111,6 +112,16 @@ class AcquirerSips(models.Model):
return self.environment == 'prod' and self.sips_prod_url or self.sips_test_url
+class PaymentTransactionSips(models.Model):
+ _inherit = 'payment.transaction'
+
+ @api.model
+ def _get_next_reference(self, reference, acquirer=None):
+ if acquirer and acquirer.provider == 'sips':
+ reference = re.sub(r'[^0-9a-zA-Z]+', 'x' , reference)
+ return super(PaymentTransactionSips, self)._get_next_reference(reference, acquirer=acquirer)
+
+
class TxSips(models.Model):
_inherit = 'payment.transaction'
diff --git a/addons/point_of_sale/models/product.py b/addons/point_of_sale/models/product.py
index 9c9af81000d..43a959bcfa4 100644
--- a/addons/point_of_sale/models/product.py
+++ b/addons/point_of_sale/models/product.py
@@ -21,6 +21,11 @@ class ProductTemplate(models.Model):
raise UserError(_('You cannot delete a product saleable in point of sale while a session is still opened.'))
return super(ProductTemplate, self).unlink()
+ @api.onchange('sale_ok')
+ def _onchange_sale_ok(self):
+ if not self.sale_ok:
+ self.available_in_pos = False
+
class UomCateg(models.Model):
_inherit = 'uom.category'
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 7c74af95cf6..63c9e1c0cb0 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -260,6 +260,7 @@ exports.PosModel = Backbone.Model.extend({
// we attribute a role to the user, 'cashier' or 'manager', depending
// on the group the user belongs.
var pos_users = [];
+ var current_cashier = self.get_cashier();
for (var i = 0; i < users.length; i++) {
var user = users[i];
for (var j = 0; j < user.groups_id.length; j++) {
@@ -277,6 +278,8 @@ exports.PosModel = Backbone.Model.extend({
// replace the current user with its updated version
if (user.id === self.user.id) {
self.user = user;
+ }
+ if (user.id === current_cashier.id) {
self.set_cashier(user);
}
}
@@ -630,7 +633,7 @@ exports.PosModel = Backbone.Model.extend({
// changes the current cashier
set_cashier: function(user){
this.set('cashier', user);
- this.db.set_cashier(this.cashier);
+ this.db.set_cashier(this.get('cashier'));
},
//creates a new empty order and sets it as the current order
add_new_order: function(){
diff --git a/addons/portal/views/portal_templates.xml b/addons/portal/views/portal_templates.xml
index 9865d97c87c..9522a1fcda4 100644
--- a/addons/portal/views/portal_templates.xml
+++ b/addons/portal/views/portal_templates.xml
@@ -217,7 +217,7 @@
<div class="input-group">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
- <span id="search_label" class="hidden-xs">Search</span><span class="caret"></span>
+ <span id="search_label" class="hidden-xs mr4">Search</span><span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li t-foreach='searchbar_inputs' t-as='input' t-att-class="search_in == searchbar_inputs[input]['input'] and 'active'">
diff --git a/addons/project/i18n/project.pot b/addons/project/i18n/project.pot
index 5ed93bba242..ae8e38c46ce 100644
--- a/addons/project/i18n/project.pot
+++ b/addons/project/i18n/project.pot
@@ -1021,6 +1021,12 @@ msgid "Ending Date"
msgstr ""
#. module: project
+#: code:addons/project/models/project.py:556
+#, python-format
+msgid "Error! You cannot create recursive hierarchy of task(s)."
+msgstr ""
+
+#. module: project
#: sql_constraint:project.project:0
msgid "Error! project start-date must be lower than project end-date."
msgstr ""
diff --git a/addons/project/models/project.py b/addons/project/models/project.py
index 9c9871a7e89..20578c30c93 100644
--- a/addons/project/models/project.py
+++ b/addons/project/models/project.py
@@ -620,6 +620,12 @@ class Task(models.Model):
default['name'] = _("%s (copy)") % self.name
return super(Task, self).copy(default)
+ @api.constrains('parent_id')
+ def _check_parent_id(self):
+ for task in self:
+ if not task._check_recursion():
+ raise ValidationError(_('Error! You cannot create recursive hierarchy of task(s).'))
+
# Override view according to the company definition
@api.model
def fields_view_get(self, view_id=None, view_type='form', toolbar=False, submenu=False):
@@ -967,6 +973,20 @@ class Task(models.Model):
'type': 'ir.actions.act_window'
}
+ def action_subtask(self):
+ action = self.env.ref('project.project_task_action_sub_task').read()[0]
+ ctx = self.env.context.copy()
+ ctx.update({
+ 'default_parent_id': self.id,
+ 'default_project_id': self.env.context.get('project_id', self.project_id.id),
+ 'default_name': self.env.context.get('name', self.name) + ':',
+ 'default_partner_id': self.env.context.get('partner_id', self.partner_id.id),
+ 'search_default_project_id': self.env.context.get('project_id', self.project_id.id),
+ })
+ action['context'] = ctx
+ action['domain'] = [('id', 'child_of', self.id), ('id', '!=', self.id)]
+ return action
+
# ---------------------------------------------------
# Rating business
# ---------------------------------------------------
diff --git a/addons/project/views/project_portal_templates.xml b/addons/project/views/project_portal_templates.xml
index 2a75caba97f..7273a76faad 100644
--- a/addons/project/views/project_portal_templates.xml
+++ b/addons/project/views/project_portal_templates.xml
@@ -142,7 +142,7 @@
<thead>
<tr class="active">
<th t-if="groupby == 'none'" class="col-md-9">Name</th>
- <th t-else="" class="col-md-9"><span t-field="tasks[0].project_id.label_tasks"/> for project: <span t-field="tasks[0].project_id.name"/></th>
+ <th t-else="" class="col-md-9"><span t-field="tasks[0].sudo().project_id.label_tasks"/> for project: <span t-field="tasks[0].sudo().project_id.name"/></th>
<th class="col-md-2">Stage</th>
<th>Ref</th>
</tr>
diff --git a/addons/project/views/project_views.xml b/addons/project/views/project_views.xml
index 4ec031f7aa7..7bb76b1af98 100644
--- a/addons/project/views/project_views.xml
+++ b/addons/project/views/project_views.xml
@@ -84,14 +84,6 @@
<field name="name">Sub-tasks</field>
<field name="res_model">project.task</field>
<field name="view_mode">kanban,tree,form,calendar,pivot,graph</field>
- <field name="domain">[('id', 'child_of', active_id), ('id', '!=', active_id)]</field>
- <field name="context">
- {
- 'default_parent_id' : active_id,
- 'default_project_id' : project_id,
- 'default_name' : name + ': ',
- }
- </field>
<field name="search_view_id" ref="project.view_task_search_form"/>
</record>
@@ -419,7 +411,7 @@
<sheet string="Task">
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" icon="fa-tasks" type="object" name="action_open_parent_task" string="Parent Task" attrs="{'invisible' : [('parent_id', '=', False)]}" groups="project.group_subtask_project"/>
- <button id="subtask_stat_button" name="%(project_task_action_sub_task)d" type="action" class="oe_stat_button" icon="fa-tasks"
+ <button name="action_subtask" type="object" class="oe_stat_button" icon="fa-tasks"
attrs="{'invisible' : [('parent_id', '!=', False)]}"
context="{'project_id': project_id, 'name': name, 'parent_id': id}" groups="project.group_subtask_project">
<field string="Sub-tasks" name="subtask_count" widget="statinfo"/>
diff --git a/addons/purchase/models/account_invoice.py b/addons/purchase/models/account_invoice.py
index 877ba90d0cf..23d40d15452 100644
--- a/addons/purchase/models/account_invoice.py
+++ b/addons/purchase/models/account_invoice.py
@@ -170,8 +170,10 @@ class AccountInvoice(models.Model):
#for average/fifo/lifo costing method, fetch real cost price from incomming moves
valuation_price_unit = i_line.purchase_line_id.product_uom._compute_price(i_line.purchase_line_id.price_unit, i_line.uom_id)
stock_move_obj = self.env['stock.move']
- valuation_stock_move = stock_move_obj.search([('purchase_line_id', '=', i_line.purchase_line_id.id), ('state', '=', 'done')])
-
+ valuation_stock_move = stock_move_obj.search([
+ ('purchase_line_id', '=', i_line.purchase_line_id.id),
+ ('state', '=', 'done'), ('product_qty', '!=', 0.0)
+ ])
if self.type == 'in_refund':
valuation_stock_move = valuation_stock_move.filtered(lambda m: m._is_out())
elif self.type == 'in_invoice':
diff --git a/addons/sale_payment/models/payment.py b/addons/sale_payment/models/payment.py
index 831af37550f..f710b63630f 100644
--- a/addons/sale_payment/models/payment.py
+++ b/addons/sale_payment/models/payment.py
@@ -182,7 +182,7 @@ class PaymentTransaction(models.Model):
'currency_id': order.pricelist_id.currency_id.id,
'partner_id': order.partner_id.id,
'partner_country_id': order.partner_id.country_id.id,
- 'reference': self.get_next_reference(order.name),
+ 'reference': self._get_next_reference(order.name, acquirer=acquirer),
'sale_order_id': order.id,
}
if add_tx_values:
diff --git a/addons/stock/models/stock_move.py b/addons/stock/models/stock_move.py
index fdbf05f1866..e621a86c61a 100644
--- a/addons/stock/models/stock_move.py
+++ b/addons/stock/models/stock_move.py
@@ -425,10 +425,10 @@ class StockMove(models.Model):
if propagated_date_field:
current_date = datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
new_date = datetime.strptime(vals.get(propagated_date_field), DEFAULT_SERVER_DATETIME_FORMAT)
- delta = relativedelta.relativedelta(new_date, current_date)
- if abs(delta.days) >= move.company_id.propagation_minimum_delta:
+ delta_days = (new_date - current_date).total_seconds() / 86400
+ if abs(delta_days) >= move.company_id.propagation_minimum_delta:
old_move_date = datetime.strptime(move.move_dest_ids[0].date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
- new_move_date = (old_move_date + relativedelta.relativedelta(days=delta.days or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+ new_move_date = (old_move_date + relativedelta.relativedelta(days=delta_days or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
propagated_changes_dict['date_expected'] = new_move_date
#For pushed moves as well as for pulled moves, propagate by recursive call of write().
#Note that, for pulled moves we intentionally don't propagate on the procurement.
diff --git a/addons/stock_account/models/stock.py b/addons/stock_account/models/stock.py
index 039a934d1bf..a0764069865 100644
--- a/addons/stock_account/models/stock.py
+++ b/addons/stock_account/models/stock.py
@@ -372,6 +372,7 @@ class StockMove(models.Model):
product_tot_qty_available = move.product_id.qty_available + tmpl_dict[move.product_id.id]
rounding = move.product_id.uom_id.rounding
+ qty_done = 0.0
if float_is_zero(product_tot_qty_available, precision_rounding=rounding):
new_std_price = move._get_price_unit()
elif float_is_zero(product_tot_qty_available + move.product_qty, precision_rounding=rounding):
@@ -379,10 +380,11 @@ class StockMove(models.Model):
else:
# Get the standard price
amount_unit = std_price_update.get((move.company_id.id, move.product_id.id)) or move.product_id.standard_price
- qty = forced_qty or move.product_qty
- new_std_price = ((amount_unit * product_tot_qty_available) + (move._get_price_unit() * qty)) / (product_tot_qty_available + move.product_qty)
+ qty_done = move.product_uom._compute_quantity(move.quantity_done, move.product_id.uom_id)
+ qty = forced_qty or qty_done
+ new_std_price = ((amount_unit * product_tot_qty_available) + (move._get_price_unit() * qty)) / (product_tot_qty_available + qty_done)
- tmpl_dict[move.product_id.id] += move.product_qty
+ tmpl_dict[move.product_id.id] += qty_done
# Write the standard price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products
move.product_id.with_context(force_company=move.company_id.id).sudo().write({'standard_price': new_std_price})
std_price_update[move.company_id.id, move.product_id.id] = new_std_price
diff --git a/addons/stock_account/tests/test_stockvaluation.py b/addons/stock_account/tests/test_stockvaluation.py
index 0123322c656..1679ba51368 100644
--- a/addons/stock_account/tests/test_stockvaluation.py
+++ b/addons/stock_account/tests/test_stockvaluation.py
@@ -2522,6 +2522,39 @@ class TestStockValuation(TransactionCase):
move2.move_line_ids.qty_done = 0
self.assertEqual(self.product1.stock_value, -187.5)
+ def test_average_perpetual_4(self):
+ self.product1.product_tmpl_id.cost_method = 'average'
+
+ move1 = self.env['stock.move'].create({
+ 'name': 'Receive 1 unit at 10',
+ 'location_id': self.supplier_location.id,
+ 'location_dest_id': self.stock_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 1.0,
+ 'price_unit': 10,
+ })
+ move1._action_confirm()
+ move1._action_assign()
+ move1.move_line_ids.qty_done = 1.0
+ move1._action_done()
+
+ move2 = self.env['stock.move'].create({
+ 'name': 'Receive 3 units at 5',
+ 'location_id': self.supplier_location.id,
+ 'location_dest_id': self.stock_location.id,
+ 'product_id': self.product1.id,
+ 'product_uom': self.uom_unit.id,
+ 'product_uom_qty': 3.0,
+ 'price_unit': 5,
+ })
+ move2._action_confirm()
+ move2._action_assign()
+ move2.move_line_ids.qty_done = 1.0
+ move2._action_done()
+
+ self.assertAlmostEqual(self.product1.standard_price, 7.5)
+
def test_average_negative_1(self):
""" Test edit in the past. Receive 10, send 20, edit the second move to only send 10.
"""
diff --git a/addons/test_mail/data/test_mail_data.py b/addons/test_mail/data/test_mail_data.py
index ef55fb82be9..adf963fcd7b 100644
--- a/addons/test_mail/data/test_mail_data.py
+++ b/addons/test_mail/data/test_mail_data.py
@@ -432,3 +432,245 @@ Web: <a class="moz-txt-link-freetext" href="http://www.odoo.com">http://www.odoo
--------------1F2D18B1129FC2F0B9EECF50--
--------------A6B5FD5F68F4D73ECD739009--"""
+
+
+MAIL_XHTML = """Return-Path: <xxxx@xxxx.com>
+Received: from xxxx.internal (xxxx.xxxx.internal [1.1.1.1])
+ by xxxx (xxxx 1.1.1-111-g972eecc-slipenbois) with LMTPA;
+ Fri, 13 Apr 2018 22:11:52 -0400
+X-Cyrus-Session-Id: sloti35d1t38-1111111-11111111111-5-11111111111111111111
+X-Sieve: CMU Sieve 1.0
+X-Spam-known-sender: no ("Email failed DMARC policy for domain"); in-addressbook
+X-Spam-score: 0.0
+X-Spam-hits: ALL_TRUSTED -1, BAYES_00 -1.9, FREEMAIL_FROM 0.001,
+ HTML_FONT_LOW_CONTRAST 0.001, HTML_MESSAGE 0.001, SPF_SOFTFAIL 0.665,
+ LANGUAGES en, BAYES_USED global, SA_VERSION 1.1.0
+X-Spam-source: IP='1.1.1.1', Host='unk', Country='unk', FromHeader='com',
+ MailFrom='com'
+X-Spam-charsets: plain='utf-8', html='utf-8'
+X-IgnoreVacation: yes ("Email failed DMARC policy for domain")
+X-Resolved-to: catchall@xxxx.xxxx
+X-Delivered-to: catchall@xxxx.xxxx
+X-Mail-from: xxxx@xxxx.com
+Received: from mx4 ([1.1.1.1])
+ by xxxx.internal (LMTPProxy); Fri, 13 Apr 2018 22:11:52 -0400
+Received: from xxxx.xxxx.com (localhost [127.0.0.1])
+ by xxxx.xxxx.internal (Postfix) with ESMTP id E1111C1111;
+ Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
+Received: from xxxx.xxxx.internal (localhost [127.0.0.1])
+ by xxxx.xxxx.com (Authentication Milter) with ESMTP
+ id BBDD1111D1A;
+ Fri, 13 Apr 2018 22:11:51 -0400
+ARC-Authentication-Results: i=1; xxxx.xxxx.com; arc=none (no signatures found);
+ dkim=pass (2048-bit rsa key sha256) header.d=xxxx.com header.i=@xxxx.com header.b=P1aaAAaa x-bits=2048 x-keytype=rsa x-algorithm=sha256 x-selector=fm2;
+ dmarc=fail (p=none,d=none) header.from=xxxx.com;
+ iprev=pass policy.iprev=1.1.1.1 (out1-smtp.xxxx.com);
+ spf=softfail smtp.mailfrom=xxxx@xxxx.com smtp.helo=out1-smtp.xxxx.com;
+ x-aligned-from=pass (Address match);
+ x-cm=none score=0;
+ x-ptr=pass x-ptr-helo=out1-smtp.xxxx.com x-ptr-lookup=out1-smtp.xxxx.com;
+ x-return-mx=pass smtp.domain=xxxx.com smtp.result=pass smtp_is_org_domain=yes header.domain=xxxx.com header.result=pass header_is_org_domain=yes;
+ x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128;
+ x-vs=clean score=0 state=0
+Authentication-Results: xxxx.xxxx.com;
+ arc=none (no signatures found);
+ dkim=pass (2048-bit rsa key sha256) header.d=xxxx.com header.i=@xxxx.com header.b=P1awJPiy x-bits=2048 x-keytype=rsa x-algorithm=sha256 x-selector=fm2;
+ dmarc=fail (p=none,d=none) header.from=xxxx.com;
+ iprev=pass policy.iprev=66.111.4.25 (out1-smtp.xxxx.com);
+ spf=softfail smtp.mailfrom=xxxx@xxxx.com smtp.helo=out1-smtp.xxxx.com;
+ x-aligned-from=pass (Address match);
+ x-cm=none score=0;
+ x-ptr=pass x-ptr-helo=out1-smtp.xxxx.com x-ptr-lookup=out1-smtp.xxxx.com;
+ x-return-mx=pass smtp.domain=xxxx.com smtp.result=pass smtp_is_org_domain=yes header.domain=xxxx.com header.result=pass header_is_org_domain=yes;
+ x-tls=pass version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128;
+ x-vs=clean score=0 state=0
+X-ME-VSCategory: clean
+X-ME-CMScore: 0
+X-ME-CMCategory: none
+Received-SPF: softfail
+ (gmail.com ... _spf.xxxx.com: Sender is not authorized by default to use 'xxxx@xxxx.com' in 'mfrom' identity, however domain is not currently prepared for false failures (mechanism '~all' matched))
+ receiver=xxxx.xxxx.com;
+ identity=mailfrom;
+ envelope-from="xxxx@xxxx.com";
+ helo=out1-smtp.xxxx.com;
+ client-ip=1.1.1.1
+Received: from xxxx.xxxx.internal (gateway1.xxxx.internal [1.1.1.1])
+ (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits))
+ (No client certificate requested)
+ by xxxx.xxxx.internal (Postfix) with ESMTPS;
+ Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
+Received: from compute3.internal (xxxx.xxxx.internal [10.202.2.43])
+ by xxxx.xxxx.internal (Postfix) with ESMTP id 8BD5B21BBD;
+ Fri, 13 Apr 2018 22:11:51 -0400 (EDT)
+Received: from xxxx ([10.202.2.163])
+ by xxxx.internal (MEProxy); Fri, 13 Apr 2018 22:11:51 -0400
+X-ME-Sender: <xms:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
+Received: from [1.1.1.1] (unknown [1.1.1.1])
+ by mail.xxxx.com (Postfix) with ESMTPA id BF5E1111D
+ for <catchall@xxxx.xxxx>; Fri, 13 Apr 2018 22:11:50 -0400 (EDT)
+From: "xxxx xxxx" <xxxx@xxxx.com>
+To: "xxxx" <catchall@xxxx.xxxx>
+Subject: Re: xxxx (Ref PO1)
+Date: Sat, 14 Apr 2018 02:11:42 +0000
+Message-Id: <em67f5c44a-xxxx-xxxx-xxxx-69f56d618a94@wswin7hg4n4l1ce>
+In-Reply-To: <829228111124527.1111111602.256611118262939-openerp-129-xxxx.xxxx@ip-1-1-1-1>
+References: <867911111953277.1523671337.187951111160400-openerp-129-xxxx.xxxx@ip-1-1-1-1>
+ <867911111953277.1523671337.256611118262939-openerp-129-xxxx.xxxx@ip-1-1-1-1>
+Reply-To: "xxxx xxxx" <xxxx@xxxx.com>
+User-Agent: eM_Client/7.0.26687.0
+Mime-Version: 1.0
+Content-Type: multipart/alternative;
+ boundary="------=_MB48E455BD-1111-42EC-1111-886CDF48905E"
+
+--------=_MB48E455BD-1111-42EC-1111-886CDF48905E
+Content-Type: text/plain; format=flowed; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+xxxx
+
+
+------ Original Message ------
+From: "xxxx" <xxxx@xxxx.com>
+To: "xxxx" <xxxx@xxxx.com>
+Sent: 4/13/2018 7:06:43 PM
+Subject: xxxx
+
+>xxxx
+
+--------=_MB48E455BD-1111-42EC-1111-886CDF48905E
+Content-Type: text/html; charset=utf-8
+Content-Transfer-Encoding: quoted-printable
+
+<?xml version=3D"1.0" encoding=3D"utf-16"?><html><head><style type=3D"text/=
+css"><!--blockquote.cite
+{margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:=
+ 0px; border-left-width: 1px; border-left-style: solid; border-left-color:=
+ rgb(204, 204, 204);}
+blockquote.cite2
+{margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:=
+ 0px; border-left-width: 1px; border-left-style: solid; border-left-color:=
+ rgb(204, 204, 204); margin-top: 3px; padding-top: 0px;}
+a img
+{border: 0px;}
+body
+{font-family: Tahoma; font-size: 12pt;}
+--></style></head><body><div>this is a reply to PO200109 from emClient</div=
+><div id=3D"signature_old"><div style=3D"font-family: Tahoma; font-size:=
+ 12 pt;">-- <br /><span><span class=3D"__postbox-detected-content __postbox=
+-detected-address" style=3D"TEXT-DECORATION: underline; COLOR: rgb(115,133,=
+172); PADDING-BOTTOM: 0pt; PADDING-TOP: 0pt; PADDING-LEFT: 0pt; DISPLAY:=
+ inline; PADDING-RIGHT: 0pt" __postbox-detected-content=3D"__postbox-detect=
+ed-address"></span>xxxx<br />xxxx<br /><b=
+r />xxxx</span></=
+div></div><div><br /></div><div><br /></div><div><br /></div>
+<div>------ Original Message ------</div>
+<div>From: "xxxx" &lt;<a href=3D"mailto:xxxx@xxxx.com">xxxx=
+@xxxx.com</a>&gt;</div>
+<div>To: "xxxx" &lt;<a href=3D"mailto:xxxx@xxxx.com">a=
+xxxx@xxxx.com</a>&gt;</div>
+<div>Sent: 4/13/2018 7:06:43 PM</div>
+<div>Subject: xxxx</div><div><br /></div=
+>
+<div id=3D"x00b4101ba6e64ce"><blockquote cite=3D"829228972724527.1523671602=
+.256660938262939-openerp-129-xxxx.xxxx@ip-1-1-1-1" type=3D"cite"=
+ class=3D"cite2">
+<table border=3D"0" width=3D"100%" cellpadding=3D"0" bgcolor=3D"#ededed"=
+ style=3D"padding: 20px; background-color: #ededed" summary=3D"o_mail_notif=
+ication">
+ <tbody>
+
+ <!-- HEADER -->
+ <tr>
+ <td align=3D"center" style=3D"min-width: 590px;">
+ <table width=3D"590" border=3D"0" cellpadding=3D=
+"0" bgcolor=3D"#875A7B" style=3D"min-width: 590px; background-color: rgb(13=
+5,90,123); padding: 20px;">
+ <tbody><tr>
+ <td valign=3D"middle">
+ <span style=3D"font-size:20px; color:whit=
+e; font-weight: bold;">
+ mangez des saucisses
+ </span>
+ </td>
+ <td valign=3D"middle" align=3D"right">
+ <img src=3D"http://erp.xxxx.xxxx/logo.png=
+" style=3D"padding: 0px; margin: 0px; height: auto; width: 80px;" alt=3D=
+"xxxx" />
+ </td>
+ </tr>
+ </tbody></table>
+ </td>
+ </tr>
+
+ <!-- CONTENT -->
+ <tr>
+ <td align=3D"center" style=3D"min-width: 590px;">
+ <table width=3D"590" border=3D"0" cellpadding=3D=
+"0" bgcolor=3D"#ffffff" style=3D"min-width: 590px; background-color: rgb(25=
+5, 255, 255); padding: 20px;">
+ <tbody>
+ <tr><td valign=3D"top" style=3D"font-family:A=
+rial,Helvetica,sans-serif; color: #555; font-size: 14px;">
+ <p style=3D"margin: 0px 0px 9px 0px; font-s=
+ize: 13px; font-family: &quot;Lucida Grande&quot;, Helvetica, Verdana, Aria=
+l, sans-serif">xxxx.=20
+,</p>
+<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
+cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">
+xxxx.
+</p>
+
+<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
+cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">You can reply =
+to this email if you have any questions.</p>
+<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: &quot;Lu=
+cida Grande&quot;, Helvetica, Verdana, Arial, sans-serif">Thank you,</p>
+ </td>
+ </tr></tbody>
+ </table>
+ </td>
+ </tr>
+
+ <!-- FOOTER -->
+ <tr>
+ <td align=3D"center" style=3D"min-width: 590px;">
+ <table width=3D"590" border=3D"0" cellpadding=3D=
+"0" bgcolor=3D"#875A7B" style=3D"min-width: 590px; background-color: rgb(13=
+5,90,123); padding: 20px;">
+ <tbody><tr>
+ <td valign=3D"middle" align=3D"left" style=
+=3D"color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"=
+>
+ xxxx<br />
+ +1-801-980-4240
+ </td>
+ <td valign=3D"middle" align=3D"right" style=
+=3D"color: #fff; padding-top: 10px; padding-bottom: 10px; font-size: 12px;"=
+>
+ <a href=3D"http://erp.xxxx.xxxx/info@xxxx-a=
+aa.com" style=3D"text-decoration:none; color: white;">info@aust-mfg.com</a>=
+<br />
+ <a href=3D"http://www.xxxx=
+.com" style=3D"text-decoration:none; color: white;">
+ http://www.xxxx.com
+ </a>
+ </td>
+ </tr>
+ </tbody></table>
+ </td>
+ </tr>
+ <tr>
+ <td align=3D"center">
+ Powered by <a href=3D"https://www.odoo.com">Odo=
+o</a>.
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ =20
+ <pre style=3D"white-space: pre-wrap">xxxx.
+</pre>
+</blockquote></div>
+</body></html>
+--------=_MB48E455BD-2850-42EC-B1CA-886CDF48905E--"""
+
diff --git a/addons/test_mail/tests/test_mail_gateway.py b/addons/test_mail/tests/test_mail_gateway.py
index f07a21bcaf6..327a0c7f007 100644
--- a/addons/test_mail/tests/test_mail_gateway.py
+++ b/addons/test_mail/tests/test_mail_gateway.py
@@ -7,7 +7,7 @@ from email.utils import formataddr
from odoo.addons.test_mail.data.test_mail_data import \
MAIL_TEMPLATE, MAIL_TEMPLATE_PLAINTEXT, MAIL_MULTIPART_MIXED, MAIL_MULTIPART_MIXED_TWO, \
- MAIL_MULTIPART_IMAGE, MAIL_SINGLE_BINARY, MAIL_EML_ATTACHMENT
+ MAIL_MULTIPART_IMAGE, MAIL_SINGLE_BINARY, MAIL_EML_ATTACHMENT, MAIL_XHTML
from odoo.addons.test_mail.tests.common import BaseFunctionalTest, MockEmails
from odoo.tools import mute_logger
@@ -85,6 +85,12 @@ class TestMailgateway(BaseFunctionalTest, MockEmails):
self.env['mail.thread'].message_process('mail.channel', MAIL_EML_ATTACHMENT)
@mute_logger('odoo.addons.mail.models.mail_thread')
+ def test_message_parse_xhtml(self):
+ """ Test that the parsing of mail with embedded emails as eml(msg) which generates empty attachments, can be processed.
+ """
+ self.env['mail.thread'].message_process('mail.channel', MAIL_XHTML)
+
+ @mute_logger('odoo.addons.mail.models.mail_thread')
def test_message_process_cid(self):
record = self.format_and_process(MAIL_MULTIPART_IMAGE, to='groups@example.com')
message = record.message_ids[0]
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 709fbb61d9c..76baf196c2d 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -1116,7 +1116,7 @@ class Binary(http.Controller):
if request.httprequest.user_agent.browser == 'safari':
# Safari sends NFD UTF-8 (where é is composed by 'e' and [accent])
# we need to send it the same stuff, otherwise it'll fail
- filename = unicodedata.normalize('NFD', ufile.filename).encode('UTF-8')
+ filename = unicodedata.normalize('NFD', ufile.filename)
try:
attachment = Model.create({
diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js
index d7cc08ea974..9c38ecc3a58 100644
--- a/addons/web/static/src/js/views/basic/basic_model.js
+++ b/addons/web/static/src/js/views/basic/basic_model.js
@@ -4137,10 +4137,20 @@ var BasicModel = AbstractModel.extend({
var r2 = self.localData[record2ID];
var data1 = _.extend({}, r1.data, r1._changes);
var data2 = _.extend({}, r2.data, r2._changes);
- if (data1[order.name] < data2[order.name]) {
+
+ // Default value to sort against: the value of the field
+ var orderData1 = data1[order.name];
+ var orderData2 = data2[order.name];
+
+ // If the field is a relation, sort on the display_name of those records
+ if (list.fields[order.name].type === 'many2one') {
+ orderData1 = orderData1 ? self.localData[orderData1].data.display_name : "";
+ orderData2 = orderData2 ? self.localData[orderData2].data.display_name : "";
+ }
+ if (orderData1 < orderData2) {
return order.asc ? -1 : 1;
}
- if (data1[order.name] > data2[order.name]) {
+ if (orderData1 > orderData2) {
return order.asc ? 1 : -1;
}
return compareRecords(record1ID, record2ID, level + 1);
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index b8358796b7f..d33da5c0650 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -2272,6 +2272,43 @@ QUnit.module('relational_fields', {
form.destroy();
});
+ QUnit.test('x2many list sorted by many2one', function (assert) {
+ assert.expect(3);
+
+ this.data.partner.records[0].p = [1, 2, 4];
+ this.data.partner.fields.trululu.sortable = true;
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ arch: '<form>' +
+ '<field name="p">' +
+ '<tree>' +
+ '<field name="id"/>' +
+ '<field name="trululu"/>' +
+ '</tree>' +
+ '</field>' +
+ '</form>',
+ res_id: 1,
+ });
+
+ assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '124',
+ "should have correct order initially");
+
+ form.$('.o_list_view thead th:nth(1)').click();
+
+ assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '412',
+ "should have correct order (ASC)");
+
+ form.$('.o_list_view thead th:nth(1)').click();
+
+ assert.strictEqual(form.$('.o_data_row .o_list_number').text(), '214',
+ "should have correct order (DESC)");
+
+ form.destroy();
+ });
+
QUnit.test('many2many list add *many* records, remove, re-add', function (assert) {
assert.expect(5);
diff --git a/addons/web/static/tests/helpers/test_utils.js b/addons/web/static/tests/helpers/test_utils.js
index f993a2f9250..73366253865 100644
--- a/addons/web/static/tests/helpers/test_utils.js
+++ b/addons/web/static/tests/helpers/test_utils.js
@@ -730,53 +730,6 @@ function patch (target, props) {
});
}
}
-/**
- * In Phantomjs, there is a crash when calling window.getSelection
- * in order for the tests to work, for the specific test that uses it, replace
- * the default window.getSelection by a mock
- *
- * usage:
- * QUnit.test('...',function(done){
- * var unpatchWindowGetSelection = testUtils.patchWindowGetSelection();
- *
- * // do something that needs to use window.getSelection()
- * assert.strictEqual(....);
- *
- * // restore the original function
- * unpatchWindowGetSelection();
- *
- * // finish the test
- * done();
- * })
- *
- * @returns {function} the unpatch function
- */
-function patchWindowGetSelection() {
- var originalWindowGetSelection = window.getSelection;
- window.getSelection = function () {
- return {
- removeAllRanges: function () {},
- addRange: function (range) {},
- getRangeAt: function (index) {
- return {
- startOffset : 0
- };
- },
- anchorNode: {
- parentNode: {
- childNodes: [{
- outerHTML: "@",
- nodeType: 3,
- textContent: '@',
- }],
- },
- },
- };
- };
- return function () {
- window.getSelection = originalWindowGetSelection;
- };
-}
/**
* Unpatches a given Class or Object.
@@ -828,7 +781,6 @@ return $.when(
intercept: intercept,
observe: observe,
patch: patch,
- patchWindowGetSelection: patchWindowGetSelection,
removeSrcAttribute: removeSrcAttribute,
triggerKeypressEvent: triggerKeypressEvent,
triggerMouseEvent: triggerMouseEvent,
diff --git a/addons/website_event/controllers/main.py b/addons/website_event/controllers/main.py
index b9c08b8232e..38bd5b0ce7f 100644
--- a/addons/website_event/controllers/main.py
+++ b/addons/website_event/controllers/main.py
@@ -62,7 +62,6 @@ class WebsiteEventController(http.Controller):
]
# search domains
- # TDE note: WTF ???
current_date = None
current_type = None
current_country = None
@@ -117,9 +116,12 @@ class WebsiteEventController(http.Controller):
step=step,
scope=5)
- order = 'website_published desc, date_begin'
+ order = 'date_begin'
if searches.get('date', 'all') == 'old':
- order = 'website_published desc, date_begin desc'
+ order = 'date_begin desc'
+ if searches["country"] != 'all': # if we are looking for a specific country
+ order = 'is_online, ' + order # show physical events first
+ order = 'website_published desc, ' + order
events = Event.search(dom_without("none"), limit=step, offset=pager['offset'], order=order)
values = {
diff --git a/addons/website_event_sale/controllers/main.py b/addons/website_event_sale/controllers/main.py
index 7ccb7f9e4b5..d9af6bd312e 100644
--- a/addons/website_event_sale/controllers/main.py
+++ b/addons/website_event_sale/controllers/main.py
@@ -39,7 +39,7 @@ class WebsiteEventSaleController(WebsiteEventController):
# free tickets -> order with amount = 0: auto-confirm, no checkout
if not order.amount_total:
order.action_confirm() # tde notsure: email sending ?
- attendees = request.env['event.registration'].browse(list(attendee_ids))
+ attendees = request.env['event.registration'].browse(list(attendee_ids)).sudo()
# clean context and session, then redirect to the confirmation page
request.website.sale_reset()
return request.render("website_event.registration_complete", {
diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml
index 0e954536d42..dd27b8312b9 100644
--- a/addons/website_sale/views/templates.xml
+++ b/addons/website_sale/views/templates.xml
@@ -1099,7 +1099,7 @@
</t>
<input type='submit'/>
</form>
- <a t-if="not actual_partner or (ship.id in actual_partner.ids + actual_partner.child_ids.ids)" class='btn btn-link pull-right fa fa-edit js_edit_address no-decoration' title="Edit this address"></a>
+ <a t-if="not actual_partner or (ship.id in actual_partner.ids + actual_partner.child_ids.ids)" class='btn btn-link pull-right js_edit_address no-decoration' title="Edit this address"><i class='fa fa-edit'/></a>
<div t-att-class="'panel panel-default %s' % (selected and 'border_primary' or 'js_change_shipping')">
<div class='panel-body' style='min-height: 130px;'>
<t t-esc="contact" t-options="dict(widget='contact', fields=['name', 'address'], no_marker=True)"/>
diff --git a/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js b/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js
index d5e7266930c..f313a37dfa3 100644
--- a/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js
+++ b/addons/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js
@@ -147,12 +147,22 @@ var PDFSlidesViewer = (function(){
PDFSlidesViewer.prototype.toggleFullScreenFooter = function(){
if(document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement) {
- $('div#PDFViewer > div.navbar-fixed-bottom').toggleClass('oe_show_footer');
+ var $navBarFooter = $('div#PDFViewer > div.navbar-fixed-bottom');
+ $navBarFooter.toggleClass('oe_show_footer');
+
+ if ($navBarFooter.css('display') === 'none') {
+ $navBarFooter.css('display', 'block');
+ $('div#PDFViewer').css('padding-bottom', '50px');
+ } else {
+ $navBarFooter.css('display', 'none');
+ $('div#PDFViewer').css('padding-bottom', '0');
+ }
}
}
PDFSlidesViewer.prototype.toggleFullScreen = function(){
- var el = this.canvas.parentNode;
+ // The canvas and the navigation bar needs to be fullscreened
+ var el = this.canvas.parentNode.parentNode;
var isFullscreenAvailable = document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled || document.msFullscreenEnabled || false;
if(isFullscreenAvailable){ // Full screen supported
diff --git a/odoo/addons/base/models/res_users.py b/odoo/addons/base/models/res_users.py
index efcf203edf0..02f9d90562c 100644
--- a/odoo/addons/base/models/res_users.py
+++ b/odoo/addons/base/models/res_users.py
@@ -14,6 +14,7 @@ from hashlib import sha256
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationError
+from odoo.http import request
from odoo.osv import expression
from odoo.service.db import check_super
from odoo.tools import partition, pycompat
@@ -466,8 +467,12 @@ class Users(models.Model):
user.sudo(user_id).check_credentials(password)
user.sudo(user_id)._update_last_login()
except AccessDenied:
- _logger.info("Login failed for db:%s login:%s", db, login)
user_id = False
+
+ status = "successful" if user_id else "failed"
+ ip = request.httprequest.environ['REMOTE_ADDR'] if request else 'n/a'
+ _logger.info("Login %s for db:%s login:%s from %s", status, db, login, ip)
+
return user_id
@classmethod
diff --git a/odoo/addons/base/wizard/base_partner_merge.py b/odoo/addons/base/wizard/base_partner_merge.py
index e8d955f4e6b..2b91afd3e61 100644
--- a/odoo/addons/base/wizard/base_partner_merge.py
+++ b/odoo/addons/base/wizard/base_partner_merge.py
@@ -223,7 +223,7 @@ class MergePartnerAutomatic(models.TransientModel):
"""
_logger.debug('_update_values for dst_partner: %s for src_partners: %r', dst_partner.id, src_partners.ids)
- model_fields = dst_partner._fields
+ model_fields = dst_partner.fields_get().keys()
def write_serializer(item):
if isinstance(item, models.BaseModel):
@@ -232,7 +232,8 @@ class MergePartnerAutomatic(models.TransientModel):
return item
# get all fields that are not computed or x2many
values = dict()
- for column, field in model_fields.items():
+ for column in model_fields:
+ field = dst_partner._fields[column]
if field.type not in ('many2many', 'one2many') and field.compute is None:
for item in itertools.chain(src_partners, [dst_partner]):
if item[column]:
diff --git a/odoo/tests/common.py b/odoo/tests/common.py
index 3c4344b3a1e..e2fe271c5a1 100644
--- a/odoo/tests/common.py
+++ b/odoo/tests/common.py
@@ -211,16 +211,16 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
yield
count = self.cr.sql_log_count - count0
if count > (expected + margin):
- msg = "Too much query count: user %s: got %d instead of %d (margin %s)"
- self.fail(msg % (login, count, expected, margin))
+ msg = "Query count beyond margin for user %s: %d > %d"
+ self.fail(msg % (login, count, expected + margin))
elif count > expected and count <= (expected + margin):
logger = logging.getLogger(type(self).__module__)
- msg = "Query count greater but still in margin : user %s: got %d instead of %d (margin %s)"
- logger.warn(msg, login, count, expected, margin)
+ msg = "Query count within margin for user %s: %d > %d (margin %s)"
+ logger.info(msg, login, count, expected, margin)
elif count < expected:
logger = logging.getLogger(type(self).__module__)
- msg = "Better query count: user %s: got %d instead of %d (margin %s)"
- logger.info(msg, login, count, expected, margin)
+ msg = "Query count less than expected for user %s: %d < %d"
+ logger.info(msg, login, count, expected)
else:
yield