[IMP] point_of_sale: put the POS js files include function into the openerp namespace
[odoo/odoo.git] / addons / point_of_sale / static / src / js / db.js
1 openerp.point_of_sale.load_db = function load_db(instance, module){ 
2     "use strict";
3
4     /* The PosDB holds reference to data that is either
5      * - static: does not change between pos reloads
6      * - persistent : must stay between reloads ( orders )
7      */
8
9     module.PosDB = instance.web.Class.extend({
10         name: 'openerp_pos_db', //the prefix of the localstorage data
11         limit: 100,  // the maximum number of results returned by a search
12         init: function(options){
13             options = options || {};
14             this.name = options.name || this.name;
15             this.limit = options.limit || this.limit;
16             
17             if (options.uuid) {
18                 this.name = this.name + '_' + options.uuid;
19             }
20
21             //cache the data in memory to avoid roundtrips to the localstorage
22             this.cache = {};
23
24             this.product_by_id = {};
25             this.product_by_barcode = {};
26             this.product_by_category_id = {};
27
28             this.partner_sorted = [];
29             this.partner_by_id = {};
30             this.partner_by_barcode = {};
31             this.partner_search_string = "";
32             this.partner_write_date = null;
33
34             this.category_by_id = {};
35             this.root_category_id  = 0;
36             this.category_products = {};
37             this.category_ancestors = {};
38             this.category_childs = {};
39             this.category_parent    = {};
40             this.category_search_string = {};
41             this.packagings_by_id = {};
42             this.packagings_by_product_tmpl_id = {};
43             this.packagings_by_barcode = {};
44         },
45
46         /* 
47          * sets an uuid to prevent conflict in locally stored data between multiple databases running
48          * in the same browser at the same origin (Doing this is not advised !)
49          */
50         set_uuid: function(uuid){
51             this.name = this.name + '_' + uuid;
52         },
53
54         /* returns the category object from its id. If you pass a list of id as parameters, you get
55          * a list of category objects. 
56          */  
57         get_category_by_id: function(categ_id){
58             if(categ_id instanceof Array){
59                 var list = [];
60                 for(var i = 0, len = categ_id.length; i < len; i++){
61                     var cat = this.category_by_id[categ_id[i]];
62                     if(cat){
63                         list.push(cat);
64                     }else{
65                         console.error("get_category_by_id: no category has id:",categ_id[i]);
66                     }
67                 }
68                 return list;
69             }else{
70                 return this.category_by_id[categ_id];
71             }
72         },
73         /* returns a list of the category's child categories ids, or an empty list 
74          * if a category has no childs */
75         get_category_childs_ids: function(categ_id){
76             return this.category_childs[categ_id] || [];
77         },
78         /* returns a list of all ancestors (parent, grand-parent, etc) categories ids
79          * starting from the root category to the direct parent */
80         get_category_ancestors_ids: function(categ_id){
81             return this.category_ancestors[categ_id] || [];
82         },
83         /* returns the parent category's id of a category, or the root_category_id if no parent.
84          * the root category is parent of itself. */
85         get_category_parent_id: function(categ_id){
86             return this.category_parent[categ_id] || this.root_category_id;
87         },
88         /* adds categories definitions to the database. categories is a list of categories objects as
89          * returned by the openerp server. Categories must be inserted before the products or the 
90          * product/ categories association may (will) not work properly */
91         add_categories: function(categories){
92             var self = this;
93             if(!this.category_by_id[this.root_category_id]){
94                 this.category_by_id[this.root_category_id] = {
95                     id : this.root_category_id,
96                     name : 'Root',
97                 };
98             }
99             for(var i=0, len = categories.length; i < len; i++){
100                 this.category_by_id[categories[i].id] = categories[i];
101             }
102             for(var i=0, len = categories.length; i < len; i++){
103                 var cat = categories[i];
104                 var parent_id = cat.parent_id[0] || this.root_category_id;
105                 this.category_parent[cat.id] = cat.parent_id[0];
106                 if(!this.category_childs[parent_id]){
107                     this.category_childs[parent_id] = [];
108                 }
109                 this.category_childs[parent_id].push(cat.id);
110             }
111             function make_ancestors(cat_id, ancestors){
112                 self.category_ancestors[cat_id] = ancestors;
113
114                 ancestors = ancestors.slice(0);
115                 ancestors.push(cat_id);
116
117                 var childs = self.category_childs[cat_id] || [];
118                 for(var i=0, len = childs.length; i < len; i++){
119                     make_ancestors(childs[i], ancestors);
120                 }
121             }
122             make_ancestors(this.root_category_id, []);
123         },
124         category_contains: function(categ_id, product_id) {
125             var product = this.product_by_id[product_id];
126             if (product) {
127                 var cid = product.pos_categ_id[0];
128                 while (cid && cid !== categ_id){
129                     cid = this.category_parent[cid];
130                 }
131                 return !!cid;
132             }
133             return false;
134         },
135         /* loads a record store from the database. returns default if nothing is found */
136         load: function(store,deft){
137             if(this.cache[store] !== undefined){
138                 return this.cache[store];
139             }
140             var data = localStorage[this.name + '_' + store];
141             if(data !== undefined && data !== ""){
142                 data = JSON.parse(data);
143                 this.cache[store] = data;
144                 return data;
145             }else{
146                 return deft;
147             }
148         },
149         /* saves a record store to the database */
150         save: function(store,data){
151             var str_data = JSON.stringify(data);
152             localStorage[this.name + '_' + store] = JSON.stringify(data);
153             this.cache[store] = data;
154         },
155         _product_search_string: function(product){
156             var str = '' + product.id + ':' + product.display_name;
157             if (product.barcode) {
158                 str += '|' + product.barcode;
159             }
160             if (product.default_code) {
161                 str += '|' + product.default_code.replace(':','');
162             }
163             if (product.description) {
164                 str += '|' + product.description.replace(':','');
165             }
166             if (product.description_sale) {
167                 str += '|' + product.description_sale.replace(':','');
168             }
169             var packagings = this.packagings_by_product_tmpl_id[product.product_tmpl_id] || [];
170             for (var i = 0; i < packagings.length; i++) {
171                 str += '|' + packagings[i].barcode;
172             }
173             return str + '\n';
174         },
175         add_products: function(products){
176             var stored_categories = this.product_by_category_id;
177
178             if(!products instanceof Array){
179                 products = [products];
180             }
181             for(var i = 0, len = products.length; i < len; i++){
182                 var product = products[i];
183                 var search_string = this._product_search_string(product);
184                 var categ_id = product.pos_categ_id ? product.pos_categ_id[0] : this.root_category_id;
185                 product.product_tmpl_id = product.product_tmpl_id[0];
186                 if(!stored_categories[categ_id]){
187                     stored_categories[categ_id] = [];
188                 }
189                 stored_categories[categ_id].push(product.id);
190
191                 if(this.category_search_string[categ_id] === undefined){
192                     this.category_search_string[categ_id] = '';
193                 }
194                 this.category_search_string[categ_id] += search_string;
195
196                 var ancestors = this.get_category_ancestors_ids(categ_id) || [];
197
198                 for(var j = 0, jlen = ancestors.length; j < jlen; j++){
199                     var ancestor = ancestors[j];
200                     if(! stored_categories[ancestor]){
201                         stored_categories[ancestor] = [];
202                     }
203                     stored_categories[ancestor].push(product.id);
204
205                     if( this.category_search_string[ancestor] === undefined){
206                         this.category_search_string[ancestor] = '';
207                     }
208                     this.category_search_string[ancestor] += search_string; 
209                 }
210                 this.product_by_id[product.id] = product;
211                 if(product.barcode){
212                     this.product_by_barcode[product.barcode] = product;
213                 }
214             }
215         },
216         add_packagings: function(packagings){
217             for(var i = 0, len = packagings.length; i < len; i++){
218                 var pack = packagings[i];
219                 this.packagings_by_id[pack.id] = pack;
220                 if(!this.packagings_by_product_tmpl_id[pack.product_tmpl_id[0]]){
221                     this.packagings_by_product_tmpl_id[pack.product_tmpl_id[0]] = [];
222                 }
223                 this.packagings_by_product_tmpl_id[pack.product_tmpl_id[0]].push(pack);
224                 if(pack.barcode){
225                     this.packagings_by_barcode[pack.barcode] = pack;
226                 }
227             }
228         },
229         _partner_search_string: function(partner){
230             var str = '' + partner.id + ':' + partner.name;
231             if(partner.barcode){
232                 str += '|' + partner.barcode;
233             }
234             if(partner.address){
235                 str += '|' + partner.address;
236             }
237             if(partner.phone){
238                 str += '|' + partner.phone.split(' ').join('');
239             }
240             if(partner.mobile){
241                 str += '|' + partner.mobile.split(' ').join('');
242             }
243             if(partner.email){
244                 str += '|' + partner.email;
245             }
246             return str + '\n';
247         },
248         add_partners: function(partners){
249             var updated_count = 0;
250             var new_write_date = '';
251             for(var i = 0, len = partners.length; i < len; i++){
252                 var partner = partners[i];
253
254                 if (    this.partner_write_date && 
255                         this.partner_by_id[partner.id] &&
256                         new Date(this.partner_write_date).getTime() + 1000 >=
257                         new Date(partner.write_date).getTime() ) {
258                     // FIXME: The write_date is stored with milisec precision in the database
259                     // but the dates we get back are only precise to the second. This means when
260                     // you read partners modified strictly after time X, you get back partners that were
261                     // modified X - 1 sec ago. 
262                     continue;
263                 } else if ( new_write_date < partner.write_date ) { 
264                     new_write_date  = partner.write_date;
265                 }
266                 if (!this.partner_by_id[partner.id]) {
267                     this.partner_sorted.push(partner.id);
268                 }
269                 this.partner_by_id[partner.id] = partner;
270
271                 updated_count += 1;
272             }
273
274             this.partner_write_date = new_write_date || this.partner_write_date;
275
276             if (updated_count) {
277                 // If there were updates, we need to completely 
278                 // rebuild the search string and the barcode indexing
279
280                 this.partner_search_string = "";
281                 this.partner_by_barcode = {};
282
283                 for (var id in this.partner_by_id) {
284                     var partner = this.partner_by_id[id];
285
286                     if(partner.barcode){
287                         this.partner_by_barcode[partner.barcode] = partner;
288                     }
289                     partner.address = (partner.street || '') +', '+ 
290                                       (partner.zip || '')    +' '+
291                                       (partner.city || '')   +', '+ 
292                                       (partner.country_id[1] || '');
293                     this.partner_search_string += this._partner_search_string(partner);
294                 }
295             }
296             return updated_count;
297         },
298         get_partner_write_date: function(){
299             return this.partner_write_date;
300         },
301         get_partner_by_id: function(id){
302             return this.partner_by_id[id];
303         },
304         get_partner_by_barcode: function(barcode){
305             return this.partner_by_barcode[barcode];
306         },
307         get_partners_sorted: function(max_count){
308             max_count = max_count ? Math.min(this.partner_sorted.length, max_count) : this.partner_sorted.length;
309             var partners = [];
310             for (var i = 0; i < max_count; i++) {
311                 partners.push(this.partner_by_id[this.partner_sorted[i]]);
312             }
313             return partners;
314         },
315         search_partner: function(query){
316             try {
317                 query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.');
318                 query = query.replace(' ','.+');
319                 var re = RegExp("([0-9]+):.*?"+query,"gi");
320             }catch(e){
321                 return [];
322             }
323             var results = [];
324             for(var i = 0; i < this.limit; i++){
325                 var r = re.exec(this.partner_search_string);
326                 if(r){
327                     var id = Number(r[1]);
328                     results.push(this.get_partner_by_id(id));
329                 }else{
330                     break;
331                 }
332             }
333             return results;
334         },
335         /* removes all the data from the database. TODO : being able to selectively remove data */
336         clear: function(stores){
337             for(var i = 0, len = arguments.length; i < len; i++){
338                 localStorage.removeItem(this.name + '_' + arguments[i]);
339             }
340         },
341         /* this internal methods returns the count of properties in an object. */
342         _count_props : function(obj){
343             var count = 0;
344             for(var prop in obj){
345                 if(obj.hasOwnProperty(prop)){
346                     count++;
347                 }
348             }
349             return count;
350         },
351         get_product_by_id: function(id){
352             return this.product_by_id[id];
353         },
354         get_product_by_barcode: function(barcode){
355             if(this.product_by_barcode[barcode]){
356                 return this.product_by_barcode[barcode];
357             }
358             var pack = this.packagings_by_barcode[barcode];
359             if(pack){
360                 return this.product_by_id[pack.product_tmpl_id[0]];
361             }
362             return undefined;
363         },
364         get_product_by_category: function(category_id){
365             var product_ids  = this.product_by_category_id[category_id];
366             var list = [];
367             if (product_ids) {
368                 for (var i = 0, len = Math.min(product_ids.length, this.limit); i < len; i++) {
369                     list.push(this.product_by_id[product_ids[i]]);
370                 }
371             }
372             return list;
373         },
374         /* returns a list of products with :
375          * - a category that is or is a child of category_id,
376          * - a name, package or barcode containing the query (case insensitive) 
377          */
378         search_product_in_category: function(category_id, query){
379             try {
380                 query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.');
381                 query = query.replace(' ','.+');
382                 var re = RegExp("([0-9]+):.*?"+query,"gi");
383             }catch(e){
384                 return [];
385             }
386             var results = [];
387             for(var i = 0; i < this.limit; i++){
388                 var r = re.exec(this.category_search_string[category_id]);
389                 if(r){
390                     var id = Number(r[1]);
391                     results.push(this.get_product_by_id(id));
392                 }else{
393                     break;
394                 }
395             }
396             return results;
397         },
398
399         /* paid orders */
400         add_order: function(order){
401             var order_id = order.uid;
402             var orders  = this.load('orders',[]);
403
404             // if the order was already stored, we overwrite its data
405             for(var i = 0, len = orders.length; i < len; i++){
406                 if(orders[i].id === order_id){
407                     orders[i].data = order;
408                     this.save('orders',orders);
409                     return order_id;
410                 }
411             }
412
413             orders.push({id: order_id, data: order});
414             this.save('orders',orders);
415             return order_id;
416         },
417         remove_order: function(order_id){
418             var orders = this.load('orders',[]);
419             orders = _.filter(orders, function(order){
420                 return order.id !== order_id;
421             });
422             this.save('orders',orders);
423         },
424         remove_all_orders: function(){
425             this.save('orders',[]);
426         },
427         get_orders: function(){
428             return this.load('orders',[]);
429         },
430         get_order: function(order_id){
431             var orders = this.get_orders();
432             for(var i = 0, len = orders.length; i < len; i++){
433                 if(orders[i].id === order_id){
434                     return orders[i];
435                 }
436             }
437             return undefined;
438         },
439
440         /* working orders */
441         save_unpaid_order: function(order){
442             var order_id = order.uid;
443             var orders = this.load('unpaid_orders',[]);
444             var serialized = order.export_as_JSON();
445
446             for (var i = 0; i < orders.length; i++) {
447                 if (orders[i].id === order_id){
448                     orders[i].data = serialized;
449                     this.save('unpaid_orders',orders);
450                     return order_id;
451                 }
452             }
453
454             orders.push({id: order_id, data: serialized});
455             this.save('unpaid_orders',orders);
456             return order_id;
457         },
458         remove_unpaid_order: function(order){
459             var orders = this.load('unpaid_orders',[]);
460             orders = _.filter(orders, function(o){
461                 return o.id !== order.uid;
462             });
463             this.save('unpaid_orders',orders);
464         },
465         remove_all_unpaid_orders: function(){
466             this.save('unpaid_orders',[]);
467         },
468         get_unpaid_orders: function(){
469             var saved = this.load('unpaid_orders',[]);
470             var orders = [];
471             for (var i = 0; i < saved.length; i++) {
472                 orders.push(saved[i].data);
473             }
474             return orders;
475         },
476     });
477 }