[MERGE] forward port branch saas-11.2 up to 1655202924

This commit is contained in:
Christophe Simonis
2018-05-17 14:23:37 +02:00
123 changed files with 672 additions and 439 deletions
+1 -1
View File
@@ -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'),
+2 -2
View File
@@ -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
@@ -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){
@@ -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)
+1 -1
View File
@@ -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:
-1
View File
@@ -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:
@@ -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:
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-2
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-2
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-2
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
-1
View File
@@ -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"
+1
View File
@@ -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:
+2 -1
View File
@@ -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,
+5
View File
@@ -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:
+47 -3
View File
@@ -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),
@@ -114,13 +119,52 @@ class EventMailScheduler(models.Model):
self.write({'mail_sent': True})
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
+1 -1
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_event_event_user event.event.user model_event_event event.group_event_user 1 0 0 0
6 access_event_event_manager event.event.manager model_event_event event.group_event_manager 1 1 1 1
7 access_event_registration event.registration model_event_registration event.group_event_user 1 1 1 1
8 access_event_registration_portal event.registration model_event_registration 1 0 0 0 0
9 access_event_mail event.mail model_event_mail event.group_event_user 1 0 0 0
10 access_event_mail_manager event.mail manager model_event_mail event.group_event_manager 1 1 1 1
11 access_event_mail_registration event.mail.registration model_event_mail_registration event.group_event_user 1 0 0 0
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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):
+2 -6
View File
@@ -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
@@ -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
+3 -1
View File
@@ -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
+2 -2
View File
@@ -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,
@@ -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',
+10 -1
View File
@@ -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']
+7 -1
View File
@@ -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():
+2 -2
View File
@@ -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();
});
},
/**
@@ -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,
+78 -178
View File
@@ -7,6 +7,7 @@ var utils = require('mail.utils');
var config = require('web.config');
var core = require('web.core');
var data = require('web.data');
var dom = require('web.dom');
var session = require('web.session');
var Widget = require('web.Widget');
@@ -29,7 +30,7 @@ var MentionManager = Widget.extend({
events: {
"mouseover .o_mention_proposition": "on_hover_mention_proposition",
"click .o_mention_proposition": "_onClickMentionItem",
"click .o_mention_proposition": "on_click_mention_item",
},
init: function (parent, options) {
@@ -52,6 +53,49 @@ var MentionManager = Widget.extend({
this.$('.o_mention_proposition').removeClass('active');
$elem.addClass('active');
},
on_click_mention_item: function (event) {
event.preventDefault();
var text_input = this.composer.$input.val();
var id = $(event.currentTarget).data('id');
var selected_suggestion = _.find(_.flatten(this.get('mention_suggestions')), function (s) {
return s.id === id;
});
var substitution = selected_suggestion.substitution;
if (!substitution) { // no substitution string given, so use the mention name instead
// replace white spaces with non-breaking spaces to facilitate mentions detection in text
selected_suggestion.name = selected_suggestion.name.replace(/ /g, NON_BREAKING_SPACE);
substitution = this.active_listener.delimiter + selected_suggestion.name;
}
var get_mention_index = function (matches, cursor_position) {
for (var i=0; i<matches.length; i++) {
if (cursor_position <= matches[i].index) {
return i;
}
}
return i;
};
// add the selected suggestion to the list
if (this.active_listener.selection.length) {
// get mention matches (ordered by index in the text)
var matches = this._get_match(text_input, this.active_listener);
var index = get_mention_index(matches, this._get_selection_positions().start);
this.active_listener.selection.splice(index, 0, selected_suggestion);
} else {
this.active_listener.selection.push(selected_suggestion);
}
// update input text, and reset dropdown
var cursor_position = this._get_selection_positions().start;
var text_left = text_input.substring(0, cursor_position-(this.mention_word.length+1));
var text_right = text_input.substring(cursor_position, text_input.length);
var text_input_new = text_left + substitution + ' ' + text_right;
this.composer.$input.val(text_input_new);
this._set_cursor_position(text_left.length+substitution.length+2);
this.set('mention_suggestions', []);
this.composer.focus(); // to trigger autoresize
},
// Public API
/**
@@ -86,8 +130,8 @@ var MentionManager = Widget.extend({
get_listener_selection: function (delimiter) {
var listener = _.findWhere(this.listeners, {delimiter: delimiter});
if (listener) {
var inputMentions = this.composer.$input.text().match(new RegExp(delimiter+'[^ ]+(?= |&nbsp;)', 'g'));
return this._validate_selection(listener.selection, inputMentions);
var input_mentions = this.composer.$input.val().match(new RegExp(delimiter+'[^ ]+(?= |&nbsp;)', 'g'));
return this._validate_selection(listener.selection, input_mentions);
}
return [];
},
@@ -106,15 +150,15 @@ var MentionManager = Widget.extend({
*/
detect_delimiter: function () {
var self = this;
var textVal = this.composer.$input.html();
var cursorPosition = this._getSelectionPositions();
var leftString = textVal.substring(0, cursorPosition);
var text_val = this.composer.$input.val();
var cursor_position = this._get_selection_positions().start;
var left_string = text_val.substring(0, cursor_position);
function validate_keyword (delimiter, beginning_only) {
var delimiter_position = leftString.lastIndexOf(delimiter) - 1;
var delimiter_position = left_string.lastIndexOf(delimiter) - 1;
if (beginning_only && delimiter_position > 0) {
return false;
}
var search_str = textVal.substring(delimiter_position, cursorPosition);
var search_str = text_val.substring(delimiter_position, cursor_position);
var pattern = "(^"+delimiter+"|(^\\s"+delimiter+"))";
var regex_start = new RegExp(pattern, "g");
search_str = search_str.replace(/^\s\s*|^[\n\r]/g, '');
@@ -203,8 +247,6 @@ var MentionManager = Widget.extend({
/**
* Returns the matches (as RexExp.exec does) for the mention in the input text
*
* @private
* @param {String} input_text: the text to search matches
* @param {Object} listener: the listener for which we want to find a match
* @returns {Object[]} matches in the same format as RexExp.exec()
@@ -228,32 +270,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'));
}
},
/**
@@ -924,31 +849,6 @@ var BasicComposer = Widget.extend({
_onEmojiImageFocus: function () {
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();
},
});
+47 -46
View File
@@ -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;
}
}
}
}
@@ -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;
+7 -5
View File
@@ -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"/>
+8 -11
View File
@@ -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,
+3 -6
View File
@@ -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();
});
@@ -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':
@@ -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)]):
+11
View File
@@ -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'
+5
View File
@@ -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'
+4 -1
View File
@@ -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(){

Some files were not shown because too many files have changed in this diff Show More