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 @@