diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js
index 08256402b0d..0ad7b9b8765 100644
--- a/addons/account/static/src/js/reconciliation/reconciliation_model.js
+++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js
@@ -1292,7 +1292,8 @@ var ManualModel = StatementModel.extend({
});
} else {
var mv_line_ids = _.pluck(_.filter(props, function (prop) {return !isNaN(prop.id);}), 'id');
- var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id) && prop.display;}), self._formatToProcessReconciliation.bind(self, line));
+ // Dear KangOl, please FORWARD-PORT UP TO SAAS-11.3. Thank you for your hard work.
+ var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id) && prop.display && !prop.is_tax;}), self._formatToProcessReconciliation.bind(self, line));
process_reconciliations.push({
id: null,
type: null,
diff --git a/addons/mail/static/src/js/activity.js b/addons/mail/static/src/js/activity.js
index fce7eb29f6f..6e66d73ea07 100644
--- a/addons/mail/static/src/js/activity.js
+++ b/addons/mail/static/src/js/activity.js
@@ -187,6 +187,28 @@ var Activity = AbstractActivityField.extend({
this.trigger_up('reload_mail_fields', fieldsToReload);
},
+ /** Binds a focusout handler on a bootstrap popover
+ * Useful to do some operations on the popover's HTML,
+ * like keeping the user's input for the feedback
+ * @param {JQuery} $popover_el: the element on which
+ * the popover() method has been called
+ */
+ _bindPopoverFocusout: function ($popover_el) {
+ var self = this;
+ // Retrieve the actual popover's HTML
+ var $popover = $popover_el.data("bs.popover").tip();
+ var activity_id = $popover_el.data('activity-id');
+ $popover.off('focusout');
+ $popover.focusout(function (e) {
+ // outside click of popover hides the popover
+ // e.relatedTarget is the element receiving the focus
+ self.feedbackValue[activity_id] = $popover.find('#activity_feedback').val().trim();
+ if(!$popover.is(e.relatedTarget) && !$popover.find(e.relatedTarget).length) {
+ $popover_el.popover('hide');
+ }
+ });
+ },
+
// handlers
_onClickRedirect: function (ev) {
var id = $(ev.target).data('oe-id');
@@ -236,19 +258,23 @@ var Activity = AbstractActivityField.extend({
})
.then(this._reload.bind(this, {activity: true}));
},
+
_onMarkActivityDone: function (event) {
event.preventDefault();
var self = this;
+ this.feedbackValue = this.feedbackValue || {};
var $popover_el = $(event.currentTarget);
var activity_id = $popover_el.data('activity-id');
var previous_activity_type_id = $popover_el.data('previous-activity-type-id');
if (!$popover_el.data('bs.popover')) {
+ this.feedbackValue[activity_id] = "";
$popover_el.popover({
title : _t('Feedback'),
html: 'true',
trigger:'click',
content : function() {
var $popover = $(QWeb.render("mail.activity_feedback_form", {'previous_activity_type_id': previous_activity_type_id}));
+ $popover.find('#activity_feedback').val(self.feedbackValue[activity_id]);
$popover.on('click', '.o_activity_popover_done_next', function () {
var feedback = _.escape($popover.find('#activity_feedback').val());
var previous_activity_type_id = $popover_el.data('previous-activity-type-id');
@@ -272,14 +298,7 @@ var Activity = AbstractActivityField.extend({
}).on("shown.bs.popover", function () {
var $popover = $(this).data("bs.popover").tip();
$popover.find('#activity_feedback').focus();
- $popover.off('focusout');
- $popover.focusout(function (e) {
- // outside click of popover hide the popover
- // e.relatedTarget is the element receiving the focus
- if(!$popover.is(e.relatedTarget) && !$popover.find(e.relatedTarget).length) {
- $popover.popover('hide');
- }
- });
+ self._bindPopoverFocusout($(this));
}).popover('show');
}
},
diff --git a/addons/mail/static/tests/chatter_tests.js b/addons/mail/static/tests/chatter_tests.js
index f1f6097cb97..f98cd1382a8 100644
--- a/addons/mail/static/tests/chatter_tests.js
+++ b/addons/mail/static/tests/chatter_tests.js
@@ -17,6 +17,8 @@ var createBusService = mailTestUtils.createBusService;
var createAsyncView = testUtils.createAsyncView;
var createView = testUtils.createView;
+var Activity = require('mail.Activity');
+
QUnit.module('mail', {}, function () {
QUnit.module('Chatter', {
@@ -72,6 +74,7 @@ QUnit.module('Chatter', {
type: 'selection',
selection: [['overdue', 'Overdue'], ['today', 'Today'], ['planned', 'Planned']],
},
+ note : { string: "Note", type: "char" },
},
},
'mail.activity.type': {
@@ -143,6 +146,73 @@ QUnit.test('basic rendering', function (assert) {
form.destroy();
});
+QUnit.test('Activity Done keep feedback on blur', function (assert) {
+ assert.expect(3);
+ var done = assert.async();
+
+ this.data['mail.activity'].records = [
+ {activity_type_id: 1, id: 1, user_id: 2, state: 'today', note: 'But I\'m talkin\' about Shaft'},
+ ];
+ this.data.partner.records[0].activity_ids = [1];
+
+ var shownDef = $.Deferred();
+ var hiddenDef = $.Deferred();
+ testUtils.patch(Activity, {
+ _bindPopoverFocusout: function () {
+ this._super.apply(this, arguments);
+ shownDef.resolve();
+ },
+ });
+
+ var form = createView({
+ View: FormView,
+ model: 'partner',
+ data: this.data,
+ res_id: 2,
+ arch:'
',
+ });
+
+ // sanity checks
+ var $activityEl = form.$('.o_mail_activity[name=activity_ids]');
+ assert.strictEqual($activityEl.find('.o_thread_message').length, 1,
+ 'There should be one activity');
+ assert.strictEqual($activityEl.find('.o_thread_message .o_thread_message_note').text().trim(),
+ 'But I\'m talkin\' about Shaft', 'The activity should have the right note');
+
+ var $popoverEl = $activityEl.find('.o_thread_message_tools .o_activity_done');
+ $popoverEl.on('hidden.bs.popover', hiddenDef.resolve.bind(hiddenDef));
+
+ // open popover
+ $popoverEl.click();
+
+ shownDef.then(function () {
+ // write a feedback and focusout
+ var $feedbackPopover = $popoverEl.data('bs.popover').tip();
+ $feedbackPopover.find('#activity_feedback').val('John Shaft').focusout();
+
+ hiddenDef.then(function () {
+ shownDef = $.Deferred();
+
+ // re-open popover
+ $popoverEl.click();
+
+ shownDef.then(function () {
+ var $feedbackPopover = $popoverEl.data('bs.popover').tip();
+ assert.strictEqual($feedbackPopover.find('#activity_feedback').val(), 'John Shaft',
+ "feedback should have been kept");
+
+ form.destroy();
+ testUtils.unpatch(Activity);
+ done();
+ });
+ });
+ });
+});
+
QUnit.test('chatter in create mode', function (assert) {
assert.expect(9);
diff --git a/addons/membership/models/partner.py b/addons/membership/models/partner.py
index a5ec46f701f..733c5bda0df 100644
--- a/addons/membership/models/partner.py
+++ b/addons/membership/models/partner.py
@@ -164,8 +164,10 @@ class Partner(models.Model):
@api.model
def _cron_update_membership(self):
- # used to recompute 'membership_state'; should no longer be necessary
- pass
+ partners = self.search([('membership_state', 'in', ['invoiced', 'paid'])])
+ # mark the field to be recomputed, and recompute it
+ partners._recompute_todo(self._fields['membership_state'])
+ self.recompute()
@api.multi
def create_membership_invoice(self, product_id=None, datas=None):
diff --git a/addons/payment/models/payment_acquirer.py b/addons/payment/models/payment_acquirer.py
index abaf9ba0368..6026b62484c 100644
--- a/addons/payment/models/payment_acquirer.py
+++ b/addons/payment/models/payment_acquirer.py
@@ -185,9 +185,13 @@ class PaymentAcquirer(models.Model):
def _check_required_if_provider(self):
""" If the field has 'required_if_provider=""' attribute, then it
required if record.provider is . """
+ empty_field = []
for acquirer in self:
- if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
- return False
+ for k, f in acquirer._fields.items():
+ if getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k]:
+ empty_field.append(self.env['ir.model.fields'].search([('name', '=', k), ('model', '=', acquirer._name)]).field_description)
+ if empty_field:
+ raise ValidationError((', ').join(empty_field))
return True
_constraints = [
diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml
index dd98a420ed4..78d7036ecf3 100644
--- a/addons/point_of_sale/views/pos_config_view.xml
+++ b/addons/point_of_sale/views/pos_config_view.xml
@@ -451,7 +451,7 @@
Operation type used to record product pickings
-
+
diff --git a/addons/sale/views/sale_views.xml b/addons/sale/views/sale_views.xml
index 09dba5794eb..7de85dc555d 100644
--- a/addons/sale/views/sale_views.xml
+++ b/addons/sale/views/sale_views.xml
@@ -384,7 +384,7 @@
-
+
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js
index f502f37be94..cc22c2b9af0 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -190,7 +190,9 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
// If it is not handled, we should display something clearer than the common crash_manager error dialog
// since it won't show anything except "Script error."
// This link will probably explain it better: https://blog.sentry.io/2016/05/17/what-is-script-error.html
- if (message === "Script error." && !file && !line && !col && !error) {
+ if (!file && !line && !col) {
+ // Chrome and Opera set "Script error." on the `message` and hide the `error`
+ // Firefox handles the "Script error." directly. It sets the error thrown by the CORS file into `error`
if (window.onOriginError) {
window.onOriginError();
delete window.onOriginError;
@@ -198,7 +200,7 @@ var AbstractWebClient = Widget.extend(ServiceProviderMixin, KeyboardNavigationMi
crash_manager.show_error({
type: _t("Odoo Client Error"),
message: _t("Unknown CORS error"),
- data: {debug: _t("An unknown CORS error occured. The error probably originates from a JavaScript file served from a different origin.")},
+ data: {debug: _t("An unknown CORS error occured. The error probably originates from a JavaScript file served from a different origin. (Opening your browser console might give you a hint on the error.)")},
});
}
} else {
diff --git a/addons/web_settings_dashboard/static/src/js/dashboard.js b/addons/web_settings_dashboard/static/src/js/dashboard.js
index d5705c80e42..19d5dfa9f43 100644
--- a/addons/web_settings_dashboard/static/src/js/dashboard.js
+++ b/addons/web_settings_dashboard/static/src/js/dashboard.js
@@ -4,6 +4,7 @@ odoo.define('web_settings_dashboard', function (require) {
var AbstractAction = require('web.AbstractAction');
var core = require('web.core');
var framework = require('web.framework');
+var session = require('web.session');
var Widget = require('web.Widget');
var QWeb = core.qweb;
diff --git a/addons/web_settings_dashboard/static/tests/dashboard_tests.js b/addons/web_settings_dashboard/static/tests/dashboard_tests.js
index 9ff65c32783..8ebd1fe7cdb 100644
--- a/addons/web_settings_dashboard/static/tests/dashboard_tests.js
+++ b/addons/web_settings_dashboard/static/tests/dashboard_tests.js
@@ -11,12 +11,12 @@ var Dashboard = webSettingsDashboard.Dashboard;
function createDashboard(params) {
var widget = new Widget();
var dashboard = new Dashboard(widget);
- dashboard.all_dashboards = ['invitations']; // test only user invitations
+ dashboard.all_dashboards = params.dashboards || ['invitations']; // test only user invitations
testUtils.addMockEnvironment(widget, params);
var originalDestroy = Dashboard.prototype.destroy;
- Dashboard.destroy = function () {
+ dashboard.destroy = function () {
dashboard.destroy = originalDestroy;
widget.destroy();
};
diff --git a/addons/website_quote/report/sale_order_templates.xml b/addons/website_quote/report/sale_order_templates.xml
index ad67e5e9439..fa89626a186 100644
--- a/addons/website_quote/report/sale_order_templates.xml
+++ b/addons/website_quote/report/sale_order_templates.xml
@@ -3,6 +3,7 @@
+
Suggested Products
@@ -11,7 +12,7 @@
| Product |
Description |
- |
+ |
Unit Price |
@@ -23,8 +24,8 @@
|
-
-
+ |
+
% discount
|
diff --git a/addons/website_sale/models/sale_order.py b/addons/website_sale/models/sale_order.py
index bf20ac1814c..987bdaa6a66 100644
--- a/addons/website_sale/models/sale_order.py
+++ b/addons/website_sale/models/sale_order.py
@@ -105,7 +105,6 @@ class SaleOrder(models.Model):
'order_id': order_id,
'product_uom': product.uom_id.id,
'price_unit': pu,
- 'customer_lead': product.sale_delay,
}
@api.multi
diff --git a/addons/website_sale_stock/models/sale_order.py b/addons/website_sale_stock/models/sale_order.py
index 4ac8a67a871..2896163c673 100644
--- a/addons/website_sale_stock/models/sale_order.py
+++ b/addons/website_sale_stock/models/sale_order.py
@@ -31,6 +31,13 @@ class SaleOrder(models.Model):
values['warning'] = self.warning_stock
return values
+ @api.multi
+ def _website_product_id_change(self, order_id, product_id, qty=0):
+ res = super(SaleOrder, self)._website_product_id_change(order_id, product_id, qty=qty)
+ product = self.env['product.product'].browse(product_id)
+ res['customer_lead'] = product.sale_delay
+ return res
+
@api.multi
def _get_stock_warning(self, clear=True):
self.ensure_one()
diff --git a/doc/cla/corporate/camptocamp.md b/doc/cla/corporate/camptocamp.md
index 777bb974dc5..206fad26646 100644
--- a/doc/cla/corporate/camptocamp.md
+++ b/doc/cla/corporate/camptocamp.md
@@ -33,3 +33,4 @@ Akim Juillerat akim.juillerat@camptocamp.com https://github.com/grindtildeath
Damien Crier damien.crier@camptocamp.com https://github.com/damdam-s
Simone Orsi simahawk@gmail.com https://github.com/simahawk
Patrick Tombez patrick.tombez@camptocamp.com https://github.com/p-tombez
+Julien Coux julien.coux@camptocamp.com https://github.com/jcoux
diff --git a/doc/cla/individual/rocketgithub.md b/doc/cla/individual/rocketgithub.md
new file mode 100644
index 00000000000..9b30fbb4d5c
--- /dev/null
+++ b/doc/cla/individual/rocketgithub.md
@@ -0,0 +1,11 @@
+Guatemala, 2018-09-04
+
+I hereby agree to the terms of the Odoo Individual Contributor License
+Agreement v1.0.
+
+I declare that I am authorized and able to make this agreement and sign this
+declaration.
+
+Signed,
+
+Rodrigo Fernandez rodrigo@solucionesprisma.com https://github.com/rocketgithub
diff --git a/odoo/addons/base/data/res_country_data.xml b/odoo/addons/base/data/res_country_data.xml
index 54497ad8f97..2eaa0d0abf3 100644
--- a/odoo/addons/base/data/res_country_data.xml
+++ b/odoo/addons/base/data/res_country_data.xml
@@ -651,7 +651,7 @@
Guatemala
gt
-
+
diff --git a/odoo/addons/test_main_flows/__init__.py b/odoo/addons/test_main_flows/__init__.py
index ac45264fad4..ba375620a33 100644
--- a/odoo/addons/test_main_flows/__init__.py
+++ b/odoo/addons/test_main_flows/__init__.py
@@ -1,4 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-#from . import models
+from odoo import api, SUPERUSER_ID
+
+#
+# Conditional installation of enterprise modules.
+#
+# This module is defined in community but some steps (defined with 'edition: "enterprise"')
+# are only used to test enterprise. As it's not possible to direcly add enterprise
+# modules dependencies, this post install hook will install account_accountant if exists.
+#
+def _auto_install_enterprise_dependencies(cr, registry):
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ module_list = ['account_accountant']
+ module_ids = env['ir.module.module'].search([('name', 'in', module_list), ('state', '=', 'uninstalled')])
+ module_ids.sudo().button_install()
diff --git a/odoo/addons/test_main_flows/__manifest__.py b/odoo/addons/test_main_flows/__manifest__.py
index f0c471a0566..bde9b933d27 100644
--- a/odoo/addons/test_main_flows/__manifest__.py
+++ b/odoo/addons/test_main_flows/__manifest__.py
@@ -13,4 +13,5 @@ It will install some main apps and will try to execute the most important action
'views/templates.xml',
],
'installable': True,
+ 'post_init_hook': '_auto_install_enterprise_dependencies',
}
|