From cb609bd4258fd3e7b4f4caff4828ed947833f995 Mon Sep 17 00:00:00 2001 From: Olivier Dony Date: Wed, 3 Feb 2016 02:25:09 +0100 Subject: [PATCH 01/10] [FIX] tools.misc: log errors when parsing fails --- openerp/tools/misc.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openerp/tools/misc.py b/openerp/tools/misc.py index 90b89eeb99a..3d47e6f2e71 100644 --- a/openerp/tools/misc.py +++ b/openerp/tools/misc.py @@ -1094,11 +1094,15 @@ def stripped_sys_argv(*strip_args): class Pickle(object): @classmethod - def load(cls, stream): + def load(cls, stream, errors=False): unpickler = cPickle.Unpickler(stream) # pickle builtins: str/unicode, int/long, float, bool, tuple, list, dict, None unpickler.find_global = None - return unpickler.load() + try: + return unpickler.load() + except Exception: + _logger.warning('Failed unpickling data, returning default: %r', errors, exc_info=True) + return errors @classmethod def loads(cls, text): From 932a83de32a8d7809cce9d79777b3d0580ced476 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 2 Feb 2016 12:04:24 +0100 Subject: [PATCH 02/10] [FIX] account: forward port typo type is not redefined, so this comparison cannot work. Moreover, a lucky combination made the test pass, while they should not have. opw-667935 --- addons/account/models/account_invoice.py | 6 +++--- addons/account/tests/test_product_id_change.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 1ff9b2498d3..288e98aa357 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -6,7 +6,7 @@ from datetime import datetime from dateutil.relativedelta import relativedelta from openerp import api, fields, models, _, SUPERUSER_ID -from openerp.tools import float_is_zero +from openerp.tools import float_is_zero, float_compare from openerp.tools.misc import formatLang from openerp.exceptions import UserError, RedirectWarning, ValidationError @@ -1098,8 +1098,8 @@ class AccountInvoiceLine(models.Model): self.invoice_line_tax_ids = fp_taxes = self.invoice_id.fiscal_position_id.map_tax(taxes) fix_price = self.env['account.tax']._fix_tax_included_price - if type in ('in_invoice', 'in_refund'): - if not self.price_unit or self.price_unit == self.product_id.standard_price: + if self.invoice_id.type in ('in_invoice', 'in_refund'): + if not self.price_unit or float_compare(self.price_unit, self.product_id.standard_price, precision_digits=self.currency_id.rounding) == 0: self.price_unit = fix_price(self.product_id.standard_price, taxes, fp_taxes) else: self.price_unit = fix_price(self.product_id.lst_price, taxes, fp_taxes) diff --git a/addons/account/tests/test_product_id_change.py b/addons/account/tests/test_product_id_change.py index a2008b64bc7..9458a8618de 100644 --- a/addons/account/tests/test_product_id_change.py +++ b/addons/account/tests/test_product_id_change.py @@ -38,10 +38,10 @@ class TestProductIdChange(AccountingTestCase): amount='0.00')) product_tmpl = self.product_tmpl_model.create(dict(name="Voiture", list_price='121', - standard_price='121', taxes_id=[(6, 0, [tax_include_sale.id])], supplier_taxes_id=[(6, 0, [tax_include_purchase.id])])) - product = self.product_model.create(dict(product_tmpl_id=product_tmpl.id)) + product = self.product_model.create(dict(product_tmpl_id=product_tmpl.id, + standard_price='242')) fp = self.fiscal_position_model.create(dict(name="fiscal position", sequence=1)) fp_tax_sale = self.fiscal_position_tax_model.create(dict(position_id=fp.id, tax_src_id=tax_include_sale.id, @@ -80,7 +80,7 @@ class TestProductIdChange(AccountingTestCase): in_line = self.invoice_line_model.create({ 'product_id': product.id, 'quantity': 1, - 'price_unit': 121.0, + 'price_unit': 242.0, 'invoice_id': in_invoice.id, 'name': 'something in', 'account_id': self.account_revenue.id, @@ -88,4 +88,4 @@ class TestProductIdChange(AccountingTestCase): out_line._onchange_product_id() self.assertEquals(100, out_line.price_unit, "The included tax must be subtracted to the price") in_line._onchange_product_id() - self.assertEquals(100, in_line.price_unit, "The included tax must be subtracted to the price") + self.assertEquals(200, in_line.price_unit, "The included tax must be subtracted to the price") From b6250eb7f0d1c6bcfd7cf1f9fa35ffe09c3d0220 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 2 Feb 2016 14:58:43 +0100 Subject: [PATCH 03/10] [FIX] account: filter taxes In a multi-company environment, a user from company A which is a parent of the company B has access to taxes from both companies. The consequence is that the method _set_taxes will automatically add the taxes from both companies. --- addons/account/models/account_invoice.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 288e98aa357..2350e21dc35 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -5,7 +5,7 @@ from lxml import etree from datetime import datetime from dateutil.relativedelta import relativedelta -from openerp import api, fields, models, _, SUPERUSER_ID +from openerp import api, fields, models, _ from openerp.tools import float_is_zero, float_compare from openerp.tools.misc import formatLang @@ -1093,8 +1093,11 @@ class AccountInvoiceLine(models.Model): taxes = self.product_id.taxes_id or self.account_id.tax_ids else: taxes = self.product_id.supplier_taxes_id or self.account_id.tax_ids - if self.env.uid == SUPERUSER_ID: - taxes = taxes.filtered(lambda r: r.company_id == self.company_id) + + # Keep only taxes of the company + company_id = self.company_id or self.env.user.company_id + taxes = taxes.filtered(lambda r: r.company_id == company_id) + self.invoice_line_tax_ids = fp_taxes = self.invoice_id.fiscal_position_id.map_tax(taxes) fix_price = self.env['account.tax']._fix_tax_included_price From 1fb92278932609f2bfc351485a8e3992ac11a4e3 Mon Sep 17 00:00:00 2001 From: Yannick Tivisse Date: Thu, 10 Dec 2015 10:28:22 +0100 Subject: [PATCH 04/10] [IMP] stock: Missing kanban view on all stock.picking views from dashboard --- addons/stock/stock_view.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index aa59d98760e..3a5544902ac 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -1000,7 +1000,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], @@ -1023,7 +1023,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], @@ -1047,7 +1047,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], @@ -1071,7 +1071,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], @@ -1096,7 +1096,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], @@ -1120,7 +1120,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], @@ -1145,7 +1145,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], @@ -1170,7 +1170,7 @@ stock.picking ir.actions.act_window form - tree,form,calendar + tree,kanban,form,calendar { 'search_default_picking_type_id': [active_id], From d01ecb6e7989cd239b81f932fe8b32487e6297be Mon Sep 17 00:00:00 2001 From: bima Date: Wed, 3 Feb 2016 14:12:22 +0700 Subject: [PATCH 05/10] [FIX] stock: wrong variable used in _handle_renaming Could incorrectly rename the stock.location.path objects, and in case the route has push_ids but no pull_ids would crash the method entirely. closes #10747 --- addons/stock/stock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/stock/stock.py b/addons/stock/stock.py index f60e2ce6684..0562f2fe289 100644 --- a/addons/stock/stock.py +++ b/addons/stock/stock.py @@ -3620,7 +3620,7 @@ class stock_warehouse(osv.osv): for pull in route.pull_ids: pull_obj.write(cr, uid, pull.id, {'name': pull.name.replace(warehouse.name, name, 1)}, context=context) for push in route.push_ids: - push_obj.write(cr, uid, push.id, {'name': pull.name.replace(warehouse.name, name, 1)}, context=context) + push_obj.write(cr, uid, push.id, {'name': push.name.replace(warehouse.name, name, 1)}, context=context) #change the mto pull rule name if warehouse.mto_pull_id.id: pull_obj.write(cr, uid, warehouse.mto_pull_id.id, {'name': warehouse.mto_pull_id.name.replace(warehouse.name, name, 1)}, context=context) From aca28c82b4441db8830fb215a1250b54985bf886 Mon Sep 17 00:00:00 2001 From: Martin Trigaux Date: Tue, 2 Feb 2016 14:49:19 +0100 Subject: [PATCH 06/10] [FIX] website_sale: do not compute price of every pricelist This controller was very slow as not providing a pricelist computed the price of all products of all pricelists. Instead only fetch the prices for the useful products. --- addons/website_sale/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 81a4743eb3f..c9ceabb568e 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -972,7 +972,7 @@ class website_sale(http.Controller): pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id else: pricelist_id = partner.property_product_pricelist.id - prices = pool['product.pricelist'].price_rule_get_multi(cr, uid, [], [(product, add_qty, partner) for product in products], context=context) + prices = pool['product.pricelist'].price_rule_get_multi(cr, uid, [pricelist_id], [(product, add_qty, partner) for product in products], context=context) return {product_id: prices[product_id][pricelist_id][0] for product_id in product_ids} # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: From cc36e03ddc83e73f7b2fa76b9a46e3eaa6298f25 Mon Sep 17 00:00:00 2001 From: Thomas Groutars Date: Tue, 2 Feb 2016 18:41:20 -0800 Subject: [PATCH 07/10] [FIX] product_visible_discount: wrong argument type Fix call to v8 method with wrong argument type Closes #10745 opw-668544 --- addons/product_visible_discount/product_visible_discount.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/product_visible_discount/product_visible_discount.py b/addons/product_visible_discount/product_visible_discount.py index 077ec1efa21..5d77144e4cf 100644 --- a/addons/product_visible_discount/product_visible_discount.py +++ b/addons/product_visible_discount/product_visible_discount.py @@ -57,7 +57,7 @@ class sale_order_line(osv.osv): if line.product_id.company_id and line.order_id.pricelist_id.currency_id.id != line.product_id.company_id.currency_id.id: # new_list_price is in company's currency while price in pricelist currency ctx = dict(context_partner, date=self.order_id.date_order) - new_list_price = self.env['res.currency'].browse(currency_id).with_context(ctx).compute(new_list_price, line.order_id.pricelist_id.currency_id.id) + new_list_price = self.env['res.currency'].browse(currency_id).with_context(ctx).compute(new_list_price, line.order_id.pricelist_id.currency_id) discount = (new_list_price - line.price_unit) / new_list_price * 100 if discount > 0: line.price_unit = new_list_price @@ -86,7 +86,7 @@ class sale_order_line(osv.osv): if self.product_id.company_id and self.order_id.pricelist_id.currency_id.id != self.product_id.company_id.currency_id.id: # new_list_price is in company's currency while price in pricelist currency ctx = dict(context_partner, date=self.order_id.date_order) - new_list_price = self.env['res.currency'].browse(currency_id).with_context(ctx).compute(new_list_price, self.order_id.pricelist_id.currency_id.id) + new_list_price = self.env['res.currency'].browse(currency_id).with_context(ctx).compute(new_list_price, self.order_id.pricelist_id.currency_id) discount = (new_list_price - self.price_unit) / new_list_price * 100 if discount > 0: self.price_unit = new_list_price From 90d4f76100f2520f599bf3998ade81ac5e6183e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Thu, 28 Jan 2016 15:39:23 +0100 Subject: [PATCH 08/10] [FIX] mail: users can now reply directly in documents from channels A missing feature in discuss was the possibility to directly reply to a message from a channel without opening the document. This commit add that feature. Now, there is a small 'reply' icon in channels (including inbox/starred) if the message comes from another document. This icon opens an extended composer, which will post the message in the document chatter. --- addons/mail/static/src/js/chat_manager.js | 3 + addons/mail/static/src/js/chat_window.js | 1 + addons/mail/static/src/js/client_action.js | 74 ++++++++++++++++--- addons/mail/static/src/js/composer.js | 12 ++- addons/mail/static/src/js/thread.js | 56 ++++++++++---- .../mail/static/src/less/client_action.less | 14 +++- addons/mail/static/src/less/thread.less | 9 ++- addons/mail/static/src/xml/client_action.xml | 7 ++ addons/mail/static/src/xml/thread.xml | 5 +- 9 files changed, 145 insertions(+), 36 deletions(-) diff --git a/addons/mail/static/src/js/chat_manager.js b/addons/mail/static/src/js/chat_manager.js index 5e37bf237d8..a1272cea1ec 100644 --- a/addons/mail/static/src/js/chat_manager.js +++ b/addons/mail/static/src/js/chat_manager.js @@ -694,6 +694,9 @@ var chat_manager = { } }, + get_message: function (id) { + return _.findWhere(messages, {id: id}); + }, get_messages: function (options) { var channel; diff --git a/addons/mail/static/src/js/chat_window.js b/addons/mail/static/src/js/chat_window.js index 0e0289c394e..1a9c0fa93ea 100644 --- a/addons/mail/static/src/js/chat_window.js +++ b/addons/mail/static/src/js/chat_window.js @@ -29,6 +29,7 @@ return Widget.extend({ this.folded = is_folded; this.options = _.defaults(options || {}, { display_stars: true, + display_reply_icon: false, placeholder: _t("Say something"), input_less: false, }); diff --git a/addons/mail/static/src/js/client_action.js b/addons/mail/static/src/js/client_action.js index a2b4a400a1b..7b4024f66d0 100644 --- a/addons/mail/static/src/js/client_action.js +++ b/addons/mail/static/src/js/client_action.js @@ -116,7 +116,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { chat_manager.undo_mark_as_read(msgs_ids, channel); }); }, - "click .o_mail_annoying_notification_bar .fa-close": function (event) { + "click .o_mail_annoying_notification_bar .fa-close": function () { this.$(".o_mail_annoying_notification_bar").slideUp(); }, "click .o_mail_request_permission": function (event) { @@ -124,6 +124,11 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.$(".o_mail_annoying_notification_bar").slideUp(); window.Notification.requestPermission(); }, + "keydown": function (event) { + if (event.which === $.ui.keyCode.ESCAPE && this.selected_message) { + this.unselect_message(); + } + }, }, on_attach_callback: function () { @@ -144,6 +149,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.channels_scrolltop = {}; this.throttled_render_sidebar = _.throttle(this.render_sidebar.bind(this), 100, { leading: false }); this.notification_bar = (window.Notification && window.Notification.permission === "default"); + this.selected_message = null; }, willStart: function () { @@ -206,6 +212,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, { this.thread.on('toggle_star_status', this, function (message_id) { chat_manager.toggle_star_status(message_id); }); + this.thread.on('select_message', this, this.select_message); + this.thread.on('unselect_message', this, this.unselect_message); + this.basic_composer.on('post_message', this, this.on_post_message); this.basic_composer.on('input_focused', this, this.on_composer_input_focused); this.extended_composer.on('post_message', this, this.on_post_message); @@ -241,6 +250,31 @@ var ChatAction = Widget.extend(ControlPanelMixin, { }); }, + select_message: function(message_id) { + this.$el.addClass('o_mail_selection_mode'); + var message = chat_manager.get_message(message_id); + this.selected_message = message; + var subject = "Re: " + message.record_name; + this.extended_composer.set_subject(subject); + if (this.channel.type !== 'static') { + this.basic_composer.toggle(false); + } + this.extended_composer.toggle(true); + this.thread.scroll_to({id: message_id, duration: 200, only_if_necessary: true}); + this.extended_composer.focus('body'); + }, + + unselect_message: function() { + if (this.channel.type !== 'static' && !this.channel.mass_mailing) { + this.basic_composer.toggle(true); + this.basic_composer.focus(); + } + this.$el.removeClass('o_mail_selection_mode'); + this.extended_composer.toggle(this.channel.mass_mailing); + this.thread.unselect(); + this.selected_message = null; + }, + render_sidebar: function () { var self = this; var $sidebar = $(QWeb.render("mail.chat.Sidebar", { @@ -299,14 +333,16 @@ var ChatAction = Widget.extend(ControlPanelMixin, { }); }, - render_snackbar: function (nb_needactions) { - this.$snackbar = $(QWeb.render('mail.chat.UndoSnackbar', { - nb_needactions: nb_needactions, - })); + render_snackbar: function (template, context, timeout) { + if (this.$snackbar) { + this.$snackbar.remove(); + } + timeout = timeout || 20000; + this.$snackbar = $(QWeb.render(template, context)); this.$('.o_mail_chat_content').append(this.$snackbar); - // Hide snackbar after 20s + // Hide snackbar after [timeout] milliseconds (by default, 20s) var $snackbar = this.$snackbar; - setTimeout(function() { $snackbar.fadeOut(); }, 20000); + setTimeout(function() { $snackbar.fadeOut(); }, timeout); }, do_search_channel: function(search_val){ @@ -349,7 +385,9 @@ var ChatAction = Widget.extend(ControlPanelMixin, { if (channel.type !== 'static') { // Display snackbar if needactions have been cleared if (channel.needaction_counter > 0) { - self.render_snackbar(channel.needaction_counter); + self.render_snackbar('mail.chat.MessageSentSnackbar', { + nb_needactions: channel.needaction_counter, + }); } chat_manager.mark_channel_as_seen(channel); self.clear_needactions_def = chat_manager.mark_all_as_read(channel); @@ -374,6 +412,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { self.basic_composer.toggle(channel.type !== 'static' && !channel.mass_mailing); self.extended_composer.toggle(channel.type !== 'static' && channel.mass_mailing); + self.$el.removeClass('o_mail_selection_mode'); self.$('.o_mail_chat_channel_item') .removeClass('o_active') @@ -439,6 +478,7 @@ var ChatAction = Widget.extend(ControlPanelMixin, { display_empty_channel: !messages.length && !this.domain.length, display_no_match: !messages.length && this.domain.length, display_subject: this.channel.mass_mailing || this.channel.id === "channel_inbox", + display_reply_icon: true, }; }, @@ -512,11 +552,25 @@ var ChatAction = Widget.extend(ControlPanelMixin, { on_post_message: function (message) { var self = this; - var options = {channel_id: this.channel.id}; + var options = this.selected_message ? {} : {channel_id: this.channel.id}; + if (this.selected_message) { + message.subtype = 'mail.mt_comment'; + message.subtype_id = false; + message.message_type = 'comment'; + message.content_subtype = 'html'; + + options.model = this.selected_message.model; + options.res_id = this.selected_message.res_id; + } chat_manager .post_message(message, options) .then(function() { - self.thread.scroll_to(); + if (self.selected_message) { + self.render_snackbar('mail.chat.MessageSentSnackbar', {record_name: self.selected_message.record_name}, 5000); + self.unselect_message(); + } else { + self.thread.scroll_to(); + } }) .fail(function () { // todo: display notification diff --git a/addons/mail/static/src/js/composer.js b/addons/mail/static/src/js/composer.js index 92bac153267..6ffd2bc63c3 100644 --- a/addons/mail/static/src/js/composer.js +++ b/addons/mail/static/src/js/composer.js @@ -698,9 +698,15 @@ var ExtendedComposer = BasicComposer.extend({ should_send: function () { return false; }, - - focus: function () { - this.$subject_input.focus(); + focus: function (target) { + if (target === 'body') { + this.$input.focus(); + } else { + this.$subject_input.focus(); + } + }, + set_subject: function(subject) { + this.$('.o_composer_subject input').val(subject); }, }); diff --git a/addons/mail/static/src/js/thread.js b/addons/mail/static/src/js/thread.js index aee712ea905..dc2467d3862 100644 --- a/addons/mail/static/src/js/thread.js +++ b/addons/mail/static/src/js/thread.js @@ -21,27 +21,36 @@ var Thread = Widget.extend({ "click strong": "on_click_redirect", "click .o_thread_show_more": "on_click_show_more", "click .o_thread_message_needaction": function (event) { - event.stopPropagation(); var message_id = $(event.currentTarget).data('message-id'); this.trigger("mark_as_read", message_id); }, "click .o_thread_message_star": function (event) { - event.stopPropagation(); var message_id = $(event.currentTarget).data('message-id'); this.trigger("toggle_star_status", message_id); }, - "click .o_thread_message": function (event) { - var selected = $(event.currentTarget).hasClass('o_thread_selected_message'); + "click .o_thread_message_reply": function (event) { + this.selected_id = $(event.currentTarget).data('message-id'); this.$('.o_thread_message').removeClass('o_thread_selected_message'); - $(event.currentTarget).toggleClass('o_thread_selected_message', !selected); + this.$('.o_thread_message[data-message-id=' + this.selected_id + ']') + .addClass('o_thread_selected_message'); + this.trigger('select_message', this.selected_id); + event.stopPropagation(); }, "click .oe_mail_expand": function (event) { event.preventDefault(); - event.stopPropagation(); var $message = $(event.currentTarget).parents('.o_thread_message'); $message.addClass('o_message_expanded'); this.expanded_msg_ids.push($message.data('message-id')); }, + "click .o_thread_message": function (event) { + $(event.currentTarget).toggleClass('o_thread_selected_message'); + }, + "click": function () { + if (this.selected_id) { + this.unselect(); + this.trigger('unselect_message'); + } + }, }, init: function (parent, options) { @@ -54,8 +63,10 @@ var Thread = Widget.extend({ display_avatar: true, shorten_messages: true, squash_close_messages: true, + display_reply_icon: false, }); this.expanded_msg_ids = []; + this.selected_id = null; }, render: function (messages, options) { @@ -69,11 +80,14 @@ var Thread = Widget.extend({ // Hide avatar and info of a message if that message and the previous // one are both comments wrote by the same author at the same minute + // and in the same document (users can now post message in documents + // directly from a channel that follows it) var prev_msg; _.each(msgs, function (msg) { if (!prev_msg || (Math.abs(msg.date.diff(prev_msg.date)) > 60000) || prev_msg.message_type !== 'comment' || msg.message_type !== 'comment' || - (prev_msg.author_id[0] !== msg.author_id[0])) { + (prev_msg.author_id[0] !== msg.author_id[0]) || prev_msg.model !== msg.model || + prev_msg.res_id !== msg.res_id) { msg.display_author = true; } else { msg.display_author = !options.squash_close_messages; @@ -138,6 +152,7 @@ var Thread = Widget.extend({ } msg.display_subject = message.subject && !message.is_system_notification; + msg.is_selected = msg.id === this.selected_id; return msg; }, @@ -159,18 +174,23 @@ var Thread = Widget.extend({ /** * Scrolls the thread to a given message or offset if any, to bottom otherwise - * @param {int} [target.id] optional: the id of the message to scroll to - * @param {int} [target.offset] optional: the number of pixels to scroll + * @param {int} [options.id] optional: the id of the message to scroll to + * @param {int} [options.offset] optional: the number of pixels to scroll */ - scroll_to: function (target) { - target = target || {}; - if (target.id !== undefined) { - var $target = this.$('.o_thread_message[data-message-id=' + target.id + ']'); - if ($target.length) { + scroll_to: function (options) { + options = options || {}; + if (options.id !== undefined) { + var $target = this.$('.o_thread_message[data-message-id=' + options.id + ']'); + if (options.only_if_necessary) { + var delta = $target.parent().height() - $target.height(); + var offset = delta < 0 ? 0 : delta - ($target.offset().top - $target.offsetParent().offset().top); + offset = - Math.min(offset, 0); + this.$el.scrollTo("+=" + offset + "px", options); + } else if ($target.length) { this.$el.scrollTo($target); } - } else if (target.offset !== undefined) { - this.$el.scrollTop(target.offset); + } else if (options.offset !== undefined) { + this.$el.scrollTop(options.offset); } else { this.$el.scrollTop(this.el.scrollHeight); } @@ -181,6 +201,10 @@ var Thread = Widget.extend({ is_at_bottom: function () { return this.el.scrollHeight - this.$el.scrollTop() - this.$el.outerHeight() < 5; }, + unselect: function () { + this.$('.o_thread_message').removeClass('o_thread_selected_message'); + this.selected_id = null; + }, }); Thread.ORDER = ORDER; diff --git a/addons/mail/static/src/less/client_action.less b/addons/mail/static/src/less/client_action.less index c4b28874358..1b2e8163a17 100644 --- a/addons/mail/static/src/less/client_action.less +++ b/addons/mail/static/src/less/client_action.less @@ -120,8 +120,7 @@ .o_mail_thread { .o-flex(1, 0, 0); overflow: auto; - .o-webclient-padding(); - padding-bottom: 15px; + padding: 0 0 15px 0; .o_chat_help_message { padding: 25px 0; @@ -253,6 +252,17 @@ } } } + + &.o_mail_selection_mode { + .o_thread_message { + opacity: 0.5; + &.o_thread_selected_message { + padding: 4px @odoo-horizontal-padding; + opacity: 1; + background-color: lightgray; + } + } + } } // User status bullet (in Discuss sidebar and Invite dialog) diff --git a/addons/mail/static/src/less/thread.less b/addons/mail/static/src/less/thread.less index 7f0f144ffd8..7da1a13802e 100644 --- a/addons/mail/static/src/less/thread.less +++ b/addons/mail/static/src/less/thread.less @@ -40,7 +40,8 @@ .o_thread_message { .o-flex-display(); - padding: 4px 0; + padding: 4px @odoo-horizontal-padding; + margin-bottom: 0px; .o_thread_message_sidebar { .o-flex(0, 0, @mail-thread-avatar-size); @@ -69,7 +70,7 @@ } } - i.o_thread_message_star, i.o_thread_message_needaction { + i.o_thread_message_star, i.o_thread_message_needaction, i.o_thread_message_reply { cursor: pointer; opacity: 0; &.fa-star { @@ -83,7 +84,7 @@ opacity: @mail-thread-side-date-opacity; } - i.o_thread_message_star, i.o_thread_message_needaction { + i.o_thread_message_star, i.o_thread_message_needaction, i.o_thread_message_reply { opacity: @mail-thread-icon-opacity; &:hover { opacity: 1; @@ -141,7 +142,7 @@ } } - .o_thread_message_star, .o_thread_message_needaction { + .o_thread_message_star, .o_thread_message_needaction, .o_thread_message_reply { padding: 4px; } } diff --git a/addons/mail/static/src/xml/client_action.xml b/addons/mail/static/src/xml/client_action.xml index 78371340e36..15eadb00626 100644 --- a/addons/mail/static/src/xml/client_action.xml +++ b/addons/mail/static/src/xml/client_action.xml @@ -118,6 +118,13 @@ + + + + +
diff --git a/addons/mail/static/src/xml/thread.xml b/addons/mail/static/src/xml/thread.xml index ca863d9ad8f..6ed0700be11 100644 --- a/addons/mail/static/src/xml/thread.xml +++ b/addons/mail/static/src/xml/thread.xml @@ -46,7 +46,7 @@ -
+
+

From 5465dccd69ea613363bd6083cfc16c26507ebbf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 1 Feb 2016 16:19:34 +0100 Subject: [PATCH 09/10] [FIX] mail: prevent the composer to break layout in client action The composer can grow vertically. It will eventually grow out of the chat content, which breaks the layout. This commit fixes the issue. --- addons/mail/static/src/less/client_action.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/mail/static/src/less/client_action.less b/addons/mail/static/src/less/client_action.less index 1b2e8163a17..ecf4785f03e 100644 --- a/addons/mail/static/src/less/client_action.less +++ b/addons/mail/static/src/less/client_action.less @@ -112,6 +112,9 @@ height: 100%; margin-left: @mail-chat-sidebar-width; position: relative; + @media (min-width: @screen-sm-min) { + overflow: auto; + } @media (max-width: @screen-xs-max) { margin-left: 0; From 967be70c52a1f5a5ef7c17285c365a232475ca81 Mon Sep 17 00:00:00 2001 From: qdp-odoo Date: Wed, 3 Feb 2016 16:35:46 +0100 Subject: [PATCH 10/10] [FIX] account: tax of type 'group' wasn't propagating correctly the base of computation for next taxes (in case one of the children had the field 'Affect Base of Subsequent Taxes' set). Fixes issue 641078 --- addons/account/models/account.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/account/models/account.py b/addons/account/models/account.py index 5e158e158f2..cc5fc752c0c 100644 --- a/addons/account/models/account.py +++ b/addons/account/models/account.py @@ -641,7 +641,7 @@ class AccountTax(models.Model): if tax.amount_type == 'group': ret = tax.children_tax_ids.compute_all(price_unit, currency, quantity, product, partner) total_excluded = ret['total_excluded'] - base = ret['total_excluded'] + base = ret['base'] total_included = ret['total_included'] tax_amount = total_included - total_excluded taxes += ret['taxes'] @@ -677,6 +677,7 @@ class AccountTax(models.Model): 'taxes': sorted(taxes, key=lambda k: k['sequence']), 'total_excluded': currency.round(total_excluded), 'total_included': currency.round(total_included), + 'base': base, } @api.v7