1 openerp.point_of_sale.load_devices = function load_devices(instance,module){ //module is instance.point_of_sale
4 var _t = instance.web._t;
6 // the JobQueue schedules a sequence of 'jobs'. each job is
7 // a function returning a deferred. the queue waits for each job to finish
8 // before launching the next. Each job can also be scheduled with a delay.
9 // the is used to prevent parallel requests to the proxy.
11 module.JobQueue = function(){
14 var scheduled_end_time = 0;
15 var end_of_queue = (new $.Deferred()).resolve();
16 var stoprepeat = false;
19 if(end_of_queue.state() === 'resolved'){
20 end_of_queue = new $.Deferred();
25 if(!job.opts.repeat || stoprepeat){
30 // the time scheduled for this job
31 scheduled_end_time = (new Date()).getTime() + (job.opts.duration || 0);
33 // we run the job and put in def when it finishes
34 var def = job.fun() || (new $.Deferred()).resolve();
36 // we don't care if a job fails ...
37 def.always(function(){
38 // we run the next job after the scheduled_end_time, even if it finishes before
39 setTimeout(function(){
41 }, Math.max(0, scheduled_end_time - (new Date()).getTime()) );
45 scheduled_end_time = 0;
46 end_of_queue.resolve();
50 // adds a job to the schedule.
52 // duration : the job is guaranteed to finish no quicker than this (milisec)
53 // repeat : if true, the job will be endlessly repeated
54 // important : if true, the scheduled job cannot be canceled by a queue.clear()
56 this.schedule = function(fun, opts){
57 queue.push({fun:fun, opts:opts || {}});
63 // remove all jobs from the schedule (except the ones marked as important)
64 this.clear = function(){
65 queue = _.filter(queue,function(job){job.opts.important === true});
68 // end the repetition of the current job
69 this.stoprepeat = function(){
73 // returns a deferred that resolves when all scheduled
74 // jobs have been run.
75 // ( jobs added after the call to this method are considered as well )
76 this.finished = function(){
83 // this object interfaces with the local proxy to communicate to the various hardware devices
84 // connected to the Point of Sale. As the communication only goes from the POS to the proxy,
85 // methods are used both to signal an event, and to fetch information.
87 module.ProxyDevice = instance.web.Class.extend(openerp.PropertiesMixin,{
88 init: function(parent,options){
89 openerp.PropertiesMixin.init.call(this,parent);
91 options = options || {};
92 var url = options.url || 'http://localhost:8069';
96 this.weighting = false;
97 this.debug_weight = 0;
98 this.use_debug_weight = false;
101 this.default_payment_status = {
104 payment_method: undefined,
105 receipt_client: undefined,
106 receipt_shop: undefined,
108 this.custom_payment_status = this.default_payment_status;
110 this.receipt_queue = [];
112 this.notifications = {};
113 this.bypass_proxy = false;
115 this.connection = null;
117 this.keptalive = false;
119 this.set('status',{});
121 this.set_connection_status('disconnected');
123 this.on('change:status',this,function(eh,status){
124 status = status.newValue;
125 if(status.status === 'connected'){
126 self.print_receipt();
130 window.hw_proxy = this;
132 set_connection_status: function(status,drivers){
133 var oldstatus = this.get('status');
135 newstatus.status = status;
136 newstatus.drivers = status === 'disconnected' ? {} : oldstatus.drivers;
137 newstatus.drivers = drivers ? drivers : newstatus.drivers;
138 this.set('status',newstatus);
140 disconnect: function(){
141 if(this.get('status').status !== 'disconnected'){
142 this.connection.destroy();
143 this.set_connection_status('disconnected');
147 // connects to the specified url
148 connect: function(url){
150 this.connection = new instance.web.Session(undefined,url, { use_cors: true});
152 this.set_connection_status('connecting',{});
154 return this.message('handshake').then(function(response){
156 self.set_connection_status('connected');
157 localStorage['hw_proxy_url'] = url;
160 self.set_connection_status('disconnected');
161 console.error('Connection refused by the Proxy');
164 self.set_connection_status('disconnected');
165 console.error('Could not connect to the Proxy');
169 // find a proxy and connects to it. for options see find_proxy
170 // - force_ip : only try to connect to the specified ip.
171 // - port: what port to listen to (default 8069)
172 // - progress(fac) : callback for search progress ( fac in [0,1] )
173 autoconnect: function(options){
175 this.set_connection_status('connecting',{});
176 var found_url = new $.Deferred();
177 var success = new $.Deferred();
179 if ( options.force_ip ){
180 // if the ip is forced by server config, bailout on fail
181 found_url = this.try_hard_to_connect(options.force_ip, options)
182 }else if( localStorage['hw_proxy_url'] ){
183 // try harder when we remember a good proxy url
184 found_url = this.try_hard_to_connect(localStorage['hw_proxy_url'], options)
185 .then(null,function(){
186 return self.find_proxy(options);
189 // just find something quick
190 found_url = this.find_proxy(options);
193 success = found_url.then(function(url){
194 return self.connect(url);
197 success.fail(function(){
198 self.set_connection_status('disconnected');
204 // starts a loop that updates the connection status
205 keepalive: function(){
209 self.connection.rpc('/hw_proxy/status_json',{},{timeout:2500})
210 .then(function(driver_status){
211 self.set_connection_status('connected',driver_status);
213 if(self.get('status').status !== 'connecting'){
214 self.set_connection_status('disconnected');
216 }).always(function(){
217 setTimeout(status,5000);
222 this.keptalive = true;
227 message : function(name,params){
228 var callbacks = this.notifications[name] || [];
229 for(var i = 0; i < callbacks.length; i++){
230 callbacks[i](params);
232 if(this.get('status').status !== 'disconnected'){
233 return this.connection.rpc('/hw_proxy/' + name, params || {});
235 return (new $.Deferred()).reject();
239 // try several time to connect to a known proxy url
240 try_hard_to_connect: function(url,options){
241 options = options || {};
242 var port = ':' + (options.port || '8069');
244 this.set_connection_status('connecting');
246 if(url.indexOf('//') < 0){
250 if(url.indexOf(':',5) < 0){
254 // try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
255 function try_real_hard_to_connect(url, retries, done){
257 done = done || new $.Deferred();
260 url: url + '/hw_proxy/hello',
269 try_real_hard_to_connect(url,retries-1,done);
277 return try_real_hard_to_connect(url,3);
280 // returns as a deferred a valid host url that can be used as proxy.
282 // - port: what port to listen to (default 8069)
283 // - progress(fac) : callback for search progress ( fac in [0,1] )
284 find_proxy: function(options){
285 options = options || {};
287 var port = ':' + (options.port || '8069');
291 var done = new $.Deferred(); // will be resolved with the proxies valid urls
296 urls.push('http://localhost'+port);
297 for(var i = 0; i < 256; i++){
298 urls.push('http://192.168.0.'+i+port);
299 urls.push('http://192.168.1.'+i+port);
300 urls.push('http://10.0.0.'+i+port);
303 var prog_inc = 1/urls.length;
305 function update_progress(){
306 progress = found ? 1 : progress + prog_inc;
307 if(options.progress){
308 options.progress(progress);
312 function thread(done){
313 var url = urls.shift();
315 done = done || new $.Deferred();
317 if( !url || found || !self.searching_for_proxy ){
323 url: url + '/hw_proxy/hello',
339 this.searching_for_proxy = true;
341 for(var i = 0, len = Math.min(parallel,urls.length); i < len; i++){
342 threads.push(thread());
345 $.when.apply($,threads).then(function(){
347 for(var i = 0; i < arguments.length; i++){
349 urls.push(arguments[i]);
352 done.resolve(urls[0]);
358 stop_searching: function(){
359 this.searching_for_proxy = false;
360 this.set_connection_status('disconnected');
363 // this allows the client to be notified when a proxy call is made. The notification
364 // callback will be executed with the same arguments as the proxy call
365 add_notification: function(name, callback){
366 if(!this.notifications[name]){
367 this.notifications[name] = [];
369 this.notifications[name].push(callback);
372 // returns the weight on the scale.
373 scale_read: function(){
375 var ret = new $.Deferred();
376 this.message('scale_read',{})
377 .then(function(weight){
378 ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
379 }, function(){ //failed to read weight
380 ret.resolve(self.use_debug_weight ? self.debug_weight : {weight:0.0, unit:'Kg', info:'ok'});
385 // sets a custom weight, ignoring the proxy returned value.
386 debug_set_weight: function(kg){
387 this.use_debug_weight = true;
388 this.debug_weight = kg;
391 // resets the custom weight and re-enable listening to the proxy for weight values
392 debug_reset_weight: function(){
393 this.use_debug_weight = false;
394 this.debug_weight = 0;
397 // ask for the cashbox (the physical box where you store the cash) to be opened
398 open_cashbox: function(){
399 return this.message('open_cashbox');
403 * ask the printer to print a receipt
405 print_receipt: function(receipt){
408 this.receipt_queue.push(receipt);
411 function send_printing_job(){
412 if (self.receipt_queue.length > 0){
413 var r = self.receipt_queue.shift();
414 self.message('print_xml_receipt',{ receipt: r },{ timeout: 5000 })
419 self.pos.pos_widget.screen_selector.show_popup('error-traceback',{
420 'message': _t('Printing Error: ') + error.data.message,
421 'comment': error.data.debug,
425 self.receipt_queue.unshift(r)
432 // asks the proxy to log some information, as with the debug.log you can provide several arguments.
434 return this.message('log',{'arguments': _.toArray(arguments)});
439 // this module interfaces with the barcode reader. It assumes the barcode reader
440 // is set-up to act like a keyboard. Use connect() and disconnect() to activate
441 // and deactivate the barcode reader. Use set_action_callbacks to tell it
442 // what to do when it reads a barcode.
443 module.BarcodeReader = instance.web.Class.extend({
450 init: function(attributes){
451 this.pos = attributes.pos;
452 this.action_callback = {};
453 this.proxy = attributes.proxy;
454 this.remote_scanning = false;
455 this.remote_active = 0;
457 this.barcode_parser = attributes.barcode_parser;
459 this.action_callback_stack = [];
462 set_barcode_parser: function(barcode_parser) {
463 this.barcode_parser = barcode_parser;
466 save_callbacks: function(){
468 for(var name in this.action_callback){
469 callbacks[name] = this.action_callback[name];
471 this.action_callback_stack.push(callbacks);
474 restore_callbacks: function(){
475 if(this.action_callback_stack.length){
476 var callbacks = this.action_callback_stack.pop();
477 this.action_callback = callbacks;
481 // when a barcode is scanned and parsed, the callback corresponding
482 // to its type is called with the parsed_barcode as a parameter.
483 // (parsed_barcode is the result of parse_barcode(barcode))
485 // callbacks is a Map of 'actions' : callback(parsed_barcode)
486 // that sets the callback for each action. if a callback for the
487 // specified action already exists, it is replaced.
489 // possible actions include :
490 // 'product' | 'cashier' | 'client' | 'discount'
491 set_action_callback: function(action, callback){
492 if(arguments.length == 2){
493 this.action_callback[action] = callback;
495 var actions = arguments[0];
496 for(var action in actions){
497 this.set_action_callback(action,actions[action]);
502 //remove all action callbacks
503 reset_action_callbacks: function(){
504 for(var action in this.action_callback){
505 this.action_callback[action] = undefined;
509 scan: function(code){
510 var parsed_result = this.barcode_parser.parse_barcode(code);
512 if(parsed_result.type in {'product':'', 'weight':'', 'price':''}){ //barcode is associated to a product
513 if(this.action_callback['product']){
514 this.action_callback['product'](parsed_result);
517 else if (parsed_result.type in {'cashier':'', 'client':'', 'discount':''}){
518 if(this.action_callback[parsed_result.type]){
519 this.action_callback[parsed_result.type](parsed_result);
523 this.action_callback['error'](parsed_result);
527 // starts catching keyboard events and tries to interpret codebar
528 // calling the callbacks when needed.
536 this.handler = function(e){
538 if(e.which === 13){ //ignore returns
543 if(timeStamp + 50 < new Date().getTime()){
547 timeStamp = new Date().getTime();
548 clearTimeout(timeout);
550 code += String.fromCharCode(e.which);
552 // we wait for a while after the last input to be sure that we are not mistakingly
553 // returning a code which is a prefix of a bigger one :
554 // Internal Ref 5449 vs EAN13 5449000...
556 timeout = setTimeout(function(){
557 if(code.length >= 3){
564 $('body').on('keypress', this.handler);
567 // stops catching keyboard events
568 disconnect: function(){
569 $('body').off('keypress', this.handler)
572 // the barcode scanner will listen on the hw_proxy/scanner interface for
573 // scan events until disconnect_from_proxy is called
574 connect_to_proxy: function(){
576 this.remote_scanning = true;
577 if(this.remote_active >= 1){
580 this.remote_active = 1;
582 function waitforbarcode(){
583 return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500})
584 .then(function(barcode){
585 if(!self.remote_scanning){
586 self.remote_active = 0;
593 if(!self.remote_scanning){
594 self.remote_active = 0;
597 setTimeout(waitforbarcode,5000);
603 // the barcode scanner will stop listening on the hw_proxy/scanner remote interface
604 disconnect_from_proxy: function(){
605 this.remote_scanning = false;