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