diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/pos_devices.js
index 31ac45f856c..f87754c92c7 100644
--- a/addons/point_of_sale/static/src/js/pos_devices.js
+++ b/addons/point_of_sale/static/src/js/pos_devices.js
@@ -1,14 +1,120 @@
function openerp_pos_devices(module, instance){ //module is instance.point_of_sale
+
+ // this module interfaces with the local proxy to communicate to the various hardware devices
+ // connected to the Point of Sale. As the communication only goes from the POS to the proxy,
+ // methods are used both to signal an event, and to fetch information.
+ module.ProxyDevice = instance.web.Class.extend({
+ //a product has been scanned and recognized with success
+ scan_item_succes: function(){
+ },
+
+ //a product has been scanned but not recognized
+ scan_item_error_unrecognized: function(){
+ },
+
+ //the client is asking for help
+ help_needed: function(){
+ },
+
+ //the client does not need help anymore
+ help_canceled: function(){
+ },
+
+ //the client is starting to weight
+ weighting_start: function(){
+ },
+
+ //returns the weight on the scale.
+ // is called at regular interval (up to 10x/sec) between a weighting_start()
+ // and a weighting_end()
+ weighting_read_kg: function(){
+ return Math.random() + 0.1;
+ },
+
+ // the client has finished weighting products
+ weighting_end: function(){
+ },
+
+ // the pos asks the client to pay 'price' units
+ // method: 'mastercard' | 'cash' | ... ? TBD
+ // info: 'extra information to display on the payment terminal' ... ? TBD
+ payment_request: function(price, method, info){
+ },
+
+ // is called at regular interval after a payment request to see if the client
+ // has paid the required money
+ // returns 'waiting_for_payment' | 'payment_accepted' | 'payment_rejected'
+ is_payment_accepted: function(){
+ return 'waiting_for_payment'; // 'payment_accepted' | 'payment_rejected'
+ },
+
+ // the client cancels his payment
+ payment_canceled: function(){
+ },
+
+ // called when the client logs in or starts to scan product
+ transation_start: function(){
+ },
+
+ // called when the clients has finished his interaction with the machine
+ transaction_end: function(){
+ },
+
+ // called when the POS turns to cashier mode
+ cashier_mode_activated: function(){
+ },
+
+ // called when the POS turns to client mode
+ cashier_mode_deactivated: function(){
+ },
+ });
+
+ // this module interfaces with the barcode reader. It assumes the barcode reader
+ // is set-up to act like a keyboard. Use connect() and disconnect() to activate
+ // and deactivate the barcode reader. Use set_action_callbacks to tell it
+ // what to do when it reads a barcode.
module.BarcodeReader = instance.web.Class.extend({
init: function(attributes){
this.pos = attributes.pos;
- this.connect();
+ this.action_callbacks = {
+ 'product': undefined,
+ 'cashier': undefined,
+ 'client': undefined,
+ 'discount': undefined,
+ };
+ },
+
+ // when an ean is scanned and parsed, the callback corresponding
+ // to its type is called with the parsed_ean as a parameter.
+ // (parsed_ean is the result of parse_ean(ean))
+ //
+ // callbacks is a Map of 'actions' : callback(parsed_ean)
+ // that sets the callback for each action. if a callback for the
+ // specified action already exists, it is replaced.
+ //
+ // possible actions include :
+ // 'product' | 'cashier' | 'client' | 'discount'
+
+ set_action_callbacks: function(callbacks){
+ for(action in callbacks){
+ this.action_callbacks[action] = callbacks[action];
+ }
},
- // returns true if the code is a valid EAN codebar number by checking the control digit.
- checkEan: function(code){
+ //remove all action callbacks
+ reset_action_callbacks: function(){
+ for(action in this.action_callbacks){
+ this.action_callbacks[action] = undefined;
+ }
+ },
+
+
+ // returns true if the ean is a valid EAN codebar number by checking the control digit.
+ // ean must be a string
+ check_ean: function(ean){
+ var code = ean.split('');
var st1 = code.slice();
var st2 = st1.slice(0,st1.length-1).reverse();
// some EAN13 barcodes have a length of 12, as they start by 0
@@ -36,42 +142,112 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa
var cd = (10 - (st5%10)) % 10;
return code[code.length-1] === cd;
},
+
+ // attempts to interpret an ean (string encoding an ean)
+ // it will check its validity then return an object containing various
+ // information about the ean.
+ // most importantly :
+ // - ean : the ean
+ // - type : the type of the ean:
+ // 'price' | 'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error'
+ //
+ // - prefix : the prefix that has ben used to determine the type
+ // - id : the part of the ean that identifies something
+ // - value : if the id encodes a numerical value, it will be put there
+ // - unit : if the encoded value has a unit, it will be put there.
+ // not to be confused with the 'unit' type, which represent an unit of a
+ // unique product
+
+ parse_ean: function(ean){
+ var parse_result = {
+ type:'unknown', //
+ prefix:'',
+ ean:ean,
+ id:'',
+ value: 0,
+ unit: 'none',
+ };
+ var prefix2 = ean.substring(0,2);
+
+ if(!this.check_ean(ean)){
+ parse_result.type = 'error';
+ }else if (prefix2 in {'02':'', '22':'', '24':'', '26':'', '28':''}){
+ parse_result.type = 'price';
+ parse_result.prefix = prefix2;
+ parse_result.id = ean.substring(0,7);
+ parse_result.value = Number(ean.substring(7,12))/100.0;
+ parse_result.unit = 'euro';
+ } else if (prefix2 in {'21':'','23':'','27':'','29':'','25':''}) {
+ parse_result.type = 'weight';
+ parse_result.prefix = prefix2;
+ prase_result.id = ean.substring(0,7);
+ parse_result.value = Number(ean.substring(7,12))/1000.0;
+ parse_result.unit = 'Kg';
+ }else{
+ parse_result.type = 'unit';
+ parse_result.prefix = '';
+ parse_result.id = ean;
+ }
+ return parse_result;
+ },
// returns a product that has a packaging with an EAN matching to provided ean string.
// returns undefined if no such product is found.
- getProductByEAN: function(ean) {
+ get_product_by_ean: function(ean) {
var allProducts = this.pos.get('product_list');
var allPackages = this.pos.get('product.packaging');
- var prefix = ean.substring(0,2);
var scannedProductModel = undefined;
+ var parse_result = this.parse_ean(ean);
- if (prefix in {'02':'', '22':'', '24':'', '26':'', '28':''}) {
-
- // PRICE barcode
- var itemCode = ean.substring(0,7);
+ if (parse_result.type === 'price') {
+ var itemCode = parse_result.id;
var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;});
if (scannedPackaging !== undefined) {
scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
- scannedProductModel.list_price = Number(ean.substring(7,12))/100;
+ scannedProductModel.list_price = parse_result.value;
}
- } else if (prefix in {'21':'','23':'','27':'','29':'','25':''}) {
- // WEIGHT barcode
- var weight = Number(barcode.substring(7,12))/1000;
- var itemCode = ean.substring(0,7);
+ } else if (parse_result.type === 'weight') {
+ var weight = parse_result.value;
+ var itemCode = parse_result.id;
var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;});
if (scannedPackaging !== undefined) {
scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];});
scannedProductModel.list_price *= weight;
scannedProductModel.name += ' - ' + weight + ' Kg.';
}
- } else {
- // UNIT barcode
+ } else if(parse_result.type === 'unit'){
scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === ean;}); //TODO DOES NOT SCALE
}
return scannedProductModel;
},
+ // a default callback for the 'product' action. It will select the product
+ // corresponding to the ean and add it to the current order.
+ scan_product_callback: function(parse_result){
+ var selectedOrder = self.pos.get('selectedOrder');
+ var scannedProductModel = self.get_product_by_ean(parse_result.ean);
+ if (scannedProductModel === undefined) {
+ // product not recognized, raise warning
+ $(QWeb.render('pos-scan-warning')).dialog({
+ resizable: false,
+ height:220,
+ modal: true,
+ title: "Warning",
+ /*
+ buttons: {
+ "OK": function() {
+ $( this ).dialog( "close" );
+ return;
+ },
+ }*/
+ });
+ } else {
+ selectedOrder.addProduct(new module.Product(scannedProductModel));
+ }
+ },
+
// starts catching keyboard events and tries to interpret codebar
+ // calling the callbacks when needed.
connect: function(){
var self = this;
var codeNumbers = [];
@@ -99,37 +275,24 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa
codeNumbers.push(e.keyCode - 48);
lastTimeStamp = new Date().getTime();
if (codeNumbers.length == 13) {
- //console.log('found code:', codeNumbers.join(''));
+ //We have found what seems to be a valid codebar
+ var parse_result = this.parse_ean(codeNumbers.join(''));
- // a barcode reader
- if (!self.checkEan(codeNumbers)) {
- // barcode read error, raise warning
+ if (parse_result.type === 'error') { //most likely a checksum error, raise warning
$(QWeb.render('pos-scan-warning')).dialog({
resizable: false,
height:220,
modal: true,
title: "Warning",
});
- }
- var selectedOrder = self.pos.get('selectedOrder');
- var scannedProductModel = self.getProductByEAN(codeNumbers.join(''),allPackages,allProducts);
- if (scannedProductModel === undefined) {
- // product not recognized, raise warning
- $(QWeb.render('pos-scan-warning')).dialog({
- resizable: false,
- height:220,
- modal: true,
- title: "Warning",
- /*
- buttons: {
- "OK": function() {
- $( this ).dialog( "close" );
- return;
- },
- }*/
- });
- } else {
- selectedOrder.addProduct(new module.Product(scannedProductModel));
+ }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
+ if(this.action_callback['product']){
+ this.action_callback['product'](parse_result);
+ }
+ }else{
+ if(this.action_callback[parse_result.type]){
+ this.action_callback[parse_result.type](parse_result);
+ }
}
codeNumbers = [];
@@ -146,4 +309,5 @@ function openerp_pos_devices(module, instance){ //module is instance.point_of_sa
$('body').undelegate('', 'keyup')
},
});
+
}
diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js
index 42846caaa4c..981feb6e4d1 100644
--- a/addons/point_of_sale/static/src/js/pos_models.js
+++ b/addons/point_of_sale/static/src/js/pos_models.js
@@ -57,6 +57,8 @@ function openerp_pos_models(module, instance){ //module is instance.point_of_sal
this.build_tree = _.bind(this.build_tree, this);
this.session = session;
this.categories = {};
+ this.barcode_reader = new module.BarcodeReader({'pos': this});
+ this.proxy = new module.ProxyDevice({'pos': this});
this.set({
'nbr_pending_operations': 0,
'currency': {symbol: '$', position: 'after'},
diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js
index 48efc782600..ae17c6dbbc4 100644
--- a/addons/point_of_sale/static/src/js/pos_widgets.js
+++ b/addons/point_of_sale/static/src/js/pos_widgets.js
@@ -1033,14 +1033,6 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa
this.buildWidgets();
- //self.pos.app = new module.App(self.$element, self.pos);
-
- //this.shopView = new module.ShopWidget(null, { 'pos': this.pos } );
- //this.shopView.$element = self.$element;
- //this.shopView.start();
-
-
-
instance.webclient.set_content_full_screen(true);
if (self.pos.get('bank_statements').length === 0)
@@ -1114,15 +1106,15 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa
});
this.onscreenKeyboard.appendTo($(".point-of-sale #content"));
- this.barcodeReader = new module.BarcodeReader({'pos': self.pos });
-
- this.actionBar = new module.ActionbarWidget(null);
- this.actionBar.appendTo($(".point-of-sale #content"));
- this.actionBar.addNewButton('left',{
+ this.action_bar = new module.ActionbarWidget(null);
+ this.action_bar.appendTo($(".point-of-sale #content"));
+ this.action_bar.addNewButton('left',{
label : 'Hello World',
icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png',
click : function(){ console.log("Hello World!"); }
});
+
+ this.pos.barcode_reader.connect();
},
createNewOrder: function() {
@@ -1177,7 +1169,7 @@ function openerp_pos_widgets(module, instance){ //module is instance.point_of_sa
}, this));
},
close: function() {
- this.barcodeReader.disconnect();
+ this.pos.barcode_reader.disconnect();
return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_close_statement']], ['res_id']).pipe(
_.bind(function(res) {
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 25269e9abf2..7abd08f1974 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -217,6 +217,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+