[IMP] proxy upgraded to new JsonRPC API
[odoo/odoo.git] / addons / point_of_sale / static / src / js / pos_devices.js
1
2 function openerp_pos_devices(instance,module){ //module is instance.point_of_sale
3
4     var QWeb = instance.web.qweb;   //TODO FIXME this should NOT be of any use in this file
5
6     window.debug_devices = new (instance.web.Class.extend({
7         payment_status: 'waiting_for_payment',
8         weight: 0,
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'; },
12     }))();
13
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. 
17
18     module.ProxyDevice  = instance.web.Class.extend({
19         init: function(options){
20             options = options || {};
21             url = options.url || 'http://localhost:8069';
22
23             this.connection = new instance.web.JsonRPC();
24             this.connection.setup(url);
25         },
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);
30         },
31         
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');
36         },
37
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');
42         },
43
44         //the client is asking for help
45         help_needed: function(){
46             this.message('help_needed');
47             console.log('PROXY: help needed');
48         },
49
50         //the client does not need help anymore
51         help_canceled: function(){
52             this.message('help_canceled');
53             console.log('PROXY: help canceled');
54         },
55
56         //the client is starting to weight
57         weighting_start: function(){
58             this.message('weighting_start');
59             console.log('PROXY: weighting start');
60         },
61
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;
70         },
71
72         // the client has finished weighting products
73         weighting_end: function(){
74             this.message('weighting_end');
75             console.log('PROXY: weighting end');
76         },
77
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);
84         },
85
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;
94         },
95
96         // the client cancels his payment
97         payment_canceled: function(){
98             this.message('payment_canceled');
99             console.log('PROXY: payment canceled by client');
100         },
101
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');
106         },
107
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');
112         },
113
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');
118         },
119
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');
124         },
125     });
126
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({
132
133         init: function(attributes){
134             this.pos = attributes.pos;
135             this.action_callback = {
136                 'product': undefined,   
137                 'cashier': undefined,
138                 'client':  undefined,
139                 'discount': undefined,
140             };
141
142             this.action_callback_stack = [];
143
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':''};
149         },
150         save_callbacks: function(){
151             var callbacks = {};
152             for(name in this.action_callback){
153                 callbacks[name] = this.action_callback[name];
154             }
155             this.action_callback_stack.push(callbacks);
156         },
157         restore_callbacks: function(){
158             if(this.action_callback_stack.length){
159                 var callbacks = this.action_callback_stack.pop();
160                 this.action_callback = callbacks;
161             }
162         },
163        
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)) 
167         // 
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. 
171         // 
172         // possible actions include : 
173         // 'product' | 'cashier' | 'client' | 'discount' 
174     
175         set_action_callbacks: function(callbacks){
176             for(action in callbacks){
177                 this.action_callback[action] = callbacks[action];
178             }
179         },
180
181         //remove all action callbacks 
182         reset_action_callbacks: function(){
183             for(action in this.action_callback){
184                 this.action_callback[action] = undefined;
185             }
186         },
187
188
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]);
195             }
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) {
200                 st2.push(0);
201             }
202             var countSt3 = 1;
203             var st3 = 0;
204             $.each(st2, function() {
205                 if (countSt3%2 === 1) {
206                     st3 +=  this;
207                 }
208                 countSt3 ++;
209             });
210             st3 *= 3;
211             var st4 = 0;
212             var countSt4 = 1;
213             $.each(st2, function() {
214                 if (countSt4%2 === 0) {
215                     st4 += this;
216                 }
217                 countSt4 ++;
218             });
219             var st5 = st3 + st4;
220             var cd = (10 - (st5%10)) % 10;
221             return code[code.length-1] === cd;
222         },
223         
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 : 
228         // - ean    : the ean
229         // - type   : the type of the ean: 
230         //      'price' |  'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error'
231         //
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 
237         //            unique product
238
239         parse_ean: function(ean){
240             var parse_result = {
241                 type:'unknown', // 
242                 prefix:'',
243                 ean:ean,
244                 id:'',
245                 value: 0,
246                 unit: 'none',
247             };
248             var prefix2 = ean.substring(0,2);
249
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  = '%';
278             }else{
279                 parse_result.type = 'unit';
280                 parse_result.prefix = '';
281                 parse_result.id = ean;
282             }
283             return parse_result;
284         },
285
286         // starts catching keyboard events and tries to interpret codebar 
287         // calling the callbacks when needed.
288         connect: function(){
289             var self = this;
290             var codeNumbers = [];
291             var timeStamp = 0;
292             var lastTimeStamp = 0;
293
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){
297
298                 //We only care about numbers
299                 if (!isNaN(Number(String.fromCharCode(e.keyCode)))) {
300
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();
305                     } else {
306                         if (lastTimeStamp + 30 < new Date().getTime()) {
307                             // not a barcode reader
308                             codeNumbers = [];
309                             timeStamp = new Date().getTime();
310                         }
311                     }
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);
318
319                         if (parse_result.type === 'error') {    //most likely a checksum error, raise warning
320                             $(QWeb.render('pos-scan-warning')).dialog({
321                                 resizable: false,
322                                 height:220,
323                                 modal: true,
324                                 title: "Warning",
325                             });
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);
329                             }
330                             //this.trigger("codebar",parse_result );
331                         }else{
332                             if(self.action_callback[parse_result.type]){
333                                 self.action_callback[parse_result.type](parse_result);
334                             }
335                         }
336
337                         codeNumbers = [];
338                     }
339                 } else {
340                     // NaN
341                     codeNumbers = [];
342                 }
343             });
344         },
345
346         // stops catching keyboard events 
347         disconnect: function(){
348             $('body').undelegate('', 'keyup')
349         },
350     });
351
352 }