[REF] barcode, *: update code to new JS framework
With the new views, the barcode mechanisms were completely broken, because the view and widget JS code was totally changed. This commit update and refactor all that code to make it work with the new design philosophy. Also, it update the coding style.
This commit is contained in:
committed by
Aaron Bohy
parent
c5cf445462
commit
920df597d3
@@ -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',
|
||||
|
||||
@@ -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 <input> 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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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 = $('<div>').append($('<input>', {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');
|
||||
|
||||
});
|
||||
@@ -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.');
|
||||
},
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
});
|
||||
@@ -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 = $('<div>').append($('<input>', {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;
|
||||
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
});
|
||||
@@ -5,10 +5,8 @@
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/barcode_parser.js"></script>
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/barcode_events.js"></script>
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/barcode_handler_mixin.js"></script>
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/form_view_barcode_handler.js"></script>
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/inherited_form_widget_button.js"></script>
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/field_float_scannable.js"></script>
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/barcode_form_view.js"></script>
|
||||
<script type="text/javascript" src="/barcodes/static/src/js/barcode_field.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -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.<br/>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);
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<div t-name="EmptyComponent"/>
|
||||
<div t-name="Loading" class="o_loading"/>
|
||||
<t t-name="Notification">
|
||||
<div class="o_notification" t-translation="off">
|
||||
<div t-attf-class="o_notification #{className}" t-translation="off">
|
||||
<a t-if="widget.sticky" class="fa fa-times o_close" href="#"/>
|
||||
<div class="o_notification_title">
|
||||
<span class="o_icon fa fa-3x fa-lightbulb-o"/>
|
||||
<t t-raw="widget.title"/>
|
||||
</div>
|
||||
<div class="o_notification_content"><t t-raw="widget.text"/></div>
|
||||
<div class="o_notification_content" t-if="widget.text"><t t-raw="widget.text"/></div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="Warning" t-extend="Notification">
|
||||
|
||||
Reference in New Issue
Block a user