diff --git a/pos_keyboard/README.rst b/pos_keyboard/README.rst new file mode 100644 index 0000000..57394d7 --- /dev/null +++ b/pos_keyboard/README.rst @@ -0,0 +1,24 @@ +Keyboard support in Point Of Sale +================================================================ +Module allows to use usual keyboard (not virtual one) in Point of Sale. + +Usage: +------ +Using keys below switch to mode you need. Qty mode is used by default. +Then use number keys to enter quantity, price or discount. + + +=========== ===================== ================= +Type Numpad Extra keys +=========== ===================== ================= +mode qty ``/`` ``q`` +----------- --------------------- ----------------- +mode disc ``-`` ``d`` +----------- --------------------- ----------------- +mode price ``*`` ``p`` +----------- --------------------- ----------------- ++/- ``+`` ``s`` +=========== ===================== ================= + + +Tested on Odoo 9.0 2ec9a9c99294761e56382bdcd766e90b8bc1bb38 diff --git a/pos_keyboard/__init__.py b/pos_keyboard/__init__.py new file mode 100644 index 0000000..40a96af --- /dev/null +++ b/pos_keyboard/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pos_keyboard/__openerp__.py b/pos_keyboard/__openerp__.py new file mode 100644 index 0000000..562ad51 --- /dev/null +++ b/pos_keyboard/__openerp__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Keyboard support in Point Of Sale", + 'author': "IT-Projects LLC, Ivan Yelizariev", + 'summary': 'Module allows to use usual keyboard (not virtual one) in Point of Sale', + "website": "https://it-projects.info", + 'images': ['images/keyboard.png'], + 'category': 'Point Of Sale', + 'license': 'LGPL-3', + 'version': '1.0.2', + 'depends': ['point_of_sale'], + 'data': [ + 'data.xml', + ], + 'installable': True, + 'auto_install': False, +} diff --git a/pos_keyboard/data.xml b/pos_keyboard/data.xml new file mode 100644 index 0000000..d76abed --- /dev/null +++ b/pos_keyboard/data.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/pos_keyboard/doc/changelog.rst b/pos_keyboard/doc/changelog.rst new file mode 100644 index 0000000..69d509e --- /dev/null +++ b/pos_keyboard/doc/changelog.rst @@ -0,0 +1,14 @@ +.. _changelog: + +Changelog +========= + +`1.0.2` +------- + +- Fix bug: Repeat last keystroke when press non-number key or non-shortcut key after keypress on the number or shortcut key (i.e. q15jj ended up as quantity 1555) + +`1.0.1` +------- + +- Fix barcode scanner bug diff --git a/pos_keyboard/images/keyboard.png b/pos_keyboard/images/keyboard.png new file mode 100644 index 0000000..cbf5e6a Binary files /dev/null and b/pos_keyboard/images/keyboard.png differ diff --git a/pos_keyboard/static/description/icon.png b/pos_keyboard/static/description/icon.png new file mode 100644 index 0000000..8a05828 Binary files /dev/null and b/pos_keyboard/static/description/icon.png differ diff --git a/pos_keyboard/static/src/js/pos.js b/pos_keyboard/static/src/js/pos.js new file mode 100644 index 0000000..8f37e30 --- /dev/null +++ b/pos_keyboard/static/src/js/pos.js @@ -0,0 +1,190 @@ +odoo.define('pos_keyboard.pos', function (require) { + "use strict"; + + var core = require('web.core'); + var models = require('point_of_sale.models'); + var screens = require('point_of_sale.screens'); + + var _super_posmodel = models.PosModel.prototype; + models.PosModel = models.PosModel.extend({ + initialize: function (session, attributes) { + this.keypad = new Keypad({'pos': this}); + return _super_posmodel.initialize.call(this, session, attributes); + } + }); + + screens.NumpadWidget.include({ + start: function() { + this._super(); + var self = this; + this.pos.keypad.set_action_callback(function(data){ + self.keypad_action(data, self.pos.keypad.type); + }); + }, + keypad_action: function(data, type){ + if (data.type === type.numchar){ + this.state.appendNewChar(data.val); + } + else if (data.type === type.bmode) { + this.state.changeMode(data.val); + } + else if (data.type === type.sign){ + this.clickSwitchSign(); + } + else if (data.type === type.backspace){ + this.clickDeleteLastChar(); + } + } + }); + + screens.PaymentScreenWidget.include({ + show: function(){ + this._super(); + this.pos.keypad.disconnect(); + }, + hide: function(){ + this._super(); + this.pos.keypad.connect(); + } + }); + + // this module mimics a keypad-only cash register. Use connect() and + // disconnect() to activate and deactivate it. + var Keypad = core.Class.extend({ + init: function(attributes){ + this.pos = attributes.pos; + /*this.pos_widget = this.pos.pos_widget;*/ + this.type = { + numchar: 'number, dot', + bmode: 'quantity, discount, price', + sign: '+, -', + backspace: 'backspace' + }; + this.data = { + type: undefined, + val: undefined + }; + this.action_callback = undefined; + }, + + save_callback: function(){ + this.saved_callback_stack.push(this.action_callback); + }, + + restore_callback: function(){ + if (this.saved_callback_stack.length > 0) { + this.action_callback = this.saved_callback_stack.pop(); + } + }, + + set_action_callback: function(callback){ + this.action_callback = callback + }, + + //remove action callback + reset_action_callback: function(){ + this.action_callback = undefined; + }, + + // starts catching keyboard events and tries to interpret keystrokes, + // calling the callback when needed. + connect: function(){ + var self = this; + // --- additional keyboard ---// + var KC_PLU = 107; // KeyCode: + or - (Keypad '+') + var KC_QTY = 111; // KeyCode: Quantity (Keypad '/') + var KC_AMT = 106; // KeyCode: Price (Keypad '*') + var KC_DISC = 109; // KeyCode: Discount Percentage [0..100] (Keypad '-') + // --- basic keyboard --- // + var KC_PLU_1 = 83; // KeyCode: sign + or - (Keypad 's') + var KC_QTY_1 = 81; // KeyCode: Quantity (Keypad 'q') + var KC_AMT_1 = 80; // KeyCode: Price (Keypad 'p') + var KC_DISC_1 = 68; // KeyCode: Discount Percentage [0..100] (Keypad 'd') + + var KC_BACKSPACE = 8; // KeyCode: Backspace (Keypad 'backspace') + var kc_lookup = { + 48: '0', 49: '1', 50: '2', 51: '3', 52: '4', + 53: '5', 54: '6', 55: '7', 56: '8', 57: '9', + 80: 'p', 83: 's', 68: 'd', 190: '.', 81: 'q', + 96: '0', 97: '1', 98: '2', 99: '3', 100: '4', + 101: '5', 102: '6', 103: '7', 104: '8', 105: '9', + 106: '*', 107: '+', 109: '-', 110: '.', 111: '/' + }; + + //usb keyboard keyup event + var rx = /INPUT|SELECT|TEXTAREA/i; + var ok = false; + var timeStamp = 0; + $('body').on('keyup', '', function (e){ + var statusHandler = !rx.test(e.target.tagName) || + e.target.disabled || e.target.readOnly; + if (statusHandler){ + var is_number = false; + var type = self.type; + var buttonMode = { + qty: 'quantity', + disc: 'discount', + price: 'price' + }; + var token = e.keyCode; + if ((token >= 96 && token <= 105 || token == 110) || + (token >= 48 && token <= 57 || token == 190)) { + self.data.type = type.numchar; + self.data.val = kc_lookup[token]; + is_number = true; + ok = true; + } + else if (token == KC_PLU || token == KC_PLU_1) { + self.data.type = type.sign; + ok = true; + } + else if (token == KC_QTY || token == KC_QTY_1) { + self.data.type = type.bmode; + self.data.val = buttonMode.qty; + ok = true; + } + else if (token == KC_AMT || token == KC_AMT_1) { + self.data.type = type.bmode; + self.data.val = buttonMode.price; + ok = true; + } + else if (token == KC_DISC || token == KC_DISC_1) { + self.data.type = type.bmode; + self.data.val = buttonMode.disc; + ok = true; + } + else if (token == KC_BACKSPACE) { + self.data.type = type.backspace; + ok = true; + } + else { + self.data.type = undefined; + self.data.val = undefined; + ok = false; + } + + if (is_number) { + if (timeStamp + 50 > new Date().getTime()) { + ok = false; + } + } + + timeStamp = new Date().getTime(); + + setTimeout(function(){ + if (ok) {self.action_callback(self.data);} + }, 50); + } + }); + }, + + // stops catching keyboard events + disconnect: function(){ + $('body').off('keyup', '') + } + }); + + return { + Keypad: Keypad + }; +});