diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-05-17 14:23:37 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-05-17 14:23:37 +0200 |
| commit | 644f0959d05d6467260f685ec3ac369e6cf81457 (patch) | |
| tree | 84eabeb182621c974afc85355c0ff5cab6e7d7d7 | |
| parent | 87818f7fccc045f6011ab8f4f96c447740d6f41e (diff) | |
| parent | 16552029241b3a7a3d9a40b8b7399da6ee415be4 (diff) | |
[MERGE] forward port branch saas-11.2 up to 16552029241
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+'[^ ]+(?= | )', 'g')); - return this._validate_selection(listener.selection, inputMentions); + var input_mentions = this.composer.$input.val().match(new RegExp(delimiter+'[^ ]+(?= | )', '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" <<a href=3D"mailto:xxxx@xxxx.com">xxxx= +@xxxx.com</a>></div> +<div>To: "xxxx" <<a href=3D"mailto:xxxx@xxxx.com">a= +xxxx@xxxx.com</a>></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: "Lucida Grande", Helvetica, Verdana, Aria= +l, sans-serif">xxxx.=20 +,</p> +<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: "Lu= +cida Grande", Helvetica, Verdana, Arial, sans-serif"> +xxxx. +</p> + +<p style=3D"margin: 0px 0px 9px 0px; font-size: 13px; font-family: "Lu= +cida Grande", 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: "Lu= +cida Grande", 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 |
