2 function openerp_pos_devices(instance,module){ //module is instance.point_of_sale
4 var QWeb = instance.web.qweb; //TODO FIXME this should NOT be of any use in this file
6 window.debug_devices = new (instance.web.Class.extend({
7 payment_status: 'waiting_for_payment',
9 accept_payment: function(){ this.payment_status = 'payment_accepted'; },
10 reject_payment: function(){ this.payment_status = 'payment_rejected'; },
11 delay_payment: function(){ this.payment_status = 'waiting_for_payment'; },
14 // this object interfaces with the local proxy to communicate to the various hardware devices
15 // connected to the Point of Sale. As the communication only goes from the POS to the proxy,
16 // methods are used both to signal an event, and to fetch information.
18 module.ProxyDevice = instance.web.Class.extend({
19 init: function(options){
20 options = options || {};
21 url = options.url || 'http://localhost:8069';
23 this.connection = new instance.web.JsonRPC();
24 this.connection.setup(url);
26 message : function(name,params,callback){
27 var success_callback = function(result){ console.log('PROXY SUCCESS:'+name+': ',result); }
28 var error_callback = function(result){ console.log('PROXY ERROR:'+name+': ',result); }
29 this.connection.rpc('/pos/'+name, params || {}, callback || success_callback, error_callback);
32 //a product has been scanned and recognized with success
33 scan_item_success: function(){
34 this.message('scan_item_success');
35 console.log('PROXY: scan item success');
38 //a product has been scanned but not recognized
39 scan_item_error_unrecognized: function(){
40 this.message('scan_item_error_unrecognized');
41 console.log('PROXY: scan item error');
44 //the client is asking for help
45 help_needed: function(){
46 this.message('help_needed');
47 console.log('PROXY: help needed');
50 //the client does not need help anymore
51 help_canceled: function(){
52 this.message('help_canceled');
53 console.log('PROXY: help canceled');
56 //the client is starting to weight
57 weighting_start: function(){
58 this.message('weighting_start');
59 console.log('PROXY: weighting start');
62 //returns the weight on the scale.
63 // is called at regular interval (up to 10x/sec) between a weighting_start()
64 // and a weighting_end()
65 weighting_read_kg: function(){
66 this.message('weighting_read_kg');
67 console.log('PROXY: weighting read');
68 //return Math.random() + 0.1;
69 return window.debug_devices.weight;
72 // the client has finished weighting products
73 weighting_end: function(){
74 this.message('weighting_end');
75 console.log('PROXY: weighting end');
78 // the pos asks the client to pay 'price' units
79 // method: 'mastercard' | 'cash' | ... ? TBD
80 // info: 'extra information to display on the payment terminal' ... ? TBD
81 payment_request: function(price, method, info){
82 this.message('payment_request',{'price':price,'method':method,'info':info});
83 console.log('PROXY: payment request:',price,method,info);
86 // is called at regular interval after a payment request to see if the client
87 // has paid the required money
88 // returns 'waiting_for_payment' | 'payment_accepted' | 'payment_rejected'
89 is_payment_accepted: function(){
90 this.message('is_payment_accepted');
91 console.log('PROXY: is payment accepted ?');
92 //return 'waiting_for_payment'; // 'payment_accepted' | 'payment_rejected'
93 return window.debug_devices.payment_status;
96 // the client cancels his payment
97 payment_canceled: function(){
98 this.message('payment_canceled');
99 console.log('PROXY: payment canceled by client');
102 // called when the client logs in or starts to scan product
103 transaction_start: function(){
104 this.message('transaction_start');
105 console.log('PROXY: transaction start');
108 // called when the clients has finished his interaction with the machine
109 transaction_end: function(){
110 this.message('transaction_end');
111 console.log('PROXY: transaction end');
114 // called when the POS turns to cashier mode
115 cashier_mode_activated: function(){
116 this.message('cashier_mode_activated');
117 console.log('PROXY: cashier mode activated');
120 // called when the POS turns to client mode
121 cashier_mode_deactivated: function(){
122 this.message('cashier_mode_deactivated');
123 console.log('PROXY: client mode activated');
127 // this module interfaces with the barcode reader. It assumes the barcode reader
128 // is set-up to act like a keyboard. Use connect() and disconnect() to activate
129 // and deactivate the barcode reader. Use set_action_callbacks to tell it
130 // what to do when it reads a barcode.
131 module.BarcodeReader = instance.web.Class.extend({
133 init: function(attributes){
134 this.pos = attributes.pos;
135 this.action_callback = {
136 'product': undefined,
137 'cashier': undefined,
139 'discount': undefined,
142 this.action_callback_stack = [];
144 this.price_prefix_set = attributes.price_prefix_set || {'02':'', '22':'', '24':'', '26':'', '28':''};
145 this.weight_prefix_set = attributes.weight_prefix_set || {'21':'','23':'','27':'','29':'','25':''};
146 this.client_prefix_set = attributes.weight_prefix_set || {'42':''};
147 this.cashier_prefix_set = attributes.weight_prefix_set || {'40':''};
148 this.discount_prefix_set = attributes.weight_prefix_set || {'44':''};
150 save_callbacks: function(){
152 for(name in this.action_callback){
153 callbacks[name] = this.action_callback[name];
155 this.action_callback_stack.push(callbacks);
157 restore_callbacks: function(){
158 if(this.action_callback_stack.length){
159 var callbacks = this.action_callback_stack.pop();
160 this.action_callback = callbacks;
164 // when an ean is scanned and parsed, the callback corresponding
165 // to its type is called with the parsed_ean as a parameter.
166 // (parsed_ean is the result of parse_ean(ean))
168 // callbacks is a Map of 'actions' : callback(parsed_ean)
169 // that sets the callback for each action. if a callback for the
170 // specified action already exists, it is replaced.
172 // possible actions include :
173 // 'product' | 'cashier' | 'client' | 'discount'
175 set_action_callbacks: function(callbacks){
176 for(action in callbacks){
177 this.action_callback[action] = callbacks[action];
181 //remove all action callbacks
182 reset_action_callbacks: function(){
183 for(action in this.action_callback){
184 this.action_callback[action] = undefined;
189 // returns true if the ean is a valid EAN codebar number by checking the control digit.
190 // ean must be a string
191 check_ean: function(ean){
192 var code = ean.split('');
193 for(var i = 0; i < code.length; i++){
194 code[i] = Number(code[i]);
196 var st1 = code.slice();
197 var st2 = st1.slice(0,st1.length-1).reverse();
198 // some EAN13 barcodes have a length of 12, as they start by 0
199 while (st2.length < 12) {
204 $.each(st2, function() {
205 if (countSt3%2 === 1) {
213 $.each(st2, function() {
214 if (countSt4%2 === 0) {
220 var cd = (10 - (st5%10)) % 10;
221 return code[code.length-1] === cd;
224 // attempts to interpret an ean (string encoding an ean)
225 // it will check its validity then return an object containing various
226 // information about the ean.
227 // most importantly :
229 // - type : the type of the ean:
230 // 'price' | 'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error'
232 // - prefix : the prefix that has ben used to determine the type
233 // - id : the part of the ean that identifies something
234 // - value : if the id encodes a numerical value, it will be put there
235 // - unit : if the encoded value has a unit, it will be put there.
236 // not to be confused with the 'unit' type, which represent an unit of a
239 parse_ean: function(ean){
248 var prefix2 = ean.substring(0,2);
250 if(!this.check_ean(ean)){
251 parse_result.type = 'error';
252 }else if (prefix2 in this.price_prefix_set){
253 parse_result.type = 'price';
254 parse_result.prefix = prefix2;
255 parse_result.id = ean.substring(0,7);
256 parse_result.value = Number(ean.substring(7,12))/100.0;
257 parse_result.unit = 'euro';
258 } else if (prefix2 in this.weight_prefix_set){
259 parse_result.type = 'weight';
260 parse_result.prefix = prefix2;
261 parse_result.id = ean.substring(0,7);
262 parse_result.value = Number(ean.substring(7,12))/1000.0;
263 parse_result.unit = 'Kg';
264 }else if (prefix2 in this.client_prefix_set){
265 parse_result.type = 'client';
266 parse_result.prefix = prefix2;
267 parse_result.id = ean.substring(0,7);
268 }else if (prefix2 in this.cashier_prefix_set){
269 parse_result.type = 'cashier';
270 parse_result.prefix = prefix2;
271 parse_result.id = ean.substring(0,7);
272 }else if (prefix2 in this.discount_prefix_set){
273 parse_result.type = 'discount';
274 parse_result.prefix = prefix2;
275 parse_result.id = ean.substring(0,7);
276 parse_result.value = Number(ean.substring(7,12))/100.0;
277 parse_result.unit = '%';
279 parse_result.type = 'unit';
280 parse_result.prefix = '';
281 parse_result.id = ean;
286 // starts catching keyboard events and tries to interpret codebar
287 // calling the callbacks when needed.
290 var codeNumbers = [];
292 var lastTimeStamp = 0;
294 // The barcode readers acts as a keyboard, we catch all keyup events and try to find a
295 // barcode sequence in the typed keys, then act accordingly.
296 $('body').delegate('','keyup', function (e){
298 //We only care about numbers
299 if (!isNaN(Number(String.fromCharCode(e.keyCode)))) {
301 // The barcode reader sends keystrokes with a specific interval.
302 // We look if the typed keys fit in the interval.
303 if (codeNumbers.length==0) {
304 timeStamp = new Date().getTime();
306 if (lastTimeStamp + 30 < new Date().getTime()) {
307 // not a barcode reader
309 timeStamp = new Date().getTime();
312 codeNumbers.push(e.keyCode - 48);
313 lastTimeStamp = new Date().getTime();
314 if (codeNumbers.length == 13) {
315 //We have found what seems to be a valid codebar
316 var parse_result = self.parse_ean(codeNumbers.join(''));
317 console.log('BARCODE:',parse_result);
319 if (parse_result.type === 'error') { //most likely a checksum error, raise warning
320 $(QWeb.render('pos-scan-warning')).dialog({
326 }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product
327 if(self.action_callback['product']){
328 self.action_callback['product'](parse_result);
330 //this.trigger("codebar",parse_result );
332 if(self.action_callback[parse_result.type]){
333 self.action_callback[parse_result.type](parse_result);
346 // stops catching keyboard events
347 disconnect: function(){
348 $('body').undelegate('', 'keyup')