[IMP] hw_escpos / point_of_sale: print receipt with an xml template
[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, { use_cors: true});
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         //   - force_ip : only try to connect to the specified ip. 
166         //   - port: what port to listen to (default 8069)
167         //   - progress(fac) : callback for search progress ( fac in [0,1] ) 
168         autoconnect: function(options){
169             var self = this;
170             this.set_connection_status('connecting',{});
171             var found_url = new $.Deferred();
172             var success = new $.Deferred();
173
174             if ( options.force_ip ){
175                 // if the ip is forced by server config, bailout on fail
176                 found_url = this.try_hard_to_connect(options.force_ip, options)
177             }else if( localStorage['hw_proxy_url'] ){
178                 // try harder when we remember a good proxy url
179                 found_url = this.try_hard_to_connect(localStorage['hw_proxy_url'], options)
180                     .then(null,function(){
181                         return self.find_proxy(options);
182                     });
183             }else{
184                 // just find something quick
185                 found_url = this.find_proxy(options);
186             }
187
188             success = found_url.then(function(url){
189                     return self.connect(url);
190                 });
191
192             success.fail(function(){
193                 self.set_connection_status('disconnected');
194             });
195
196             return success;
197         },
198
199         // starts a loop that updates the connection status
200         keepalive: function(){
201             var self = this;
202             if(!this.keptalive){
203                 this.keptalive = true;
204                 function status(){
205                     self.connection.rpc('/hw_proxy/status_json',{},{timeout:2500})       
206                         .then(function(driver_status){
207                             self.set_connection_status('connected',driver_status);
208                         },function(){
209                             if(self.get('status').status !== 'connecting'){
210                                 self.set_connection_status('disconnected');
211                             }
212                         }).always(function(){
213                             setTimeout(status,5000);
214                         });
215                 }
216                 status();
217             };
218         },
219
220         message : function(name,params){
221             var callbacks = this.notifications[name] || [];
222             for(var i = 0; i < callbacks.length; i++){
223                 callbacks[i](params);
224             }
225             if(this.get('status').status !== 'disconnected'){
226                 return this.connection.rpc('/hw_proxy/' + name, params || {});       
227             }else{
228                 return (new $.Deferred()).reject();
229             }
230         },
231
232         // try several time to connect to a known proxy url
233         try_hard_to_connect: function(url,options){
234             options   = options || {};
235             var port  = ':' + (options.port || '8069');
236
237             this.set_connection_status('connecting');
238
239             if(url.indexOf('//') < 0){
240                 url = 'http://'+url;
241             }
242
243             if(url.indexOf(':',5) < 0){
244                 url = url+port;
245             }
246
247             // try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
248             function try_real_hard_to_connect(url, retries, done){
249
250                 done = done || new $.Deferred();
251
252                 var c = $.ajax({
253                     url: url + '/hw_proxy/hello',
254                     method: 'GET',
255                     timeout: 1000,
256                 })
257                 .done(function(){
258                     done.resolve(url);
259                 })
260                 .fail(function(){
261                     if(retries > 0){
262                         try_real_hard_to_connect(url,retries-1,done);
263                     }else{
264                         done.reject();
265                     }
266                 });
267                 return done;
268             }
269
270             return try_real_hard_to_connect(url,3);
271         },
272
273         // returns as a deferred a valid host url that can be used as proxy.
274         // options:
275         //   - port: what port to listen to (default 8069)
276         //   - progress(fac) : callback for search progress ( fac in [0,1] ) 
277         find_proxy: function(options){
278             options = options || {};
279             var self  = this;
280             var port  = ':' + (options.port || '8069');
281             var urls  = [];
282             var found = false;
283             var parallel = 8;
284             var done = new $.Deferred(); // will be resolved with the proxies valid urls
285             var threads  = [];
286             var progress = 0;
287
288
289             urls.push('http://localhost'+port);
290             for(var i = 0; i < 256; i++){
291                 urls.push('http://192.168.0.'+i+port);
292                 urls.push('http://192.168.1.'+i+port);
293                 urls.push('http://10.0.0.'+i+port);
294             }
295
296             var prog_inc = 1/urls.length; 
297
298             function update_progress(){
299                 progress = found ? 1 : progress + prog_inc;
300                 if(options.progress){
301                     options.progress(progress);
302                 }
303             }
304
305             function thread(done){
306                 var url = urls.shift();
307
308                 done = done || new $.Deferred();
309
310                 if( !url || found || !self.searching_for_proxy ){ 
311                     done.resolve();
312                     return done;
313                 }
314
315                 var c = $.ajax({
316                         url: url + '/hw_proxy/hello',
317                         method: 'GET',
318                         timeout: 400, 
319                     }).done(function(){
320                         found = true;
321                         update_progress();
322                         done.resolve(url);
323                     })
324                     .fail(function(){
325                         update_progress();
326                         thread(done);
327                     });
328
329                 return done;
330             }
331
332             this.searching_for_proxy = true;
333
334             for(var i = 0, len = Math.min(parallel,urls.length); i < len; i++){
335                 threads.push(thread());
336             }
337             
338             $.when.apply($,threads).then(function(){
339                 var urls = [];
340                 for(var i = 0; i < arguments.length; i++){
341                     if(arguments[i]){
342                         urls.push(arguments[i]);
343                     }
344                 }
345                 done.resolve(urls[0]);
346             });
347
348             return done;
349         },
350
351         stop_searching: function(){
352             this.searching_for_proxy = false;
353             this.set_connection_status('disconnected');
354         },
355
356         // this allows the client to be notified when a proxy call is made. The notification 
357         // callback will be executed with the same arguments as the proxy call
358         add_notification: function(name, callback){
359             if(!this.notifications[name]){
360                 this.notifications[name] = [];
361             }
362             this.notifications[name].push(callback);
363         },
364         
365         //a product has been scanned and recognized with success
366         // ean is a parsed ean object
367         scan_item_success: function(ean){
368             return this.message('scan_item_success',{ean: ean});
369         },
370
371         // a product has been scanned but not recognized
372         // ean is a parsed ean object
373         scan_item_error_unrecognized: function(ean){
374             return this.message('scan_item_error_unrecognized',{ean: ean});
375         },
376
377         //the client is asking for help
378         help_needed: function(){
379             return this.message('help_needed');
380         },
381
382         //the client does not need help anymore
383         help_canceled: function(){
384             return this.message('help_canceled');
385         },
386
387         //the client is starting to weight
388         weighting_start: function(){
389             var ret = new $.Deferred();
390             if(!this.weighting){
391                 this.weighting = true;
392                 this.message('weighting_start').always(function(){
393                     ret.resolve();
394                 });
395             }else{
396                 console.error('Weighting already started!!!');
397                 ret.resolve();
398             }
399             return ret;
400         },
401
402         // the client has finished weighting products
403         weighting_end: function(){
404             var ret = new $.Deferred();
405             if(this.weighting){
406                 this.weighting = false;
407                 this.message('weighting_end').always(function(){
408                     ret.resolve();
409                 });
410             }else{
411                 console.error('Weighting already ended !!!');
412                 ret.resolve();
413             }
414             return ret;
415         },
416
417         //returns the weight on the scale. 
418         // is called at regular interval (up to 10x/sec) between a weighting_start()
419         // and a weighting_end()
420         weighting_read_kg: function(){
421             var self = this;
422             var ret = new $.Deferred();
423             this.message('weighting_read_kg',{})
424                 .then(function(weight){
425                     ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
426                 }, function(){ //failed to read weight
427                     ret.resolve(self.use_debug_weight ? self.debug_weight : 0.0);
428                 });
429             return ret;
430         },
431
432         // sets a custom weight, ignoring the proxy returned value. 
433         debug_set_weight: function(kg){
434             this.use_debug_weight = true;
435             this.debug_weight = kg;
436         },
437
438         // resets the custom weight and re-enable listening to the proxy for weight values
439         debug_reset_weight: function(){
440             this.use_debug_weight = false;
441             this.debug_weight = 0;
442         },
443
444
445         // the pos asks the client to pay 'price' units
446         payment_request: function(price){
447             var ret = new $.Deferred();
448             this.paying = true;
449             this.custom_payment_status = this.default_payment_status;
450             return this.message('payment_request',{'price':price});
451         },
452
453         payment_status: function(){
454             if(this.bypass_proxy){
455                 this.bypass_proxy = false;
456                 return (new $.Deferred()).resolve(this.custom_payment_status);
457             }else{
458                 return this.message('payment_status');
459             }
460         },
461         
462         // override what the proxy says and accept the payment
463         debug_accept_payment: function(){
464             this.bypass_proxy = true;
465             this.custom_payment_status = {
466                 status: 'paid',
467                 message: 'Successfull Payment, have a nice day',
468                 payment_method: 'AMEX',
469                 receipt_client: '<xml>bla</xml>',
470                 receipt_shop:   '<xml>bla</xml>',
471             };    
472         },
473
474         // override what the proxy says and reject the payment
475         debug_reject_payment: function(){
476             this.bypass_proxy = true;
477             this.custom_payment_status = {
478                 status: 'error-rejected',
479                 message: 'Sorry you don\'t have enough money :(',
480             };    
481         },
482         // the client cancels his payment
483         payment_cancel: function(){
484             this.paying = false;
485             this.custom_payment_status = 'waiting_for_payment';
486             return this.message('payment_cancel');
487         },
488
489         // called when the client logs in or starts to scan product
490         transaction_start: function(){
491             return this.message('transaction_start');
492         },
493
494         // called when the clients has finished his interaction with the machine
495         transaction_end: function(){
496             return this.message('transaction_end');
497         },
498
499         // called when the POS turns to cashier mode
500         cashier_mode_activated: function(){
501             return this.message('cashier_mode_activated');
502         },
503
504         // called when the POS turns to client mode
505         cashier_mode_deactivated: function(){
506             return this.message('cashier_mode_deactivated');
507         },
508         
509         // ask for the cashbox (the physical box where you store the cash) to be opened
510         open_cashbox: function(){
511             return this.message('open_cashbox');
512         },
513
514         /* 
515          * ask the printer to print a receipt
516          */
517         print_receipt: function(receipt){
518             var self = this;
519             if(receipt){
520                 this.receipt_queue.push(receipt);
521             }
522             var aborted = false;
523             function send_printing_job(){
524                 if (self.receipt_queue.length > 0){
525                     var r = self.receipt_queue.shift();
526                     self.message('print_xml_receipt',{ receipt: r },{ timeout: 5000 })
527                         .then(function(){
528                             send_printing_job();
529                         },function(){
530                             self.receipt_queue.unshift(r)
531                         });
532                 }
533             }
534             send_printing_job();
535         },
536
537         // asks the proxy to log some information, as with the debug.log you can provide several arguments.
538         log: function(){
539             return this.message('log',{'arguments': _.toArray(arguments)});
540         },
541
542         // asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server ) 
543         print_pdf_invoice: function(pdfinvoice){
544             return this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice});
545         },
546     });
547
548     // this module interfaces with the barcode reader. It assumes the barcode reader
549     // is set-up to act like  a keyboard. Use connect() and disconnect() to activate 
550     // and deactivate the barcode reader. Use set_action_callbacks to tell it
551     // what to do when it reads a barcode.
552     module.BarcodeReader = instance.web.Class.extend({
553         actions:[
554             'product',
555             'cashier',
556             'client',
557             'discount',
558         ],
559         init: function(attributes){
560             this.pos = attributes.pos;
561             this.action_callback = {};
562             this.proxy = attributes.proxy;
563             this.remote_scanning = false;
564             this.remote_active = 0;
565
566             this.action_callback_stack = [];
567
568             this.weight_prefix_set   = attributes.weight_prefix_set   ||  {'21':''};
569             this.discount_prefix_set = attributes.discount_prefix_set ||  {'22':''};
570             this.price_prefix_set    = attributes.price_prefix_set    ||  {'23':''};
571             this.cashier_prefix_set  = attributes.cashier_prefix_set  ||  {'041':''};
572             this.client_prefix_set   = attributes.client_prefix_set   ||  {'042':''};
573
574         },
575
576         save_callbacks: function(){
577             var callbacks = {};
578             for(name in this.action_callback){
579                 callbacks[name] = this.action_callback[name];
580             }
581             this.action_callback_stack.push(callbacks);
582         },
583
584         restore_callbacks: function(){
585             if(this.action_callback_stack.length){
586                 var callbacks = this.action_callback_stack.pop();
587                 this.action_callback = callbacks;
588             }
589         },
590        
591         // when an ean is scanned and parsed, the callback corresponding
592         // to its type is called with the parsed_ean as a parameter. 
593         // (parsed_ean is the result of parse_ean(ean)) 
594         // 
595         // callbacks is a Map of 'actions' : callback(parsed_ean)
596         // that sets the callback for each action. if a callback for the
597         // specified action already exists, it is replaced. 
598         // 
599         // possible actions include : 
600         // 'product' | 'cashier' | 'client' | 'discount' 
601     
602         set_action_callback: function(action, callback){
603             if(arguments.length == 2){
604                 this.action_callback[action] = callback;
605             }else{
606                 var actions = arguments[0];
607                 for(action in actions){
608                     this.set_action_callback(action,actions[action]);
609                 }
610             }
611         },
612
613         //remove all action callbacks 
614         reset_action_callbacks: function(){
615             for(action in this.action_callback){
616                 this.action_callback[action] = undefined;
617             }
618         },
619         // returns the checksum of the ean, or -1 if the ean has not the correct length, ean must be a string
620         ean_checksum: function(ean){
621             var code = ean.split('');
622             if(code.length !== 13){
623                 return -1;
624             }
625             var oddsum = 0, evensum = 0, total = 0;
626             code = code.reverse().splice(1);
627             for(var i = 0; i < code.length; i++){
628                 if(i % 2 == 0){
629                     oddsum += Number(code[i]);
630                 }else{
631                     evensum += Number(code[i]);
632                 }
633             }
634             total = oddsum * 3 + evensum;
635             return Number((10 - total % 10) % 10);
636         },
637         // returns true if the ean is a valid EAN codebar number by checking the control digit.
638         // ean must be a string
639         check_ean: function(ean){
640             return /^\d+$/.test(ean) && this.ean_checksum(ean) === Number(ean[ean.length-1]);
641         },
642         // returns a valid zero padded ean13 from an ean prefix. the ean prefix must be a string.
643         sanitize_ean:function(ean){
644             ean = ean.substr(0,13);
645
646             for(var n = 0, count = (13 - ean.length); n < count; n++){
647                 ean = ean + '0';
648             }
649             return ean.substr(0,12) + this.ean_checksum(ean);
650         },
651         
652         // attempts to interpret an ean (string encoding an ean)
653         // it will check its validity then return an object containing various
654         // information about the ean.
655         // most importantly : 
656         // - code    : the ean
657         // - type   : the type of the ean: 
658         //      'price' |  'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error'
659         //
660         // - prefix : the prefix that has ben used to determine the type
661         // - id     : the part of the ean that identifies something
662         // - value  : if the id encodes a numerical value, it will be put there
663         // - unit   : if the encoded value has a unit, it will be put there. 
664         //            not to be confused with the 'unit' type, which represent an unit of a 
665         //            unique product
666         // - base_code : the ean code with all the encoding parts set to zero; the one put on
667         //               the product in the backend
668
669         parse_ean: function(ean){
670             var parse_result = {
671                 encoding: 'ean13',
672                 type:'unknown',  
673                 prefix:'',
674                 code:ean,
675                 base_code: ean,
676                 id:'',
677                 value: 0,
678                 unit: 'none',
679             };
680
681             function match_prefix(prefix_set, type){
682                 for(prefix in prefix_set){
683                     if(ean.substring(0,prefix.length) === prefix){
684                         parse_result.prefix = prefix;
685                         parse_result.type = type;
686                         return true;
687                     }
688                 }
689                 return false;
690             }
691
692             if (!this.check_ean(ean)){
693                 parse_result.type = 'error';
694             } else if( match_prefix(this.price_prefix_set,'price')){
695                 parse_result.id = ean.substring(0,7);
696                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
697                 parse_result.value = Number(ean.substring(7,12))/100.0;
698                 parse_result.unit  = 'euro';
699             } else if( match_prefix(this.weight_prefix_set,'weight')){
700                 parse_result.id = ean.substring(0,7);
701                 parse_result.value = Number(ean.substring(7,12))/1000.0;
702                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
703                 parse_result.unit = 'Kg';
704             } else if( match_prefix(this.client_prefix_set,'client')){
705                 parse_result.id = ean.substring(0,7);
706                 parse_result.unit = 'Kg';
707             } else if( match_prefix(this.cashier_prefix_set,'cashier')){
708                 parse_result.id = ean.substring(0,7);
709             } else if( match_prefix(this.discount_prefix_set,'discount')){
710                 parse_result.id    = ean.substring(0,7);
711                 parse_result.base_code = this.sanitize_ean(ean.substring(0,7));
712                 parse_result.value = Number(ean.substring(7,12))/100.0;
713                 parse_result.unit  = '%';
714             } else {
715                 parse_result.type = 'unit';
716                 parse_result.prefix = '';
717                 parse_result.id = ean;
718             }
719             return parse_result;
720         },
721         
722         scan: function(code){
723             if(code.length < 3){
724                 return;
725             }else if(code.length === 13 && this.check_ean(code)){
726                 var parse_result = this.parse_ean(code);
727             }else if(code.length === 12 && this.check_ean('0'+code)){
728                 // many barcode scanners strip the leading zero of ean13 barcodes.
729                 // This is because ean-13 are UCP-A with an additional zero at the beginning,
730                 // so by stripping zeros you get retrocompatibility with UCP-A systems.
731                 var parse_result = this.parse_ean('0'+code);
732             }else if(this.pos.db.get_product_by_reference(code)){
733                 var parse_result = {
734                     encoding: 'reference',
735                     type: 'unit',
736                     code: code,
737                     prefix: '',
738                 };
739             }else{
740                 var parse_result = {
741                     encoding: 'error',
742                     type: 'error',
743                     code: code,
744                     prefix: '',
745                 };
746                 return;
747             }
748
749             if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){    //ean is associated to a product
750                 if(this.action_callback['product']){
751                     this.action_callback['product'](parse_result);
752                 }
753             }else{
754                 if(this.action_callback[parse_result.type]){
755                     this.action_callback[parse_result.type](parse_result);
756                 }
757             }
758         },
759
760         // starts catching keyboard events and tries to interpret codebar 
761         // calling the callbacks when needed.
762         connect: function(){
763
764             var self = this;
765             var code = "";
766             var timeStamp  = 0;
767             var onlynumbers = true;
768             var timeout = null;
769
770             this.handler = function(e){
771
772                 if(e.which === 13){ //ignore returns
773                     e.preventDefault();
774                     return;
775                 }
776
777                 if(timeStamp + 50 < new Date().getTime()){
778                     code = "";
779                     onlynumbers = true;
780                 }
781
782                 timeStamp = new Date().getTime();
783                 clearTimeout(timeout);
784
785                 if( e.which < 48 || e.which >= 58 ){ // not a number
786                     onlynumbers = false;
787                 }
788
789                 code += String.fromCharCode(e.which);
790
791                 // we wait for a while after the last input to be sure that we are not mistakingly
792                 // returning a code which is a prefix of a bigger one :
793                 // Internal Ref 5449 vs EAN13 5449000...
794
795                 timeout = setTimeout(function(){
796                     self.scan(code);
797                     code = "";
798                     onlynumbers = true;
799                 },100);
800             };
801
802             $('body').on('keypress', this.handler);
803         },
804
805         // stops catching keyboard events 
806         disconnect: function(){
807             $('body').off('keypress', this.handler)
808         },
809
810         // the barcode scanner will listen on the hw_proxy/scanner interface for 
811         // scan events until disconnect_from_proxy is called
812         connect_to_proxy: function(){ 
813             var self = this;
814             this.remote_scanning = true;
815             if(this.remote_active >= 1){
816                 return;
817             }
818             this.remote_active = 1;
819
820             function waitforbarcode(){
821                 return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500})
822                     .then(function(barcode){
823                         if(!self.remote_scanning){ 
824                             self.remote_active = 0;
825                             return; 
826                         }
827                         self.scan(barcode);
828                         waitforbarcode();
829                     },
830                     function(){
831                         if(!self.remote_scanning){
832                             self.remote_active = 0;
833                             return;
834                         }
835                         setTimeout(waitforbarcode,5000);
836                     });
837             }
838             waitforbarcode();
839         },
840
841         // the barcode scanner will stop listening on the hw_proxy/scanner remote interface
842         disconnect_from_proxy: function(){
843             this.remote_scanning = false;
844         },
845     });
846
847 }