diff --git a/addons/mail/i18n/mail.pot b/addons/mail/i18n/mail.pot index bd6e0be0ab2..c7dc8d62d5d 100644 --- a/addons/mail/i18n/mail.pot +++ b/addons/mail/i18n/mail.pot @@ -181,6 +181,12 @@ msgid "

\n" " You can insert canned responses in your message by typing :shortcut.
" msgstr "" +#. module: mail +#: code:addons/mail/models/mail_channel.py:475 +#, python-format +msgid "
%(author)s invited %(new_partner)s to #%(channel_name)s
" +msgstr "" + #. module: mail #: code:addons/mail/models/mail_channel.py:552 #, python-format diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index 9abaf4f1499..77d71993caa 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -472,7 +472,15 @@ class Channel(models.Model): partners_to_add = partners - channel.channel_partner_ids channel.write({'channel_last_seen_partner_ids': [(0, 0, {'partner_id': partner_id}) for partner_id in partners_to_add.ids]}) for partner in partners_to_add: - notification = _('
joined #%s
') % (self.id, self.name,) + if partner.id != self.env.user.partner_id.id: + notification = _('
%(author)s invited %(new_partner)s to #%(channel_name)s
') % { + 'author': self.env.user.display_name, + 'new_partner': partner.display_name, + 'channel_id': channel.id, + 'channel_name': channel.name, + } + else: + notification = _('
joined #%s
') % (channel.id, channel.name,) self.message_post(body=notification, message_type="notification", subtype="mail.mt_comment", author_id=partner.id) # broadcast the channel header to the added partner diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js index 2176eff55fa..36180a5ea2a 100644 --- a/addons/mail/static/src/js/thread.js +++ b/addons/mail/static/src/js/thread.js @@ -199,6 +199,10 @@ var Thread = Widget.extend({ }); }, on_click_redirect: function (event) { + // ignore inherited branding + if ($(event.target).data('oe-field') !== undefined) { + return; + } var id = $(event.target).data('oe-id'); if (id) { event.preventDefault(); diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index ea74c32ff1f..e61ffdadea9 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -82,6 +82,11 @@ class MrpBom(models.Model): if self.product_tmpl_id: self.product_uom_id = self.product_tmpl_id.uom_id.id + @api.onchange('routing_id') + def onchange_routing_id(self): + for line in self.bom_line_ids: + line.operation_id = False + @api.multi def name_get(self): return [(bom.id, '%s%s' % (bom.code and '%s: ' % bom.code or '', bom.product_tmpl_id.display_name)) for bom in self] diff --git a/addons/project/models/project.py b/addons/project/models/project.py index 36310219909..97eff93dd91 100644 --- a/addons/project/models/project.py +++ b/addons/project/models/project.py @@ -409,13 +409,11 @@ class Task(models.Model): @api.onchange('project_id') def _onchange_project(self): - default_partner_id = self.env.context.get('default_partner_id') - default_partner = self.env['res.partner'].browse(default_partner_id) if default_partner_id else self.env['res.partner'] if self.project_id: - self.partner_id = self.project_id.partner_id or default_partner + if self.project_id.partner_id: + self.partner_id = self.project_id.partner_id self.stage_id = self.stage_find(self.project_id.id, [('fold', '=', False)]) else: - self.partner_id = default_partner self.stage_id = False @api.onchange('user_id') diff --git a/addons/sale/models/sale.py b/addons/sale/models/sale.py index a91ed8862fb..b71001b946d 100644 --- a/addons/sale/models/sale.py +++ b/addons/sale/models/sale.py @@ -116,7 +116,7 @@ class SaleOrder(models.Model): validity_date = fields.Date(string='Expiration Date', readonly=True, copy=False, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Manually set the expiration date of your quotation (offer), or it will set the date automatically based on the template if online quotation is installed.") create_date = fields.Datetime(string='Creation Date', readonly=True, index=True, help="Date on which sales order is created.") - confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sales order is confirmed.", oldname="date_confirm") + confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sales order is confirmed.", oldname="date_confirm", copy=False) user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', default=lambda self: self.env.user) partner_id = fields.Many2one('res.partner', string='Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, index=True, track_visibility='always') partner_invoice_id = fields.Many2one('res.partner', string='Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Invoice address for current sales order.") diff --git a/addons/stock_account/report/stock_history.py b/addons/stock_account/report/stock_history.py index ee5a5554729..077aea8cef5 100644 --- a/addons/stock_account/report/stock_history.py +++ b/addons/stock_account/report/stock_history.py @@ -30,7 +30,7 @@ class StockHistory(models.Model): # on the individual lines. res = super(StockHistory, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, lazy=lazy) if 'inventory_value' in fields: - groupby_list = groupby if not lazy else groupby[:-1] + groupby_list = groupby[:1] if lazy else groupby date = self._context.get('history_date', fieldsDatetime.now()) # Step 2: retrieve the stock history lines. The result contains the 'expanded' @@ -39,7 +39,9 @@ class StockHistory(models.Model): fields_2 = set( ['id', 'product_id', 'price_unit_on_quant', 'company_id', 'quantity'] + groupby_list ) - tables, where_clause, where_clause_params = self._where_calc(domain).get_sql() + query = self._where_calc(domain) + self._apply_ir_rules(query, 'read') + tables, where_clause, where_clause_params = query.get_sql() select = "SELECT %s FROM %s WHERE %s " query = select % (','.join(fields_2), tables, where_clause) self._cr.execute(query, where_clause_params) diff --git a/addons/web/static/src/js/framework/utils.js b/addons/web/static/src/js/framework/utils.js index 45dda977dc8..fe6de0e2df2 100644 --- a/addons/web/static/src/js/framework/utils.js +++ b/addons/web/static/src/js/framework/utils.js @@ -504,6 +504,17 @@ function swap(array, elem1, elem2) { array[i1] = elem2; } +function is_email(value, allow_mailto) { + // http://stackoverflow.com/questions/46155/validate-email-address-in-javascript + var re; + if (allow_mailto) { + re = /^(mailto:)?(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + } else { + re = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + } + return re.test(value); +} + function toBoolElse (str, elseValues, trueValues, falseValues) { var ret = _.str.toBool(str, trueValues, falseValues); if (_.isUndefined(ret)) { @@ -569,6 +580,7 @@ return { reject_after: reject_after, delay: delay, swap: swap, + is_email: is_email, toBoolElse: toBoolElse, async_when: async_when, }; diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js index 64772e319af..aa2040485d1 100644 --- a/addons/web_editor/static/src/js/widgets.js +++ b/addons/web_editor/static/src/js/widgets.js @@ -5,6 +5,7 @@ var core = require('web.core'); var ajax = require('web.ajax'); var Dialog = require('web.Dialog'); var Widget = require('web.Widget'); +var utils = require('web.utils'); var base = require('web_editor.base'); var rte = require('web_editor.rte'); @@ -1048,6 +1049,7 @@ var LinkDialog = Dialog.extend({ if (!$e.length) { $e = this.$('input.url-source:first'); } + $e.closest('.form-group').removeClass('has-error'); var val = $e.val(); var label = this.$('#o_link_dialog_label_input').val() || val; @@ -1068,8 +1070,7 @@ var LinkDialog = Dialog.extend({ var size = this.$("select.link-style").val() || ''; var classes = (this.data.className || "") + (style && style.length ? " btn " : "") + style + " " + size; var isNewWindow = this.$('input.window-new').prop('checked'); - - if ($e.hasClass('email-address') && $e.val().indexOf("@") !== -1) { + if ($e.hasClass('email-address') && (_.str.startsWith(val, 'mailto:') || (val.indexOf("@") !== -1 && !_.str.startsWith(val, 'http') && !_.str.startsWith(val, 'www')))) { self.get_data_buy_mail(def, $e, isNewWindow, label, classes, test); } else { self.get_data_buy_url(def, $e, isNewWindow, label, classes, test); @@ -1078,7 +1079,13 @@ var LinkDialog = Dialog.extend({ }, get_data_buy_mail: function (def, $e, isNewWindow, label, classes, test) { var val = $e.val(); - def.resolve(val.indexOf("mailto:") === 0 ? val : 'mailto:' + val, isNewWindow, label, classes); + if (utils.is_email(val, true)) { + def.resolve(val.indexOf("mailto:") === 0 ? val : 'mailto:' + val, isNewWindow, label, classes); + } else { + $e.closest('.form-group').addClass('has-error'); + $e.focus(); + def.reject(); + } }, get_data_buy_url: function (def, $e, isNewWindow, label, classes, test) { def.resolve($e.val(), isNewWindow, label, classes); diff --git a/addons/website_quote/models/sale_order.py b/addons/website_quote/models/sale_order.py index d09ffc53bcc..4019ddae9e7 100644 --- a/addons/website_quote/models/sale_order.py +++ b/addons/website_quote/models/sale_order.py @@ -74,7 +74,14 @@ class SaleOrder(models.Model): (0, 'Not mandatory on website quote validation'), (1, 'Immediate after website order validation'), (2, 'Immediate after website order validation and save a token'), - ], 'Payment', help="Require immediate payment by the customer when validating the order from the website quote") + ], 'Payment', help="Require immediate payment by the customer when validating the order from the website quote", compute='_compute_require_payment', store=True) + + @api.one + @api.depends('template_id') + def _compute_require_payment(self): + # Do not overwrite if the field is already set + if self.template_id and (self.require_payment is None or self.require_payment is False): + self.require_payment = self.template_id.require_payment @api.multi def copy(self, default=None): diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 66a887cbb54..02bb081c3a5 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -617,14 +617,11 @@