[MERGE] Forward-port 9.0 up to 93e8bf8ccf

This commit is contained in:
Olivier Dony
2016-02-03 17:19:16 +01:00
17 changed files with 181 additions and 61 deletions
+2 -1
View File
@@ -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
+9 -6
View File
@@ -5,8 +5,8 @@ from lxml import etree
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 import api, fields, models, _
from openerp.tools import float_is_zero, float_compare
from openerp.tools.misc import formatLang
from openerp.exceptions import UserError, RedirectWarning, ValidationError
@@ -1093,13 +1093,16 @@ 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
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)
@@ -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")
@@ -694,6 +694,9 @@ var chat_manager = {
}
},
get_message: function (id) {
return _.findWhere(messages, {id: id});
},
get_messages: function (options) {
var channel;
+1
View File
@@ -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,
});
+64 -10
View File
@@ -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
+9 -3
View File
@@ -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);
},
});
+40 -16
View File
@@ -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;
+15 -2
View File
@@ -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;
@@ -120,8 +123,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 +255,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)
+5 -4
View File
@@ -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;
}
}
@@ -118,6 +118,13 @@
</div>
</t>
<t t-name="mail.chat.MessageSentSnackbar">
<div class="alert o_mail_snackbar" data-dismiss="alert" role="alert">
Message sent in "<t t-esc="record_name"/>".
</div>
</t>
<!-- Buttons of the Control Panel -->
<t t-name="mail.chat.ControlButtons">
<div>
+4 -1
View File
@@ -46,7 +46,7 @@
</t>
<t t-name="mail.ChatThread.Message">
<div t-att-class="'o_thread_message' + (message.expanded ? ' o_message_expanded' : '')" t-att-data-message-id="message.id">
<div t-att-class="'o_thread_message' + (message.expanded ? ' o_message_expanded ' : ' ') + (message.is_selected ? 'o_thread_selected_message' : '')" t-att-data-message-id="message.id">
<div t-if="options.display_avatar" class="o_thread_message_sidebar">
<t t-if="!message.mailto &amp;&amp; message.author_id[0]">
<img t-if="message.avatar_src &amp;&amp; message.display_author"
@@ -103,6 +103,9 @@
<i t-if="message.is_needaction &amp;&amp; options.display_needactions"
class="fa fa-check o_thread_message_needaction"
t-att-data-message-id="message.id" title="Mark as Read"/>
<i t-if="message.record_name &amp;&amp; message.model != 'mail.channel' &amp;&amp; options.display_reply_icon"
class="fa fa-reply o_thread_message_reply"
t-att-data-message-id="message.id" title="Reply"/>
</span>
</p>
<t t-if="message.tracking_value_ids &amp;&amp; message.tracking_value_ids.length &gt; 0">
@@ -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
+1 -1
View File
@@ -3877,7 +3877,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 procurement 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)
+8 -8
View File
@@ -1000,7 +1000,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
@@ -1023,7 +1023,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
@@ -1047,7 +1047,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
@@ -1071,7 +1071,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
@@ -1096,7 +1096,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
@@ -1120,7 +1120,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
@@ -1145,7 +1145,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
@@ -1170,7 +1170,7 @@
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{
'search_default_picking_type_id': [active_id],
+1 -1
View File
@@ -1054,5 +1054,5 @@ class website_sale(http.Controller):
pricelist_id = request.website.get_current_pricelist(context=context).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}
+6 -2
View File
@@ -1111,11 +1111,15 @@ consteq = getattr(passlib.utils, 'consteq', _consteq)
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):