[IMP] point_of_sale: hold receipts until the printer is back online
[odoo/odoo.git] / addons / point_of_sale / static / src / js / devices.js
1
2 function openerp_pos_devices(instance,module){ //module is instance.point_of_sale
3
4     // the JobQueue schedules a sequence of 'jobs'. each job is
5     // a function returning a deferred. the queue waits for each job to finish 
6     // before launching the next. Each job can also be scheduled with a delay. 
7     // the  is used to prevent parallel requests to the proxy.
8
9     module.JobQueue = function(){
10         var queue = [];
11         var running = false;
12         var scheduled_end_time = 0;
13         var end_of_queue = (new $.Deferred()).resolve();
14         var stoprepeat = false;
15
16         var run = function(){
17             if(end_of_queue.state() === 'resolved'){
18                 end_of_queue =  new $.Deferred();
19             }
20             if(queue.length > 0){
21                 running = true;
22                 var job = queue[0];
23                 if(!job.opts.repeat || stoprepeat){
24                     queue.shift();
25                     stoprepeat = false;
26                 }
27
28                 // the time scheduled for this job
29                 scheduled_end_time = (new Date()).getTime() + (job.opts.duration || 0);
30
31                 // we run the job and put in def when it finishes
32                 var def = job.fun() || (new $.Deferred()).resolve();
33                 
34                 // we don't care if a job fails ... 
35                 def.always(function(){
36                     // we run the next job after the scheduled_end_time, even if it finishes before
37                     setTimeout(function(){
38                         run();
39                     }, Math.max(0, scheduled_end_time - (new Date()).getTime()) ); 
40                 });
41             }else{
42                 running = false;
43                 scheduled_end_time = 0;
44                 end_of_queue.resolve();
45             }
46         };
47         
48         // adds a job to the schedule.
49         // opts : {
50         //    duration    : the job is guaranteed to finish no quicker than this (milisec)
51         //    repeat      : if true, the job will be endlessly repeated
52         //    important   : if true, the scheduled job cannot be canceled by a queue.clear()
53         // }
54         this.schedule  = function(fun, opts){
55             queue.push({fun:fun, opts:opts || {}});
56             if(!running){
57                 run();
58             }
59         }
60
61         // remove all jobs from the schedule (except the ones marked as important)
62         this.clear = function(){
63             queue = _.filter(queue,function(job){job.opts.important === true}); 
64         };
65
66         // end the repetition of the current job
67         this.stoprepeat = function(){
68             stoprepeat = true;
69         };
70         
71         // returns a deferred that resolves when all scheduled 
72         // jobs have been run.
73         // ( jobs added after the call to this method are considered as well )
74         this.finished = function(){
75             return end_of_queue;
76         }
77
78     };
79
80     // this object interfaces with the local proxy to communicate to the various hardware devices
81     // connected to the Point of Sale. As the communication only goes from the POS to the proxy,
82     // methods are used both to signal an event, and to fetch information. 
83
84     module.ProxyDevice  = instance.web.Class.extend(openerp.PropertiesMixin,{
85         init: function(parent,options){
86             openerp.PropertiesMixin.init.call(this,parent);
87             var self = this;
88             options = options || {};
89             url = options.url || 'http://localhost:8069';
90             
91             this.weighting = false;
92             this.debug_weight = 0;
93             this.use_debug_weight = false;
94
95             this.paying = false;
96             this.default_payment_status = {
97                 status: 'waiting',
98                 message: '',
99                 payment_method: undefined,
100                 receipt_client: undefined,
101                 receipt_shop:   undefined,
102             };    
103             this.custom_payment_status = this.default_payment_status;
104
105             this.receipt_queue = [];
106
107             this.notifications = {};
108             this.bypass_proxy = false;
109
110             this.connection = null; 
111             this.host       = '';
112             this.keptalive  = false;
113
114             this.set('status',{});
115
116             this.set_connection_status('disconnected');
117
118             this.on('change:status',this,function(eh,status){
119                 status = status.newValue;
120                 if(status.status === 'connected'){
121                     self.print_receipt();
122                 }
123             });
124
125             window.hw_proxy = this;
126         },
127         set_connection_status: function(status,drivers){
128             oldstatus = this.get('status');
129             newstatus = {};
130             newstatus.status = status;
131             newstatus.drivers = status === 'disconnected' ? {} : oldstatus.drivers;
132             newstatus.drivers = drivers ? drivers : newstatus.drivers;
133             this.set('status',newstatus);
134         },
135         disconnect: function(){
136             if(this.get('status').status !== 'disconnected'){
137                 this.connection.destroy();
138                 this.set_connection_status('disconnected');
139             }
140         },
141
142         // connects to the specified url
143         connect: function(url){
144             var self = this;
145             this.connection = new instance.web.Session(undefined,url);
146             this.host   = url;
147             this.set_connection_status('connecting',{});
148
149             return this.message('handshake').then(function(response){
150                     if(response){
151                         self.set_connection_status('connected');
152                         localStorage['hw_proxy_url'] = url;
153                         self.keepalive();
154                     }else{
155                         self.set_connection_status('disconnected');
156                         console.error('Connection refused by the Proxy');
157                     }
158                 },function(){
159                     self.set_connection_status('disconnected');
160                     console.error('Could not connect to the Proxy');
161                 });
162         },
163
164         // find a proxy and connects to it. for options see find_proxy
165         autoconnect: function(options){
166             var self = this;
167             this.set_connection_status('connecting',{});
168             var success = new $.Deferred();
169             this.find_proxy(options)
170                 .then(function(proxies){
171                     if(proxies.length > 0){
172                         self.connect(proxies[0])
173                             .then(function(){
174                                 success.resolve();
175                             },function(){
176                                 self.set_connection_status('disconnected');
177                                 success.reject();
178                             });
179                     }else{
180                         self.set_connection_status('disconnected');
181                         success.reject();
182                     }
183                 });
184             return success;
185         },
186
187         // starts a loop that updates the connection status
188         keepalive: function(){
189             var self = this;
190             if(!this.keptalive){
191                 this.keptalive = true;
192                 function status(){
193                     self.connection.rpc('/hw_proxy/status_json',{},{timeout:500})       
194                         .then(function(driver_status){
195                             self.set_connection_status('connected',driver_status);
196                         },function(){
197                             if(self.get('status').status !== 'connecting'){
198                                 self.set_connection_status('disconnected');
199                             }
200                         }).always(function(){
201                             setTimeout(status,5000);
202                         });
203                 }
204                 status();
205             };
206         },
207
208         message : function(name,params){
209             var callbacks = this.notifications[name] || [];
210             for(var i = 0; i < callbacks.length; i++){
211                 callbacks[i](params);
212             }
213             if(this.get('status').status !== 'disconnected'){
214                 return this.connection.rpc('/hw_proxy/' + name, params || {});       
215             }else{
216                 return (new $.Deferred()).reject();
217             }
218         },
219
220         // returns as a deferred a list of valid hosts urls that can be used as proxy.
221         // options:
222         //   - port: what port to listen to (default 8069)
223         //   - force_ip : limit the search to the specified ip
224         //   - progress(fac) : callback for search progress ( fac in [0,1] ) 
225         find_proxy: function(options){
226             options = options || {};
227             var self  = this;
228             var port  = ':' + (options.port || '8069');
229             var urls  = [];
230             var found = false;
231             var proxies = [];
232             var done  = new $.Deferred();
233             var parallel = 8;
234             var threads  = [];
235             var progress = 0;
236
237             this.set_connection_status('connecting');
238
239             if(options.force_ip){
240                 urls.push(options.force_ip);
241             }else{
242                 if(localStorage['hw_proxy_url']){
243                     urls.push(localStorage['hw_proxy_url']);
244                 }
245
246                 urls.push('http://localhost'+port);
247
248                 for(var i = 0; i < 256; i++){
249                     urls.push('http://192.168.0.'+i+port);
250                     urls.push('http://192.168.1.'+i+port);
251                     urls.push('http://192.168.2.'+i+port);
252                     urls.push('http://10.0.0.'+i+port);
253                 }
254             }
255
256             var prog_inc = 1/urls.length; 
257
258             function update_progress(){
259                 progress = found ? 1 : progress + prog_inc;
260                 if(options.progress){
261                     options.progress(progress);
262                 }
263             }
264
265             function thread(url,done){
266                 if(!url){ 
267                     done.resolve();
268                 }
269                 var c = $.ajax({
270                         url: url + '/hw_proxy/hello',
271                         method: 'GET',
272                         timeout: 300, 
273                     }).done(function(){
274                         found = true;
275                         update_progress();
276                         proxies.push(url);
277                         done.resolve(url);
278                     })
279                     .fail(function(){
280                         update_progress();
281                         var next_url = urls.shift();
282                         if(found ||! self.searching_for_proxy || !next_url){
283                             done.resolve();
284                         }else{
285                             thread(next_url,done);
286                         }
287                     });
288                 return done;
289             }
290
291             this.searching_for_proxy = true;
292
293             for(var i = 0; i < Math.min(parallel,urls.length); i++){
294                 threads.push(thread(urls.shift(),new $.Deferred()));
295             }
296             
297             var done = new $.Deferred();
298             
299             $.when.apply($,threads).then(function(){
300                 var urls = [];
301                 for(var i = 0; i < arguments.length; i++){
302                     if(arguments[i]){
303                         urls.push(arguments[i]);
304                     }
305                 }
306                 done.resolve(urls);
307             });
308
309             return done;
310         },
311
312         stop_searching: function(){
313             this.searching_for_proxy = false;
314             this.set_connection_status('disconnected');
315         },
316
317         // this allows the client to be notified when a proxy call is made. The notification 
318         // callback will be executed with the same arguments as the proxy call
319         add_notification: function(name, callback){
320             if(!this.notifications[name]){
321                 this.notifications[name] = [];
322             }
323             this.notifications[name].push(callback);
324         },
325         
326         //a product has been scanned and recognized with success
327         // ean is a parsed ean object
328         scan_item_success: function(ean){
329             return this.message('scan_item_success',{ean: ean});
330         },
331
332         // a product has been scanned but not recognized
333         // ean is a parsed ean object
334         scan_item_error_unrecognized: function(ean){
335             return this.message('scan_item_error_unrecognized',{ean: ean});
336         },
337
338         //the client is asking for help
339         help_needed: function(){
340             return this.message('help_needed');
341         },
342
343         //the client does not need help anymore
344         help_canceled: function(){
345             return this.message('help_canceled');
346         },
347
348         //the client is starting to weight
349         weighting_start: function(){
350             var ret = new $.Deferred();
351             if(!this.weighting){
352                 this.weighting = true;
353                 this.message('weighting_start').always(function(){
354                     ret.resolve();
355                 });
356             }else{
357                 console.error('Weighting already started!!!');
358                 ret.resolve();
359             }
360             return ret;
361         },
362
363         // the client has finished weighting products
364         weighting_end: function(){
365             var ret = new $.Deferred();
366             if(this.weighting){
367                 this.weighting = false;
368                 this.message('weighting_end').always(function(){
369                     ret.resolve();
370                 });
371             }else{
372                 console.error('Weighting already ended !!!');
373                 ret.resolve();
374             }
375             return ret;
376         },
377
378         //returns the weight on the scale. 
379         // is called at regular interval (up to 10x/sec) between a weighting_start()
380         // and a weighting_end()
381         weighting_read_kg: function(){
382             var self = this;
383             var ret = new $.Deferred();
384             this.message('weighting_read_kg',{})
385                 .then(function(weight){
386                     ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
387                 }, function(){ //failed to read weight
388                     ret.resolve(self.use_debug_weight ? self.debug_weight : 0.0);
389                 });
390             return ret;
391         },
392
393         // sets a custom weight, ignoring the proxy returned value. 
394         debug_set_weight: function(kg){
395             this.use_debug_weight = true;
396             this.debug_weight = kg;
397         },
398
399         // resets the custom weight and re-enable listening to the proxy for weight values
400         debug_reset_weight: function(){
401             this.use_debug_weight = false;
402             this.debug_weight = 0;
403         },
404
405
406         // the pos asks the client to pay 'price' units
407         payment_request: function(price){
408             var ret = new $.Deferred();
409             this.paying = true;
410             this.custom_payment_status = this.default_payment_status;
411             return this.message('payment_request',{'price':price});
412         },
413
414         payment_status: function(){
415             if(this.bypass_proxy){
416                 this.bypass_proxy = false;
417                 return (new $.Deferred()).resolve(this.custom_payment_status);
418             }else{
419                 return this.message('payment_status');
420             }
421         },
422         
423         // override what the proxy says and accept the payment
424         debug_accept_payment: function(){
425             this.bypass_proxy = true;
426             this.custom_payment_status = {
427                 status: 'paid',
428                 message: 'Successfull Payment, have a nice day',
429                 payment_method: 'AMEX',
430                 receipt_client: '<xml>bla</xml>',
431                 receipt_shop:   '<xml>bla</xml>',
432             };    
433         },
434
435         // override what the proxy says and reject the payment
436         debug_reject_payment: function(){
437             this.bypass_proxy = true;
438             this.custom_payment_status = {
439                 status: 'error-rejected',
440                 message: 'Sorry you don\'t have enough money :(',
441             };    
442         },
443         // the client cancels his payment
444         payment_cancel: function(){
445             this.paying = false;
446             this.custom_payment_status = 'waiting_for_payment';
447             return this.message('payment_cancel');
448         },
449
450         // called when the client logs in or starts to scan product
451         transaction_start: function(){
452             return this.message('transaction_start');
453         },
454
455         // called when the clients has finished his interaction with the machine
456         transaction_end: function(){
457             return this.message('transaction_end');
458         },
459
460         // called when the POS turns to cashier mode
461         cashier_mode_activated: function(){
462             return this.message('cashier_mode_activated');
463         },
464
465         // called when the POS turns to client mode
466         cashier_mode_deactivated: function(){
467             return this.message('cashier_mode_deactivated');
468         },
469         
470         // ask for the cashbox (the physical box where you store the cash) to be opened
471         open_cashbox: function(){
472             return this.message('open_cashbox');
473         },
474
475         /* ask the printer to print a receipt
476          * receipt is a JSON object with the following specs:
477          * receipt{
478          *  - orderlines : list of orderlines :
479          *     {
480          *          quantity:           (number) the number of items, or the weight, 
481          *          unit_name:          (string) the name of the item's unit (kg, dozen, ...)
482          *          price:              (number) the price of one unit of the item before discount
483          *          discount:           (number) the discount on the product in % [0,100] 
484          *          product_name:       (string) the name of the product
485          *          price_with_tax:     (number) the price paid for this orderline, tax included
486          *          price_without_tax:  (number) the price paid for this orderline, without taxes
487          *          tax:                (number) the price paid in taxes on this orderline
488          *          product_description:         (string) generic description of the product
489          *          product_description_sale:    (string) sales related information of the product
490          *     }
491          *  - paymentlines : list of paymentlines :
492          *     {
493          *          amount:             (number) the amount paid
494          *          journal:            (string) the name of the journal on wich the payment has been made  
495          *     }
496          *  - total_with_tax:     (number) the total of the receipt tax included
497          *  - total_without_tax:  (number) the total of the receipt without taxes
498          *  - total_tax:          (number) the total amount of taxes paid
499          *  - total_paid:         (number) the total sum paid by the client
500          *  - change:             (number) the amount of change given back to the client
501          *  - name:               (string) a unique name for this order
502          *  - client:             (string) name of the client. or null if no client is logged
503          *  - cashier:            (string) the name of the cashier
504          *  - date: {             the date at wich the payment has been done
505          *      year:             (number) the year  [2012, ...]
506          *      month:            (number) the month [0,11]
507          *      date:             (number) the day of the month [1,31]
508          *      day:              (number) the day of the week  [0,6] 
509          *      hour:             (number) the hour [0,23]
510          *      minute:           (number) the minute [0,59]
511          *    }
512          */
513         print_receipt: function(receipt){
514             var self = this;
515             if(receipt){
516                 this.receipt_queue.push(receipt);
517             }
518             var aborted = false;
519             function send_printing_job(){
520                 if (self.receipt_queue.length > 0){
521                     var r = self.receipt_queue.shift();
522                     self.message('print_receipt',{ receipt: r },{ timeout: 5000 })
523                         .then(function(){
524                             send_printing_job();
525                         },function(){
526                             self.receipt_queue.unshift(r)
527                         });
528                 }
529             }
530             send_printing_job();
531         },
532
533         // asks the proxy to log some information, as with the debug.log you can provide several arguments.
534         log: function(){
535             return this.message('log',{'arguments': _.toArray(arguments)});
536         },
537
538         // asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server ) 
539         print_pdf_invoice: function(pdfinvoice){
540             return this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice});
541         },
542     });
543
544     // this module interfaces with the barcode reader. It assumes the barcode reader
545     // is set-up to act like  a keyboard. Use connect() and disconnect() to activate 
546     // and deactivate the barcode reader. Use set_action_callbacks to tell it
547     // what to do when it reads a barcode.
548     module.BarcodeReader = instance.web.Class.extend({
549         actions:[
550             'product',
551             'cashier',
552             'client',
553             'discount',
554         ],
555         init: function(attributes){
556             this.pos = attributes.pos;
557             this.action_callback = {};
558             this.proxy = attributes.proxy;
559             this.remote_scanning = false;
560             this.remote_active = 0;
561
562             this.action_callback_stack = [];
563
564             this.weight_prefix_set   = attributes.weight_prefix_set   ||  {'21':''};
565             this.discount_prefix_set = attributes.discount_prefix_set ||  {'22':''};
566             this.price_prefix_set    = attributes.price_prefix_set    ||  {'23':''};
567             this.cashier_prefix_set  = attributes.cashier_prefix_set  ||  {'041':''};
568             this.client_prefix_set   = attributes.client_prefix_set   ||  {'042':''};
569
570         },
571
572         save_callbacks: function(){
573             var callbacks = {};
574             for(name in this.action_callback){
575                 callbacks[name] = this.action_callback[name];
576             }
577             this.action_callback_stack.push(callbacks);
578         },
579
580         restore_callbacks: function(){
581             if(this.action_callback_stack.length){
582                 var callbacks = this.action_callback_stack.pop();
583                 this.action_callback = callbacks;
584             }
585         },
586        
587         // when an ean is scanned and parsed, the callback corresponding
588         // to its type is called with the parsed_ean as a parameter. 
589         // (parsed_ean is the result of parse_ean(ean)) 
590         // 
591         // callbacks is a Map of 'actions' : callback(parsed_ean)
592         // that sets the callback for each action. if a callback for the
593         // specified action already exists, it is replaced. 
594         // 
595         // possible actions include : 
596         // 'product' | 'cashier' | 'client' | 'discount' 
597     
598         set_action_callback: function(action, callback){
599             if(arguments.length == 2){
600                 this.action_callback[action] = callback;
601             }else{
602                 var actions = arguments[0];
603                 for(action in actions){
604                     this.set_action_callback(action,actions[action]);
605                 }
606             }
607         },
608
609         //remove all action callbacks 
610         reset_action_callbacks: function(){
611             for(action in this.action_callback){
612                 this.action_callback[action] = undefined;
613             }
614         },
615         // returns the checksum of the ean, or -1 if the ean has not the correct length, ean must be a string
616         ean_checksum: function(ean){
617             var code = ean.split('');
618             if(code.length !== 13){
619                 return -1;
620             }
621             var oddsum = 0, evensum = 0, total = 0;
622             code = code.reverse().splice(1);
623             for(var i = 0; i < code.length; i++){
624                 if(i % 2 == 0){
625                     oddsum += Number(code[i]);
626                 }else{
627                     evensum += Number(code[i]);
628                 }
629             }
630             total = oddsum * 3 + evensum;
631             return Number((10 - total % 10) % 10);
632         },
633         // returns true if the ean is a valid EAN codebar number by checking the control digit.
634         // ean must be a string
635         check_ean: function(ean){
636             return this.ean_checksum(ean) === Number(ean[ean.length-1]);
637         },
638         // returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
639         sanitize_ean:function(ean){
640             ean = ean.substr(0,13);
641
642             for(var n = 0, count = (13 - ean.length); n < count; n++){
643                 ean = ean + '0';
644             }
645             return ean.substr(0,12) + this.ean_checksum(ean);
646         },
647         
648         // attempts to interpret an ean (string encoding an ean)
649         // it will check its validity then return an object containing various
650         // information about the ean.
651         // most importantly : 
652         // - code    : the ean
653         // - type   : the type of the ean: 
654         //      'price' |  'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error'
655         //
656         // - prefix : the prefix that has ben used to determine the type
657         // - id     : the part of the ean that identifies something
658         // - value  : if the id encodes a numerical value, it will be put there
659         // - unit   : if the encoded value has a unit, it will be put there. 
660         //            not to be confused with the 'unit' type, which represent an unit of a 
661         //            unique product
662         // - base_code : the ean code with all the encoding parts set to zero; the one put on
663         //               the product in the backend
664
665         parse_ean: function(ean){
666             var parse_result = {
667                 encoding: 'ean13',
668                 type:'unknown',  
669                 prefix:'',
670                 code:ean,
671                 base_code: ean,
672                 id:'',
673                 value: 0,
674                 unit: 'none',
675             };
676
677             function match_prefix(prefix_set, type){
678                 for(prefix in prefix_set){
679                     if(ean.substring(0,prefix.length) === prefix){
680                         parse_result.prefix = prefix;
681                         parse_result.type = type;
682                         return true;
683                     }
684                 }
685                 return false;
686             }
687
688             if (!this.check_ean(ean)){
689                 parse_result.type = 'error';
690             } else if( match_prefix(this.price_prefix_set,'price')){
691                 parse_result.id = ean.substring(0,7);
692                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
693                 parse_result.value = Number(ean.substring(7,12))/100.0;
694                 parse_result.unit  = 'euro';
695             } else if( match_prefix(this.weight_prefix_set,'weight')){
696                 parse_result.id = ean.substring(0,7);
697                 parse_result.value = Number(ean.substring(7,12))/1000.0;
698                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
699                 parse_result.unit = 'Kg';
700             } else if( match_prefix(this.client_prefix_set,'client')){
701                 parse_result.id = ean.substring(0,7);
702                 parse_result.unit = 'Kg';
703             } else if( match_prefix(this.cashier_prefix_set,'cashier')){
704                 parse_result.id = ean.substring(0,7);
705             } else if( match_prefix(this.discount_prefix_set,'discount')){
706                 parse_result.id    = ean.substring(0,7);
707                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
708                 parse_result.value = Number(ean.substring(7,12))/100.0;
709                 parse_result.unit  = '%';
710             } else {
711                 parse_result.type = 'unit';
712                 parse_result.prefix = '';
713                 parse_result.id = ean;
714             }
715             return parse_result;
716         },
717         
718         scan: function(code){
719             if(code.length < 3){
720                 return;
721             }else if(code.length === 13 && /^\d+$/.test(code)){
722                 var parse_result = this.parse_ean(code);
723             }else if(this.pos.db.get_product_by_reference(code)){
724                 var parse_result = {
725                     encoding: 'reference',
726                     type: 'unit',
727                     code: code,
728                     prefix: '',
729                 };
730             }else{
731                 return;
732             }
733
734             if (parse_result.type === 'error') {    //most likely a checksum error, raise warning
735                 console.warn('WARNING: barcode checksum error:',parse_result);
736             }else if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){    //ean is associated to a product
737                 if(this.action_callback['product']){
738                     this.action_callback['product'](parse_result);
739                 }
740             }else{
741                 if(this.action_callback[parse_result.type]){
742                     this.action_callback[parse_result.type](parse_result);
743                 }
744             }
745         },
746
747         // starts catching keyboard events and tries to interpret codebar 
748         // calling the callbacks when needed.
749         connect: function(){
750
751             var self = this;
752             var code = "";
753             var timeStamp  = 0;
754             var onlynumbers = true;
755             var timeout = null;
756
757             this.handler = function(e){
758
759                 if(e.which === 13){ //ignore returns
760                     e.preventDefault();
761                     return;
762                 }
763
764                 if(timeStamp + 50 < new Date().getTime()){
765                     code = "";
766                     onlynumbers = true;
767                 }
768
769                 timeStamp = new Date().getTime();
770                 clearTimeout(timeout);
771
772                 if( e.which < 48 || e.which >= 58 ){ // not a number
773                     onlynumbers = false;
774                 }
775
776                 code += String.fromCharCode(e.which);
777
778                 // we wait for a while after the last input to be sure that we are not mistakingly
779                 // returning a code which is a prefix of a bigger one :
780                 // Internal Ref 5449 vs EAN13 5449000...
781
782                 timeout = setTimeout(function(){
783                     self.scan(code);
784                     code = "";
785                     onlynumbers = true;
786                 },100);
787             };
788
789             $('body').on('keypress', this.handler);
790         },
791
792         // stops catching keyboard events 
793         disconnect: function(){
794             $('body').off('keypress', this.handler)
795         },
796
797         // the barcode scanner will listen on the hw_proxy/scanner interface for 
798         // scan events until disconnect_from_proxy is called
799         connect_to_proxy: function(){ 
800             var self = this;
801             this.remote_scanning = true;
802             if(this.remote_active >= 1){
803                 return;
804             }
805             this.remote_active = 1;
806
807             function waitforbarcode(){
808                 return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500})
809                     .then(function(barcode){
810                         if(!self.remote_scanning){ 
811                             self.remote_active = 0;
812                             return; 
813                         }
814                         self.scan(barcode);
815                         waitforbarcode();
816                     },
817                     function(){
818                         if(!self.remote_scanning){
819                             self.remote_active = 0;
820                             return;
821                         }
822                         setTimeout(waitforbarcode,5000);
823                     });
824             }
825             waitforbarcode();
826         },
827
828         // the barcode scanner will stop listening on the hw_proxy/scanner remote interface
829         disconnect_from_proxy: function(){
830             this.remote_scanning = false;
831         },
832     });
833
834 }