diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 5085b757f20..791d94e515d 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -1011,7 +1011,7 @@ var ManualModel = StatementModel.extend({ } })); }); - return $.when(defs).then(function() { + return $.when.apply($, defs).then(function() { if (account_ids.length) { self._rpc({ model: 'account.account', diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index da0074fb5c7..04c03a41308 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -154,7 +154,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // have no way of redispatching 'genuine' key events. Resent events // don't trigger native event handlers of elements. So this means that // our fake events will not appear in eg. an element. - if (this.element_is_editable(e.target) && e.target.getAttribute("barcode_events") !== "true") + if ((this.element_is_editable(e.target) && !$(e.target).data('enableBarcode')) && e.target.getAttribute("barcode_events") !== "true") return; // Catch and buffer the event diff --git a/addons/barcodes/static/src/js/barcode_field.js b/addons/barcodes/static/src/js/barcode_field.js new file mode 100644 index 00000000000..c9f30d53094 --- /dev/null +++ b/addons/barcodes/static/src/js/barcode_field.js @@ -0,0 +1,107 @@ +odoo.define('barcodes.field', function(require) { +"use strict"; + +var AbstractField = require('web.AbstractField'); +var basicFields = require('web.basic_fields'); +var fieldRegistry = require('web.field_registry'); + +// Field in which the user can both type normally and scan barcodes + +var FieldFloatScannable = basicFields.FieldFloat.extend({ + events: { + // The barcode_events component intercepts keypresses and releases them when it + // appears they are not part of a barcode. But since released keypresses don't + // trigger native behaviour (like characters input), we must simulate it. + 'keypress': '_onKeypress', + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _renderEdit: function() { + var self = this; + $.when(this._super()).then(function () { + self.$input.data('enableBarcode', true); + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {KeyboardEvent} e + */ + _onKeypress: function (e) { + /* only simulate a keypress if it has been previously prevented */ + if (e.originalEvent.dispatched_by_barcode_reader !== true) { + e.preventDefault(); + this.$input.blur(); + return; + } + var character = String.fromCharCode(e.which); + var current_str = e.target.value; + var str_before_carret = current_str.substring(0, e.target.selectionStart); + var str_after_carret = current_str.substring(e.target.selectionEnd); + e.target.value = str_before_carret + character + str_after_carret; + var new_carret_index = str_before_carret.length + character.length; + e.target.setSelectionRange(new_carret_index, new_carret_index); + // Note: FieldChar (that FieldFloat extends) calls store_dom_value upon change + // event, which is triggered when the input loses focus and its internal dirty + // flag is set. But here, we directly modify the value property of the input, + // which doesn't set the dirty flag. So we could call store_dom_value upon blur. + // But we also want the DOM value to be stored when a barcode_event occurs and + // triggers an onchange. We could listen to barcode_event in order to call + // store_dom_value, but that would have to happen before the onchange is triggered. + // So the safest method is still to store the value each time it changes. + // This long explanation is here to avoid having to do the thinking all over agan + // in case this strategy doesn't work / breaks. + // TL;DR Safest way not to lose the value when a barcode scan triggers an onchange. + this._setValue(e.target.value); + }, +}); + +// Field to use scan barcodes + +var FormViewBarcodeHandler = AbstractField.extend({ + /** + * Trigger_up 'activeBarcode' to activate features and send options + * - @params {string} name: the current field name + * - @params {string} [fieldName] optional for x2many sub field + * - @params {string} [quantity] optional field to increase quantity + * - @params {Object} [commands] optional added methods + * can use comand with specific barcode (with ReservedBarcodePrefixes) + * or change 'barcode' for all other received barcodes + * (e.g.: 'O-CMD.MAIN-MENU': function ..., barcode: function () {...}) + * + * @override + */ + init: function() { + this._super.apply(this, arguments); + + this.trigger_up('activeBarcode', { + name: this.name, + fieldName: 'pack_operation_product_ids', + quantity: 'qty_done', + commands: { + barcode: '_barcodeAddX2MQuantity', + } + }); + }, +}); + +fieldRegistry.add('field_float_scannable', FieldFloatScannable); +fieldRegistry.add('barcode_handler', FormViewBarcodeHandler); + +return { + FieldFloatScannable: FieldFloatScannable, + FormViewBarcodeHandler: FormViewBarcodeHandler, +}; + +}); diff --git a/addons/barcodes/static/src/js/barcode_form_view.js b/addons/barcodes/static/src/js/barcode_form_view.js new file mode 100644 index 00000000000..2e22587fbe6 --- /dev/null +++ b/addons/barcodes/static/src/js/barcode_form_view.js @@ -0,0 +1,340 @@ +odoo.define('barcodes.FormView', function (require) { +"use strict"; + +var BarcodeEvents = require('barcodes.BarcodeEvents'); // handle to trigger barcode on bus +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var FormController = require('web.FormController'); +var FormRenderer = require('web.FormRenderer'); + +var _t = core._t; + + +FormController.include({ + custom_events: _.extend({}, FormController.prototype.custom_events, { + activeBarcode: '_barcodeActivated', + }), + + init: function () { + this._super.apply(this, arguments); + this.activeBarcode = { + form_view: { + commands: { + 'O-CMD.NEW': 'createRecord', + 'O-CMD.EDIT': 'toEditMode', + 'O-CMD.CANCEL': 'discardChange', + 'O-CMD.SAVE': 'saveRecord', + // 'O-CMD.PAGER-PREV': + // 'O-CMD.PAGER-NEXT': + } + } + }; + this._barcodeStartListening(); + }, + destroy: function () { + this._barcodeStopListening(); + this._super(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {any} barcode + * @param {any} activeBarcode + * @returns {any} + */ + _barcodeAddX2MQuantity: function (barcode, activeBarcode) { + if (this.mode === 'readonly') { + this.do_warn(_t('Error : Document not editable'), + _t('To modify this document, please first start edition.')); + return new $.Deferred().reject(); + } + + var record = this.model.get(this.handle); + var candidate = this._getBarCodeRecord(record, barcode, activeBarcode); + if (candidate) { + return this._barcodeSelectedCandidate(candidate, record, barcode, activeBarcode); + } else { + return this._barcodeWithoutCandidate(record, barcode, activeBarcode); + } + }, + /** + * @private + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {boolean} + */ + _barcodeRecordFilter: function (record, barcode, activeBarcode) { + return record.data.product_barcode === barcode; + }, + /** + * @private + * @param {any} candidate + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {Deferred} + */ + _barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode) { + var changes = {}; + changes[activeBarcode.quantity] = candidate.data[activeBarcode.quantity] + 1; + return this.model.notifyChanges(candidate.id, changes); + }, + /** + * @private + */ + _barcodeStartListening: function () { + core.bus.on('barcode_scanned', this, this._barcodeScanned); + core.bus.on('keypress', this, this._quantityListener); + }, + + /** + * @private + */ + _barcodeStopListening: function () { + core.bus.off('barcode_scanned', this, this._barcodeScanned); + core.bus.off('keypress', this, this._quantityListener); + }, + /** + * @private + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {Deferred} + */ + _barcodeWithoutCandidate: function (record, barcode, activeBarcode) { + var changes = {}; + changes[activeBarcode.name] = barcode; + return this.model.notifyChanges(record.id, changes).then(function () { + return new $.Deferred().reject(); + }); + }, + /** + * @private + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {any} + */ + _getBarCodeRecord: function (record, barcode, activeBarcode) { + var self = this; + if (!activeBarcode.fieldName) { + return; + } + return _.find(record.data[activeBarcode.fieldName].data, function (record) { + return self._barcodeRecordFilter(record, barcode, activeBarcode); + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * The barcode is activate when at least one widget trigger_up 'activeBarcode' event + * with the widget option + * + * @param {OdooEvent} event + */ + _barcodeActivated: function (event) { + event.stopPropagation(); + var name = event.data.name; + this.activeBarcode[name] = { + name: name, + handle: this.handle, + target: event.target, + widget: event.target.attrs && event.target.attrs.widget, + fieldName: event.data.fieldName, + quantity: event.data.quantity, + commands: event.data.commands || {}, + candidate: this.activeBarcode[name] && this.activeBarcode[name].handle === this.handle ? + this.activeBarcode[name].candidate : null, + }; + }, + /** + * @private + * @param {any} method + * @param {any} barcode + * @param {any} activeBarcode + * @returns {Deferred} + */ + _barcodeActiveScanned: function (method, barcode, activeBarcode) { + var self = this; + var methodDef; + var def = new $.Deferred(); + if (typeof method === 'string') { + methodDef = this[method](barcode, activeBarcode); + } else { + methodDef = method.call(this, barcode, activeBarcode); + } + methodDef.always(function () { + var record = self.model.get(self.handle); + var candidate = self._getBarCodeRecord(record, barcode, activeBarcode); + activeBarcode.candidate = candidate; + def.resolve(); + }); + return def; + }, + /** + * Method called when a user scan a barcode, call each method in function of the + * widget options then update the renderer + * + * @private + * @param {string} barcode + * @returns {Deferred} + */ + _barcodeScanned: function (barcode, target) { + var prefixed = _.any(BarcodeEvents.ReservedBarcodePrefixes, + function (reserved) {return barcode.indexOf(reserved) === 0;}); + var hasCommand = false; + var self = this; + var defs = []; + for (var k in this.activeBarcode) { + var activeBarcode = this.activeBarcode[k]; + // Handle the case where there are several barcode widgets on the same page. Since the + // event is global on the page, all barcode widgets will be triggered. However, we only + // want to keep the event on the target widget. + if (self.target && !$.contains(target, self.target.el)) { + continue; + } + + var methods = this.activeBarcode[k].commands; + var method = prefixed ? methods[barcode] : methods.barcode; + if (method) { + if (prefixed) { + hasCommand = true; + } + defs.push(this._barcodeActiveScanned(method, barcode, activeBarcode)); + } + } + if (prefixed && !hasCommand) { + return this.do_warn(_t('Error : Barcode command is undefined'), barcode); + } + return $.when.apply($, defs).then(function () { + self.update({}, {reload: false}); + }); + }, + /** + * @private + * @param {any} event + */ + _quantityListener: function (event) { + var character = String.fromCharCode(event.which); + + // only catch the event if we're not focused in + // another field and it's a number + if (!$(event.target).is('body') || !/[0-9]/.test(character)) { + return; + } + + if (!_.compact(_.pluck(this.activeBarcode, 'candidate')).length) { + return this.do_warn(_t('Error : No last scanned barcode'), + _t('To set the quantity please scan a barcode first.')); + } + + for (var k in this.activeBarcode) { + if (this.activeBarcode[k].candidate) { + this._quantityOpenDialog(character, this.activeBarcode[k]); + } + } + }, + /** + * @private + * @param {any} character + * @param {any} activeBarcode + */ + _quantityOpenDialog: function (character, activeBarcode) { + var self = this; + var $content = $('
').append($('', {type: 'text', class: 'o_set_qty_input'})); + this.dialog = new Dialog(this, { + title: _t('Set quantity'), + buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () { + var new_qty = this.$content.find('.o_set_qty_input').val(); + var values = {}; + values[activeBarcode.quantity] = parseFloat(new_qty); + return self.model.notifyChanges(activeBarcode.candidate.id, values).then(function () { + self.update({}, {reload: false}); + }); + }}, {text: _t('Discard'), close: true}], + $content: $content, + }).open(); + // This line set the value of the key which triggered the _set_quantity in the input + var $input = this.dialog.$content.find('.o_set_qty_input').focus().val(character); + + var $selectBtn = this.dialog.$footer.find('.btn-primary'); + $input.on('keypress', function (event){ + if (event.which === 13) { + event.preventDefault(); + $input.off(); + $selectBtn.click(); + } + }); + }, +}); + + +FormRenderer.include({ + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** + * trigger_up 'activeBarcode' to Add barcode event handler + * + * @private + * @param {jQueryElement} $button + * @param {Object} node + */ + _barcodeButtonHandler: function ($button, node) { + var commands = {}; + commands.barcode = function () {return $.when();}; + commands['O-BTN.' + node.attrs.barcode_trigger] = function () { + if (!$button.hasClass('o_form_invisible')) { + $button.click(); + } + return $.when(); + }; + this.trigger_up('activeBarcode', { + name: node.attrs.name, + commands: commands + }); + }, + /** + * Add barcode event handler + * + * @override + * @private + * @param {Object} node + * @returns {jQueryElement} + */ + _renderHeaderButton: function (node) { + var $button = this._super.apply(this, arguments); + if (node.attrs.barcode_trigger) { + this._barcodeButtonHandler($button, node); + } + return $button; + }, + /** + * Add barcode event handler + * + * @override + * @private + * @param {Object} node + * @returns {jQueryElement} + */ + _renderStatButton: function (node) { + var $button = this._super.apply(this, arguments); + if (node.attrs.barcode_trigger) { + this._barcodeButtonHandler($button, node); + } + return $button; + }, +}); + +BarcodeEvents.ReservedBarcodePrefixes.push('O-BTN'); + +}); diff --git a/addons/barcodes/static/src/js/barcode_handler_mixin.js b/addons/barcodes/static/src/js/barcode_handler_mixin.js deleted file mode 100644 index 40f7ac734ca..00000000000 --- a/addons/barcodes/static/src/js/barcode_handler_mixin.js +++ /dev/null @@ -1,52 +0,0 @@ -odoo.define('barcodes.BarcodeHandlerMixin', function(require) { -"use strict"; - -var AbstractView = require('web.AbstractView'); -var core = require('web.core'); - -// Mixin implementing the common basis for barcode handlers. -// The object on which this mixin is applied must also include ParentedMixin. Example : -// Widget.extend(BarcodeHandlerMixin, { ... }); -// Class.extend(PropertiesMixin, BarcodeHandlerMixin, { ... }); - -return { - init: function() { - var self = this; - this._super.apply(this, arguments); - this.__on_barcode_scanned = function (barcode, target) { - // Handle the case where there are several barcode widgets on the same page. Since the - // event is global on the page, all barcode widgets will be triggered. However, we only - // want to keep the event on the target widget. - if ($.contains(target, self.el)) { - self.on_barcode_scanned.call(self, barcode); - } - }; - this.start_listening(); - // Handlers inside a View managed by a ViewManager only listen to barcode events while their view is displayed - var view = this.findAncestor(function(ancestor) { return ancestor instanceof AbstractView }); - if (view) { - view.on('attached', this, this.start_listening); - view.on('detached', this, this.stop_listening); - } - }, - - start_listening: function() { - if (! this.is_listening) { - core.bus.on('barcode_scanned', this, this.__on_barcode_scanned); - this.is_listening = true; - } - }, - - stop_listening: function() { - if (this.is_listening) { - core.bus.off('barcode_scanned', this, this.__on_barcode_scanned); - this.is_listening = false; - } - }, - - on_barcode_scanned: function(barcode) { - console.error('A class implementing BarcodeHandlerMixin must redefine method on_barcode_scanned.'); - }, -}; - -}); diff --git a/addons/barcodes/static/src/js/field_float_scannable.js b/addons/barcodes/static/src/js/field_float_scannable.js deleted file mode 100644 index f30e42d461a..00000000000 --- a/addons/barcodes/static/src/js/field_float_scannable.js +++ /dev/null @@ -1,59 +0,0 @@ -odoo.define('barcodes.FieldFloatScannable', function(require) { -"use strict"; - -return; - -var AbstractField = require('web.AbstractField'); -var field_registry = require('web.field_registry'); - -// Field in which the user can both type normally and scan barcodes - -var FieldFloatScannable = form_widgets.FieldFloat.extend({ - events: { - // The barcode_events component intercepts keypresses and releases them when it - // appears they are not part of a barcode. But since released keypresses don't - // trigger native behaviour (like characters input), we must simulate it. - 'keypress': 'simulateKeypress', - }, - - // Widget values are parsed according to the widget type. Since this widget is of type - // "FieldFloatScannable" and there is no parsing planned for this type, it defaults - // to outputting the value as a string. Hence the need to redefine parse_value - parse_value: function(val, def) { - return formats.parse_value(val, {type: "float"}, def); - }, - - simulateKeypress: function (e) { - /* only simulate a keypress if it has been previously prevented */ - if (e.originalEvent.dispatched_by_barcode_reader !== true) { - return; - } - var character = String.fromCharCode(e.which); - var current_str = e.target.value; - var str_before_carret = current_str.substring(0, e.target.selectionStart); - var str_after_carret = current_str.substring(e.target.selectionEnd); - e.target.value = str_before_carret + character + str_after_carret; - var new_carret_index = str_before_carret.length + character.length; - e.target.setSelectionRange(new_carret_index, new_carret_index); - // Note: FieldChar (that FieldFloat extends) calls store_dom_value upon change - // event, which is triggered when the input loses focus and its internal dirty - // flag is set. But here, we directly modify the value property of the input, - // which doesn't set the dirty flag. So we could call store_dom_value upon blur. - // But we also want the DOM value to be stored when a barcode_event occurs and - // triggers an onchange. We could listen to barcode_event in order to call - // store_dom_value, but that would have to happen before the onchange is triggered. - // So the safest method is still to store the value each time it changes. - // This long explanation is here to avoid having to do the thinking all over agan - // in case this strategy doesn't work / breaks. - // TL;DR Safest way not to lose the value when a barcode scan triggers an onchange. - this.store_dom_value(); - }, -}); - -core.form_widget_registry.add('field_float_scannable', FieldFloatScannable); - -return { - FieldFloatScannable: AbstractField, -}; - -}); diff --git a/addons/barcodes/static/src/js/form_view_barcode_handler.js b/addons/barcodes/static/src/js/form_view_barcode_handler.js deleted file mode 100644 index 2b3fe8cfaad..00000000000 --- a/addons/barcodes/static/src/js/form_view_barcode_handler.js +++ /dev/null @@ -1,205 +0,0 @@ -odoo.define('barcodes.FormViewBarcodeHandler', function(require) { -"use strict"; - -var core = require('web.core'); -var concurrency = require('web.concurrency'); -var BarcodeEvents = require('barcodes.BarcodeEvents'); -var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); -var KanbanRecord = require('web.KanbanRecord'); -var Dialog = require('web.Dialog'); -var AbstractField = require('web.AbstractField'); - -var _t = core._t; - -// web.KanbanRecord and web.list_common.Record do not implement the -// same interface and are thus inherently incompatible with each -// other. Luckily barcodes keeps things pretty simple when it comes to -// the records it wants to use. So if we give the KanbanRecord a get() -// function that behaves like the one of web.list.Record, everything -// is fine. -KanbanRecord.include({ - get: function (key) { - return this.values[key]; - }, -}); - -var FormViewBarcodeHandler = AbstractField.extend(BarcodeHandlerMixin, { - init: function(parent, context) { - this.__quantity_listener = _.bind(this._set_quantity_listener, this); - BarcodeHandlerMixin.init.apply(this, arguments); - - this.process_barcode_mutex = new concurrency.Mutex(); - - return this._super.apply(this, arguments); - }, - - start: function() { - this._super(); - this.form_view = this.field_manager; - // Hardcoded barcode actions - this.map_barcode_method = { - 'O-CMD.NEW': _.bind(this.form_view.on_button_new, this.form_view), - 'O-CMD.EDIT': _.bind(this.form_view.on_button_edit, this.form_view), - 'O-CMD.CANCEL': _.bind(this.form_view.on_button_cancel, this.form_view), - // FIXME: on_button_save shouldn't mix view and model concerns (it expects to be used as onclick handler) - 'O-CMD.SAVE': _.bind(this.form_view.on_button_save, this.form_view, {target: $('.o_cp_buttons .o_form_button_save')}), - }; - // Old design pager actions - if (this.form_view.execute_pager_action) { - this.map_barcode_method['O-CMD.PAGER-PREV'] = _.bind(this.form_view.execute_pager_action, this.form_view, 'previous'); - this.map_barcode_method['O-CMD.PAGER-NEXT'] = _.bind(this.form_view.execute_pager_action, this.form_view, 'next'); - // New design pager actions - } else if (this.form_view.pager) { - this.map_barcode_method['O-CMD.PAGER-PREV'] = _.bind(this.form_view.pager.previous, this.form_view.pager); - this.map_barcode_method['O-CMD.PAGER-NEXT'] = _.bind(this.form_view.pager.next, this.form_view.pager); - } - }, - - destroy: function () { - this.stop_listening(); - this._super.apply(this, arguments); - }, - - _display_no_edit_mode_warning: function() { - this.do_warn(_t('Error : Document not editable'), _t('To modify this document, please first start edition.')); - }, - - _display_no_last_scanned_warning: function() { - this.do_warn(_t('Error : No last scanned barcode'), _t('To set the quantity please scan a barcode first.')); - }, - - _set_quantity_listener: function(event) { - var self = this; - var character = String.fromCharCode(event.which); - - // only catch the event if we're not focused in - // another field and it's a number - if ($(event.target).is('body') && /[0-9]/.test(character)) { - if (this.form_view.get('actual_mode') === 'view') { - this._display_no_edit_mode_warning(); - } else { - var field = this.form_view.fields[this.m2x_field]; - var view = field.viewmanager.active_view; - var $content = $('
').append($('', {type: 'text', class: 'o_set_qty_input'})); - - if (this.last_scanned_barcode) { - this.dialog = new Dialog(this, { - title: _t('Set quantity'), - buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () { - var new_qty = this.$content.find('.o_set_qty_input').val(); - var record = _.find(self._get_records(field), function (record) { - return record.get('product_barcode') === self.last_scanned_barcode; - }); - if (record) { - var values = {}; - values[self.quantity_field] = parseFloat(new_qty); - field.data_update(record.get('id'), values).then(function () { - view.controller.reload_record(record); - }); - } else { - self._display_no_last_scanned_warning(); - } - }}, {text: _t('Discard'), close: true}], - $content: $content, - }).open(); - // This line set the value of the key which triggered the _set_quantity in the input - this.dialog.$content.find('.o_set_qty_input').focus().val(character); - - var $selectBtn = this.dialog.$footer.find('.btn-primary'); - core.bus.on('keypress', this.dialog, function(event){ - if (event.which === 13) { - event.preventDefault(); - $selectBtn.click(); - } - }); - } else { - this._display_no_last_scanned_warning(); - } - } - } - }, - - start_listening: function() { - if (this.quantity_field && ! this.is_listening) { - core.bus.on('keypress', this, this.__quantity_listener); - } - - BarcodeHandlerMixin.start_listening.call(this); - }, - - stop_listening: function() { - if (this.quantity_field && this.is_listening) { - core.bus.off('keypress', this, this.__quantity_listener); - delete this.last_scanned_barcode; - } - - BarcodeHandlerMixin.stop_listening.call(this); - }, - - // Let subclasses add custom behaviour before onchange. Must return a deferred. - // Resolve the deferred with true proceed with the onchange, false to prevent it. - pre_onchange_hook: function(barcode) { - return $.Deferred().resolve(true); - }, - - on_barcode_scanned: function(barcode) { - var self = this; - self.last_scanned_barcode = barcode; - // Execute a harcoded action - var action = this.map_barcode_method[barcode]; - if (typeof action === "function") - return $.when(action()); - if (_.any(BarcodeEvents.ReservedBarcodePrefixes, function(prefix) { return barcode.indexOf(prefix) === 0 })) - return; - // Warn the user if form view is not editable - else if (this.form_view.get('actual_mode') === 'view') - this._display_no_edit_mode_warning(); - else { - var process_barcode = function () { - // this function can be passed to `Mutex.exec` in order to make sure - // that every ongoing onchanges in the form view are done - var form_onchanges_mutex = function () { - return self.form_view.onchanges_mutex.def; - } - - // before setting the barcode field with the received barcode, we commit - // every fields of the form view and we wait for their hypothetical ongoing - // onchanges to finish - var commit_mutex = new concurrency.Mutex(); - _.each(self.form_view.fields, function (field) { - commit_mutex.exec(function () { - return field.commit_value(); - }); - commit_mutex.exec(form_onchanges_mutex); - }); - - return commit_mutex.def.then(function () { - return self.pre_onchange_hook(barcode).then(function (proceed) { - if (proceed) { - self.set_value(barcode); // set the barcode field with the received one - return form_onchanges_mutex(); // wait for its onchange to finish - } - }); - }); - }; - - this.process_barcode_mutex.exec(process_barcode); - } - }, - - _get_records: function(field) { - var active_view = field.viewmanager.active_view; - if (active_view.type === "kanban") { - return active_view.controller.widgets; - } else { - // tree view case - return active_view.controller.records.records; - } - }, -}); - -core.form_widget_registry.add('barcode_handler', FormViewBarcodeHandler); - -return FormViewBarcodeHandler; - -}); diff --git a/addons/barcodes/static/src/js/inherited_form_widget_button.js b/addons/barcodes/static/src/js/inherited_form_widget_button.js deleted file mode 100644 index d1998cd40cf..00000000000 --- a/addons/barcodes/static/src/js/inherited_form_widget_button.js +++ /dev/null @@ -1,34 +0,0 @@ -odoo.define('barcode.InheritedFormWidgetButton', function (require) { -"use strict"; - -// FIXME -// var widgets = require('web.form_widgets'); -// var BarcodeEvents = require('barcodes.BarcodeEvents'); -// var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); - -// // If the button has a barcode_trigger attribute, dynamically inherit -// // BarcodeHandlerMixin and redefine on_barcode_scanned - -// var ButtonBarcodeHandlerMixin = _.extend({}, BarcodeHandlerMixin, { -// init: function(field_manager, node) { -// if (node.attrs.barcode_trigger) { -// BarcodeHandlerMixin.init.call(this, field_manager, node); -// var self = this; -// this.on_barcode_scanned = function(barcode) { -// var match = barcode.match(/O-BTN\.(.+)/); -// if (match && match[1] === self.node.attrs.barcode_trigger && -// (self.$el.is(':visible') || self.$el.parent('.dropdown-menu').length)) { -// self.on_click(); -// } -// }; -// } else { -// this._super(field_manager, node); -// } -// }, -// }); - -// BarcodeEvents.ReservedBarcodePrefixes.push('O-BTN'); - -// widgets.WidgetButton.include(ButtonBarcodeHandlerMixin); - -}); diff --git a/addons/barcodes/views/barcodes_templates.xml b/addons/barcodes/views/barcodes_templates.xml index 4370533d4ca..420a069a491 100644 --- a/addons/barcodes/views/barcodes_templates.xml +++ b/addons/barcodes/views/barcodes_templates.xml @@ -5,10 +5,8 @@ - - - - + + diff --git a/addons/hr_attendance/static/src/js/greeting_message.js b/addons/hr_attendance/static/src/js/greeting_message.js index f60fea5a060..08c6879e077 100644 --- a/addons/hr_attendance/static/src/js/greeting_message.js +++ b/addons/hr_attendance/static/src/js/greeting_message.js @@ -1,15 +1,13 @@ odoo.define('hr_attendance.greeting_message', function (require) { "use strict"; -var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); - var core = require('web.core'); var Widget = require('web.Widget'); var _t = core._t; -var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { +var GreetingMessage = Widget.extend({ template: 'HrAttendanceGreetingMessage', events: { @@ -19,12 +17,12 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { init: function(parent, action) { var self = this; this._super.apply(this, arguments); - BarcodeHandlerMixin.init.apply(this, arguments); + this.activeBarcode = true; // if no correct action given (due to an erroneous back or refresh from the browser), we set the dismiss button to return // to the (likely) appropriate menu, according to the user access rights if(!action.attendance) { - this.stop_listening(); + this.activeBarcode = false; this.session.user_has_group('hr_attendance.group_hr_attendance_user').then(function(has_group) { if(has_group) { self.next_action = 'hr_attendance.hr_attendance_action_kiosk_mode'; @@ -38,7 +36,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { this.next_action = action.next_action || 'hr_attendance.hr_attendance_action_my_attendances'; // no listening to barcode scans if we aren't coming from the kiosk mode (and thus not going back to it with next_action) if (this.next_action != 'hr_attendance.hr_attendance_action_kiosk_mode' && this.next_action.tag != 'hr_attendance_kiosk_mode') { - this.stop_listening(); + this.activeBarcode = false; } this.attendance = action.attendance; // check in/out times displayed in the greeting message template. @@ -52,6 +50,9 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { if (this.attendance) { this.attendance.check_out ? this.farewell_message() : this.welcome_message(); } + if (this.activeBarcode) { + core.bus.on('barcode_scanned', this, this._onBarcodeScanned); + } }, welcome_message: function() { @@ -100,7 +101,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { if(now.valueOf() - last_check_in_date.valueOf() > 1000*60*60*12){ this.$('.o_hr_attendance_warning_message').append(_t("Warning! Last check in was over 12 hours ago.
If this isn't right, please contact Human Resources.")); clearTimeout(this.return_to_main_menu); - this.stop_listening(); + this.activeBarcode = false; } else if(now.valueOf() - last_check_in_date.valueOf() > 1000*60*60*8){ this.$('.o_hr_attendance_random_message').html(_t("Another good day's work! See you soon!")); } @@ -126,7 +127,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { } }, - on_barcode_scanned: function(barcode) { + _onBarcodeScanned: function(barcode) { var self = this; if (this.return_to_main_menu) { // in case of multiple scans in the greeting message view, delete the timer, a new one will be created. clearTimeout(this.return_to_main_menu); @@ -146,6 +147,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { }, destroy: function () { + core.bus.off('barcode_scanned', this, this._onBarcodeScanned); clearTimeout(this.return_to_main_menu); this._super.apply(this, arguments); }, diff --git a/addons/hr_attendance/static/src/js/kiosk_mode.js b/addons/hr_attendance/static/src/js/kiosk_mode.js index e633c4d53e1..6eb065739f6 100644 --- a/addons/hr_attendance/static/src/js/kiosk_mode.js +++ b/addons/hr_attendance/static/src/js/kiosk_mode.js @@ -4,24 +4,17 @@ odoo.define('hr_attendance.kiosk_mode', function (require) { var core = require('web.core'); var Widget = require('web.Widget'); var Session = require('web.session'); -var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); var QWeb = core.qweb; -var KioskMode = Widget.extend(BarcodeHandlerMixin, { +var KioskMode = Widget.extend({ events: { "click .o_hr_attendance_button_employees": function(){ this.do_action('hr_attendance.hr_employee_attendance_action_kanban'); }, }, - init: function (parent, action) { - // Note: BarcodeHandlerMixin.init calls this._super.init, so there's no need to do it here. - // Yet, "_super" must be present in a function for the class mechanism to replace it with the actual parent method. - this._super; - BarcodeHandlerMixin.init.apply(this, arguments); - }, - start: function () { var self = this; + core.bus.on('barcode_scanned', this, this._onBarcodeScanned); self.session = Session; this._rpc({ model: 'res.company', @@ -37,7 +30,7 @@ var KioskMode = Widget.extend(BarcodeHandlerMixin, { return self._super.apply(this, arguments); }, - on_barcode_scanned: function(barcode) { + _onBarcodeScanned: function(barcode) { var self = this; this._rpc({ model: 'hr.employee', @@ -60,6 +53,7 @@ var KioskMode = Widget.extend(BarcodeHandlerMixin, { }, destroy: function () { + core.bus.off('barcode_scanned', this, this._onBarcodeScanned); clearInterval(this.clock_start); this._super.apply(this, arguments); }, diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js index 8a5038557c2..ae513b0b506 100644 --- a/addons/web/static/src/js/core/mixins.js +++ b/addons/web/static/src/js/core/mixins.js @@ -508,11 +508,11 @@ var ServicesMixin = { this.trigger_up('do_action', {'action': action, options: options}); return def; }, - do_notify: function (title, message, sticky) { - this.trigger_up('notification', {title: title, message: message, sticky: sticky}); + do_notify: function (title, message, sticky, className) { + this.trigger_up('notification', {title: title, message: message, sticky: sticky, className: className}); }, - do_warn: function (title, message, sticky) { - this.trigger_up('warning', {title: title, message: message, sticky: sticky}); + do_warn: function (title, message, sticky, className) { + this.trigger_up('warning', {title: title, message: message, sticky: sticky, className: className}); }, }; diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index 1a4cebdc955..5d2bf18f1de 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -271,7 +271,7 @@ return core.Class.extend({ field.onChange = "1"; } - if (field.views) { + if (!_.isEmpty(field.views)) { // process the inner fields_view as well to find the fields they use. // register those fields' description directly on the view. // for those inner views, the list of all fields isn't necessary, so diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index f59cf96a881..ced26fb7cb2 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1052,7 +1052,13 @@ var BasicModel = AbstractModel.extend({ defs.push(def); break; case 'CREATE': - defs.push(this.addDefaultRecord(list.id)); + if (command.data) { + defs.push(this.addDefaultRecord(list.id).then(function (id) { + return this.notifyChanges(id, command.data); + })); + } else { + defs.push(this.addDefaultRecord(list.id)); + } break; case 'UPDATE': defs.push(this._applyChange(command.id, command.data)); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index e6e8c9a4d17..e22eebc89ce 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -4,13 +4,13 @@