diff --git a/addons/barcodes/static/src/barcode_handlers.js b/addons/barcodes/static/src/barcode_handlers.js index 20b64281830..d45d08669f8 100644 --- a/addons/barcodes/static/src/barcode_handlers.js +++ b/addons/barcodes/static/src/barcode_handlers.js @@ -56,8 +56,8 @@ const COMMANDS = { }; export const barcodeGenericHandlers = { - dependencies: ["ui", "barcode"], - start(env, { ui, barcode }) { + dependencies: ["ui", "barcode", "notification"], + start(env, { ui, barcode, notification }) { barcode.bus.addEventListener("barcode_scanned", (ev) => { const barcode = ev.detail.barcode; @@ -79,7 +79,10 @@ export const barcodeGenericHandlers = { if (fn) { fn(); } else { - console.warn(`Barcode '${barcode}' is not a valid command`); + notification.add(env._t("Barcode: ") + `'${barcode}'`, { + title: env._t("Unknown barcode command"), + type: "danger" + }); } } }); diff --git a/addons/barcodes/static/src/js/barcode_field.js b/addons/barcodes/static/src/js/barcode_field.js index a0105e4a21a..c9eebcbeff4 100644 --- a/addons/barcodes/static/src/js/barcode_field.js +++ b/addons/barcodes/static/src/js/barcode_field.js @@ -4,6 +4,7 @@ odoo.define('barcodes.field', function(require) { var AbstractField = require('web.AbstractField'); var basicFields = require('web.basic_fields'); var fieldRegistry = require('web.field_registry'); +var core = require('web.core'); // Field in which the user can both type normally and scan barcodes @@ -37,29 +38,24 @@ var FieldFloatScannable = basicFields.FieldFloat.extend({ } }); -// Field to use scan barcodes var FormViewBarcodeHandler = AbstractField.extend({ /** * @override */ init: function() { this._super.apply(this, arguments); - - this.trigger_up('activeBarcode', { - name: this.name, - commands: { - barcode: '_barcodeAddX2MQuantity', - } - }); + core.bus.on('barcode_scanned', this, this._barcodeScanned); + }, + destroy: function () { + core.bus.off('barcode_scanned', this, this._barcodeScanned); + this._super(); + }, + _barcodeScanned(barcode) { + this._setValue(barcode); }, }); 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 deleted file mode 100644 index efb5939d262..00000000000 --- a/addons/barcodes/static/src/js/barcode_form_view.js +++ /dev/null @@ -1,255 +0,0 @@ -odoo.define('barcodes.FormView', function (require) { -"use strict"; - -var concurrency = require('web.concurrency'); -var core = require('web.core'); -var FormController = require('web.FormController'); - -var _t = core._t; - -const reservedBarcodePrefixes = ['O-CMD']; - - -FormController.include({ - custom_events: _.extend({}, FormController.prototype.custom_events, { - activeBarcode: '_barcodeActivated', - }), - - /** - * add default barcode commands for from view - * - * @override - */ - init: function () { - this._super.apply(this, arguments); - this.activeBarcode = { - form_view: { - commands: { - }, - }, - }; - - this.barcodeMutex = new concurrency.Mutex(); - this._barcodeStartListening(); - }, - /** - * @override - */ - destroy: function () { - this._barcodeStopListening(); - this._super(); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - * @param {string} barcode sent by the scanner (string generate from keypress series) - * @param {Object} activeBarcode: options sent by the field who use barcode features - * @returns {Promise} - */ - _barcodeAddX2MQuantity: function (barcode, activeBarcode) { - if (this.mode === 'readonly') { - this.displayNotification({ message: _t('Enable edit mode to modify this document'), type: 'danger' }); - return Promise.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); - } - }, - /** - * Returns true iff the given barcode matches the given record (candidate). - * - * @private - * @param {Object} candidate: record in the x2m - * @param {string} barcode sent by the scanner (string generate from keypress series) - * @param {Object} activeBarcode: options sent by the field who use barcode features - * @returns {boolean} - */ - _barcodeRecordFilter: function (candidate, barcode, activeBarcode) { - return candidate.data.product_barcode === barcode; - }, - /** - * @private - * @param {Object} candidate: record in the x2m - * @param {Object} current record - * @param {string} barcode sent by the scanner (string generate from keypress series) - * @param {Object} activeBarcode: options sent by the field who use barcode features - * @returns {Promise} - */ - _barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode, quantity) { - var changes = {}; - var candidateChanges = {}; - candidateChanges[activeBarcode.quantity] = quantity ? quantity : candidate.data[activeBarcode.quantity] + 1; - changes[activeBarcode.fieldName] = { - operation: 'UPDATE', - id: candidate.id, - data: candidateChanges, - }; - return this.model.notifyChanges(this.handle, changes, {notifyChange: activeBarcode.notifyChange}); - }, - /** - * @private - */ - _barcodeStartListening: function () { - core.bus.on('barcode_scanned', this, this._barcodeScanned); - }, - /** - * @private - */ - _barcodeStopListening: function () { - core.bus.off('barcode_scanned', this, this._barcodeScanned); - }, - /** - * @private - * @param {Object} current record - * @param {string} barcode sent by the scanner (string generate from keypress series) - * @param {Object} activeBarcode: options sent by the field who use barcode features - * @returns {Promise} - */ - _barcodeWithoutCandidate: function (record, barcode, activeBarcode) { - var changes = {}; - changes[activeBarcode.name] = barcode; - return this.model.notifyChanges(record.id, changes); - }, - /** - * @private - * @param {Object} current record - * @param {string} barcode sent by the scanner (string generate from keypress series) - * @param {Object} activeBarcode: options sent by the field who use barcode features - * @returns {Object|undefined} - */ - _getBarCodeRecord: function (record, barcode, activeBarcode) { - var self = this; - if (!activeBarcode.fieldName || !record.data[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 - * @param {string} event.data.name: the current field name - * @param {string} [event.data.fieldName] optional for x2many sub field - * @param {boolean} [event.data.notifyChange] optional for x2many sub field - * do not trigger on change server side if a candidate has been found - * @param {string} [event.data.quantity] optional field to increase quantity - * @param {Object} [event.data.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 () {...}) - */ - _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, - setQuantityWithKeypress: !! event.data.setQuantityWithKeypress, - fieldName: event.data.fieldName, - notifyChange: (event.data.notifyChange !== undefined) ? event.data.notifyChange : true, - quantity: event.data.quantity, - commands: event.data.commands || {}, - candidate: this.activeBarcode[name] && this.activeBarcode[name].handle === this.handle ? - this.activeBarcode[name].candidate : null, - }; - - // we want to disable autofocus when activating the barcode to avoid - // putting the scanned value in the focused field - this.disableAutofocus = true; - }, - /** - * @private - * @param {string|function} method defined by the commands options - * @param {string} barcode sent by the scanner (string generate from keypress series) - * @param {Object} activeBarcode: options sent by the field who use barcode features - * @returns {Promise} - */ - _barcodeActiveScanned: function (method, barcode, activeBarcode) { - var self = this; - var methodDef; - var def = new Promise(function (resolve, reject) { - if (typeof method === 'string') { - methodDef = self[method](barcode, activeBarcode); - } else { - methodDef = method.call(self, barcode, activeBarcode); - } - methodDef - .then(function () { - var record = self.model.get(self.handle); - var candidate = self._getBarCodeRecord(record, barcode, activeBarcode); - activeBarcode.candidate = candidate; - }) - .then(resolve, 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 sent by the scanner (string generate from keypress series) - * @param {DOM Object} target - * @returns {Promise} - */ - _barcodeScanned: function (barcode, target) { - var self = this; - return this.barcodeMutex.exec(function () { - var prefixed = _.any(reservedBarcodePrefixes, - function (reserved) {return barcode.indexOf(reserved) === 0;}); - var hasCommand = false; - var defs = []; - if (! $.contains(target, self.el)) { - return; - } - for (var k in self.activeBarcode) { - var activeBarcode = self.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. - var methods = self.activeBarcode[k].commands; - var method = prefixed ? methods[barcode] : methods.barcode; - if (method) { - if (prefixed) { - hasCommand = true; - } - defs.push(self._barcodeActiveScanned(method, barcode, activeBarcode)); - } - } - if (prefixed && !hasCommand) { - self.displayNotification({ title: _t('Undefined barcode command'), message: barcode, type: 'danger' }); - } - return self.alive(Promise.all(defs)).then(function () { - if (!prefixed) { - // remember the barcode scanned for the quantity listener - self.current_barcode = barcode; - // redraw the view if we scanned a real barcode (required if - // we manually apply the change in JS, e.g. incrementing the - // quantity) - self.update({}, {reload: false}); - } - }); - }); - }, -}); - -}); diff --git a/addons/barcodes/static/tests/barcode_tests.js b/addons/barcodes/static/tests/barcode_tests.js index 8de9519ea00..6397a278628 100644 --- a/addons/barcodes/static/tests/barcode_tests.js +++ b/addons/barcodes/static/tests/barcode_tests.js @@ -8,8 +8,6 @@ const { makeTestEnv } = require("@web/../tests/helpers/mock_env"); const { registry } = require("@web/core/registry"); const { mockTimeout } = require("@web/../tests/helpers/utils"); -var AbstractField = require('web.AbstractField'); -var fieldRegistry = require('web.field_registry'); var FormController = require('web.FormController'); var FormView = require('web.FormView'); var testUtils = require('web.test_utils'); @@ -178,7 +176,6 @@ QUnit.test('pager buttons', async function (assert) { var form = await createView({ View: FormView, - debug: true, model: 'product', data: this.data, arch: '
', @@ -215,7 +212,7 @@ QUnit.test('pager buttons', async function (assert) { }); QUnit.test('do no update form twice after a command barcode scanned', async function (assert) { - assert.expect(7); + assert.expect(5); testUtils.mock.patch(FormController, { update: function () { @@ -254,12 +251,6 @@ QUnit.test('do no update form twice after a command barcode scanned', async func // update shouldn't be called afterwards assert.verifySteps(['update', 'read']); - simulateBarCode(['5','4','3','9','8','2','6','7','1','2','5','2','Enter']); - await testUtils.nextTick(); - // a real barcode has been scanned -> an update should be requested (with - // option reload='false', so it isn't followed by a read) - assert.verifySteps(['update']); - form.destroy(); testUtils.mock.unpatch(FormController); }); @@ -323,126 +314,4 @@ QUnit.test('widget field_float_scannable', async function (assert) { core.bus.off('barcode_scanned', null, _onBarcodeScanned) }); -QUnit.test('widget barcode_handler', async function (assert) { - assert.expect(4); - - this.data.product.fields.barcode_scanned = {string : "Scanned barcode", type: "char"}; - this.data.product.onchanges = { - barcode_scanned: function (obj) { - // simulate an onchange that increment the int_field value - // at each barcode scanned - obj.int_field = obj.int_field + 1; - }, - }; - - var form = await createView({ - View: FormView, - model: 'product', - data: this.data, - arch: '', - mockRPC: function (route, args) { - if (args.method === 'onchange') { - assert.step('onchange'); - } - return this._super.apply(this, arguments); - }, - res_id: 1, - viewOptions: { - mode: 'edit', - }, - }); - - assert.strictEqual(form.$('.o_field_widget[name=int_field]').val(), '0', - "initial value should be correct"); - - simulateBarCode(['5','4','3','9','8','2','6','7','1','2','5','2','Enter']); - await testUtils.nextTick(); - assert.strictEqual(form.$('.o_field_widget[name=int_field]').val(), '1', - "value should have been incremented"); - - assert.verifySteps(['onchange'], "an onchange should have been done"); - - form.destroy(); -}); - -QUnit.test('specification of widget barcode_handler', async function (assert) { - assert.expect(5); - - // Define a specific barcode_handler widget for this test case - var TestBarcodeHandler = AbstractField.extend({ - init: function () { - this._super.apply(this, arguments); - - this.trigger_up('activeBarcode', { - name: 'test', - fieldName: 'line_ids', - quantity: 'quantity', - commands: { - barcode: '_barcodeAddX2MQuantity', - } - }); - }, - }); - fieldRegistry.add('test_barcode_handler', TestBarcodeHandler); - - var form = await createView({ - View: FormView, - model: 'order', - data: this.data, - arch: '', - mockRPC: function (route, args) { - if (args.method === 'onchange') { - assert.notOK(true, "should not do any onchange RPC"); - } - if (args.method === 'write') { - assert.deepEqual(args.args[1].line_ids, [ - [1, 1, {quantity: 2}], [1, 2, {quantity: 1}], - ], "should have generated the correct commands"); - } - return this._super.apply(this, arguments); - }, - res_id: 1, - viewOptions: { - mode: 'edit', - }, - }); - - assert.containsN(form, '.o_data_row', 2, - "one2many should contain 2 rows"); - - // scan twice product 1 - simulateBarCode(['1','2','3','4','5','6','7','8','9','0','Enter']); - await testUtils.nextTick(); - assert.strictEqual(form.$('.o_data_row:first .o_data_cell:nth(1)').text(), '1', - "quantity of line one should have been incremented"); - simulateBarCode(['1','2','3','4','5','6','7','8','9','0','Enter']); - await testUtils.nextTick(); - assert.strictEqual(form.$('.o_data_row:first .o_data_cell:nth(1)').text(), '2', - "quantity of line one should have been incremented"); - - // scan once product 2 - simulateBarCode(['0','9','8','7','6','5','4','3','2','1','Enter']); - await testUtils.nextTick(); - assert.strictEqual(form.$('.o_data_row:nth(1) .o_data_cell:nth(1)').text(), '1', - "quantity of line one should have been incremented"); - - await testUtils.form.clickSave(form); - - form.destroy(); - delete fieldRegistry.map.test_barcode_handler; -}); - });