[IMP] point_of_sale: removed IndexeDB and asynchronous backend

bzr revid: fva@openerp.com-20120813160026-04w28p3pojzvdlkj
This commit is contained in:
Frédéric van der Essen
2012-08-13 18:00:26 +02:00
parent b6c254942a
commit 48062d4cf1
4 changed files with 66 additions and 373 deletions
+26 -323
View File
@@ -1,284 +1,5 @@
/**
* WIP : AS OF THIS COMMIT, THE INDEXEDB BACKEND IS IN A NON WORKING STATE
*
* This file contains an IndexedDB (html5 database) backend for the Point Of Sale.
* The IDB is used to store the list of products and especially their thumbnails, which may
* not fit in the localStorage.
* The IDB offers a big performance boost for products lookup, but not so much for search as
* searching is not yet implemented in the IndexedDB API and must be performed manually in JS.
*
* this file also contains a localstorage implementation of the database to be used by browsers
* that don't support the indexeddb api.
*
*/
function openerp_pos_db(instance, module){
function importIndexedDB(){
if('indexedDB' in window){
return true;
}else if('webkitIndexedDB' in window){
window.indexedDB = window.webkitIndexedDB;
window.IDBCursor = window.webkitIDBCursor;
window.IDBDatabase = window.webkitIDBDatabase;
window.IDBDatabaseError = window.webkitIDBDatabaseError;
window.IDBDatabaseException = window.webkitIDBDatabaseException;
window.IDBFactory = window.webkitIDBFactory;
window.IDBIndex = window.webkitIDBIndex;
window.IDBKeyRange = window.webkitIDBKeyRange;
window.IDBObjectSrore = window.webkitIDBOjbectStore;
window.IDBRequest = window.webkitIDBRequest;
window.IDBTransaction = window.webkitIDBTransaction;
}else if('mozIndexedDB' in window){
window.indexedDB = window.mozIndexedDB;
}else{
throw new Error("DATABASE ERROR: IndexedDB not implemented. Please upgrade your web browser");
}
}
importIndexedDB();
module.PosDB = instance.web.Class.extend({
version: 1,
name: 'openerp_pos_db',
limit: 50,
init: function(options){
var self = this;
options = options || {};
this.version = options.version || this.version;
this.name = options.name || this.name;
var open_request = indexedDB.open(this.name, this.version);
open_request.onblocked = function(event){
throw new Error("DATABASE ERROR: request blocked.");
};
// new API (firefox)
open_request.onupgradeneeded = function(event){
console.log('using new API');
var transaction = open_request.transaction;
self._upgrade_db(event.oldVersion,transaction);
};
open_request.onsuccess = function(event){
console.log('db',self.db);
self.db = open_request.result;
var oldVersion = Number(self.db.version);
if(oldVersion !== self.version){
// if we get to this point, it means that onupgradeneeded hasn't been called.
// so we try the old api (webkit)
if(!self.db.setVersion){
throw new Error("DATABASE ERROR: database API is broken. Your web browser is probably out of date");
}
console.log('using old API');
var version_request = self.db.setVersion(self.version);
version_request.onsuccess = function(event){
var transaction = version_request.result;
self._upgrade_db(oldVersion, transaction);
};
}
};
},
_upgrade_db: function(oldversion, transaction){
console.log('upgrade_db:',oldversion);
this.db = transaction.db;
var productStore = this.db.createObjectStore('products', {keyPath: 'id'});
productStore.createIndex('ean13', 'ean13', {unique:true});
productStore.createIndex('name', 'name', {unique:false});
productStore.createIndex('category','category', {unique:false});
var imageStore = this.db.createObjectStore('images', {keyPath: 'id'});
var categoryStore = this.db.createObjectStore('categories', {keypath:'name'});
},
_add_data: function(store, data){
var transaction = this.db.transaction([store], 'readwrite');
var objectStore = transaction.objectStore(store);
var request = objectStore.put(data);
},
/* adds a product to the database, replacing the original product if it is already present.
* the product must be provided as a json as returned by the openerp backend
*/
add_product: function(product){
if(product instanceof Array){
for(var i = 0, len = product.length; i < len; i++){
this.add_product(product[i]);
}
}else{
if(product.product_image_small){
var image = product.product_image_small;
var product = _.clone(product);
delete product['product_image_small'];
this._add_data('images', { id: product.id, image:image});
}
this._add_data('products',product);
}
},
/* removes all the data in the database ! */
clear: function(done_callback){
var transaction = this.db.transaction(['products','images'],'readwrite');
transaction.objectStore('products').clear();
transaction.objectStore('images').clear();
if(done_callback){
transaction.oncomplete = function(){ done_callback(); };
}
},
get_product_count: function(result_callback){
this.db.transaction('products').objectStore('products').count().onsuccess = function(event){
result_callback(event.target.result);
};
},
get_image_count: function(result_callback){
this.db.transaction('images').objectStore('images').count().onsuccess = function(event){
result_callback(event.target.result);
};
},
/* fetches a product with an id of 'id', returns the product as the first parameter
* of the result_callback function
*/
get_product_by_id: function(id, result_callback){
var transaction = this.db.transaction('products');
var objectStore = transaction.objectStore('products');
var request = objectStore.get(id);
request.onsuccess = function(event){
if(result_callback){
result_callback(event.target.result);
}
};
},
get_product_by_name: function(name, result_callback){
var transaction = this.db.transaction('products');
var objectStore = transaction.objectStore('products');
var index = objectStore.index('name');
index.get(name).onsuccess = function(event){
result_callback(event.target.result);
};
},
/* fetches the product with the ean13 matching the one provided. returns the product
* as the first parameter of the result_callback function
*/
get_product_by_ean13: function(ean13, result_callback){
var transaction = this.db.transaction('products');
var objectStore = transaction.objectStore('products');
var index = objectStore.index('ean13');
index.get(ean13).onsuccess = function(event){
result_callback(event.target.result);
};
},
/* fetches all products belonging to a category, and returns the list of products
* as the first paramter of the result_callback function
*/
get_product_by_category: function(category, result_callback){
var transaction = this.db.transaction('products');
var objectStore = transaction.objectStore('products');
var index = objectStore.index('category');
var list = [];
index.openCursor(IDBKeyRange.only(category)).onsuccess = function(event){
var cursor = event.target.result;
if(cursor){
list.push(cursor.value);
cursor.continue();
}else{
result_callback(list);
}
};
},
/* Fetches a picture associated with a product.
* product: This is a product json object as returned by the openerp backend.
* result_callback: a callback that gets the image data as parameter.
*/
get_product_image: function(product, result_callback){
var transaction = this.db.transaction('images');
var objectStore = transaction.objectStore('images');
var request = objectStore.get(product.id);
request.onsuccess = function(event){
result_callback(event.target.result.image);
}
},
/* fields: a string or a list of string representing the list of the fields that
* will be searched. the values of those field must be a string.
* query: a string that will be matched in the content of one of the searched fields.
* result_callback: a function that will be called with a list of all the products with
* a field that match the query.
*/
search_product: function(fields,query,result_callback){
var transaction = this.db.transaction('products');
var objectStore = transaction.objectStore('products');
var list = [];
if(!(fields instanceof Array)){
fields = [ fields ];
}
query = query.toLowerCase();
objectStore.openCursor().onsuccess = function(event){
var cursor = event.target.result;
if(cursor){
var obj = cursor.value;
for(var i = 0, len = fields.length; i < len; i++){
if(obj[fields[i]].toLowerCase().indexOf(query) != -1){
list.push(obj);
break;
}
}
cursor.continue();
}else{
result_callback(list);
}
}
},
/* the callback function will be called with all products as parameter, by increasing id.
* if the callback returns 'break' the iteration will stop
*/
for_all_products: function(callback){
var transaction = this.db.transaction('products');
var objectStore = transaction.objectStore('products');
objectStore.openCursor().onsuccess = function(event){
var cursor = event.target.result;
if(cursor){
var ret = callback(cursor.value);
if(ret !== 'break'){
cursor.continue();
}
}
};
},
/* the callback function will be called with all products as parameter by increasing id.
* if the callback returns 'break', the iteration will stop
* if the callback returns a product object, it will be inserted in the db, possibly
* overwriting an existing product. The intended usage is for the callback to return a
* modified version version of the product with the same id. Anything else and you're on your own.
*/
modify_all_products: function(callback){
var transaction = this.db.transaction('products','readwrite');
var objectStore = transaction.objectStore('products');
objectStore.openCursor().onsuccess = function(event){
var cursor = event.target.result;
if(cursor){
var ret = callback(cursor.value);
if(ret === undefined || ret === null){
cursor.continue();
}else if(ret === 'break'){
return;
}else{
objectStore.put(ret);
}
}
};
},
});
window.PosDB = module.PosDB;
/* PosLS is a LocalStorage based implementation of the point of sale database,
it performs better for few products, but does not scale beyond 500 products.
*/
@@ -372,7 +93,7 @@ function openerp_pos_db(instance, module){
make_ancestors(this.root_category_id, []);
},
/* this internal method returns from disc a dictionary associating ids to the products */
_get_products: function(){
_load_products: function(){
if(this.products_cache){
return this.products_cache;
}
@@ -384,12 +105,12 @@ function openerp_pos_db(instance, module){
}
},
/* this internal method saves a dictionary associating ids to product to the disc */
_set_products: function(products){
_save_products: function(products){
localStorage[this.products] = JSON.stringify(products);
this.products_cache = products;
},
/* this internal method returns from disc a dictionary associating ids to the categories */
_get_categories: function(){
_load_categories: function(){
if(this.categories_cache){
return this.categories_cache;
}
@@ -401,13 +122,13 @@ function openerp_pos_db(instance, module){
}
},
/* this internal method saves to disc an array associating ids to the categories */
_set_categories: function(categories){
_save_categories: function(categories){
localStorage[this.categories] = JSON.stringify(categories);
this.categories_cache = categories;
},
add_products: function(products){
var stored_products = this._get_products();
var stored_categories = this._get_categories();
var stored_products = this._load_products();
var stored_categories = this._load_categories();
if(!products instanceof Array){
products = [products];
@@ -429,16 +150,13 @@ function openerp_pos_db(instance, module){
}
stored_products[product.id] = product;
}
this._set_products(stored_products);
this._set_categories(stored_categories);
this._save_products(stored_products);
this._save_categories(stored_categories);
},
/* removes all the data from the database. TODO : being able to selectively remove data */
clear: function(done_callback){
clear: function(){
localStorage.removeItem(this.products);
localStorage.removeItem(this.categories);
if(done_callback){
done_callback();
}
},
/* this internal methods returns the count of properties in an object. */
_count_props : function(obj){
@@ -450,42 +168,27 @@ function openerp_pos_db(instance, module){
}
return count;
},
get_product_count: function(result_callback){
result_callback(this._count_props(this._get_products()));
get_product_by_id: function(id){
return this._load_products()[id];
},
get_product_by_id: function(id, result_callback){
var products = this._get_products();
result_callback( products[id] );
},
get_product_by_name: function(name, result_callback){
var products = this._get_products();
for(var i in products){
if(products[i] && products[i].name === name){
result_callback(products[i]);
return;
}
}
result_callback(undefined);
},
get_product_by_ean13: function(ean13, result_callback){
var products = this._get_products();
get_product_by_ean13: function(ean13){
var products = this._load_products();
for(var i in products){
if( products[i] && products[i].ean13 === ean13){
result_callback(products[i]);
return;
return products[i];
}
}
result_callback(undefined);
return undefined;
},
get_product_by_category: function(category_id, result_callback){
var stored_categories = this._get_categories();
var stored_products = this._get_products();
get_product_by_category: function(category_id){
var stored_categories = this._load_categories();
var stored_products = this._load_products();
var product_ids = stored_categories[category_id];
var list = [];
for(var i = 0, len = Math.min(product_ids.length,this.limit); i < len; i++){
list.push(stored_products[product_ids[i]]);
}
result_callback(list);
return list;
},
/* returns as a parameter of the result_callback function a list of products with :
* - a category that is or is a child of category_id,
@@ -493,10 +196,10 @@ function openerp_pos_db(instance, module){
* If a search is started before the previous has returned, the previous search may be cancelled
* (and the corresponding result_callback never called)
*/
search_product_in_category: function(category_id, fields, query, result_callback){
search_product_in_category: function(category_id, fields, query){
var self = this;
var stored_categories = this._get_categories();
var stored_products = this._get_products();
var stored_categories = this._load_categories();
var stored_products = this._load_products();
var product_ids = stored_categories[category_id];
var list = [];
var count = 0;
@@ -521,14 +224,14 @@ function openerp_pos_db(instance, module){
}
}
}
result_callback(list);
return list;
},
// TODO move the order storage from the crappy DAO in pos_models.js
add_order: function(order,done_callback){
add_order: function(order){
},
remove_order: function(order_id, done_callback){
remove_order: function(order_id){
},
get_orders: function(result_callback){
get_orders: function(){
},
});
@@ -216,7 +216,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
self.db.add_categories(categories);
return fetch(
'product.product',
['name', 'list_price','price','pos_categ_id', 'taxes_id','image', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type'],
[['pos_categ_id','!=', false]],
{pricelist: self.get('shop').pricelist_id[0]} // context for price
);
@@ -367,23 +367,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
scan_product: function(parsed_ean){
var self = this;
var def = new $.Deferred();
this.db.get_product_by_ean13(parsed_ean.base_ean, function(product){
var selectedOrder = this.get('selectedOrder');
if(!product){
def.reject('product-not-found: '+parsed_ean.base_ean);
return;
}
if(parsed_ean.type === 'price'){
selectedOrder.addProduct(new module.Product(product), {price:parsed_ean.value});
}else if(parsed_ean.type === 'weight'){
selectedOrder.addProduct(new module.Product(product), {quantity:parsed_ean.value, merge:false});
}else{
selectedOrder.addProduct(new module.Product(product));
}
def.resolve();
});
return def;
var product = this.db.get_product_by_ean13(parsed_ean.base_ean);
var selectedOrder = this.get('selectedOrder');
if(!product){
return false;
}
if(parsed_ean.type === 'price'){
selectedOrder.addProduct(new module.Product(product), {price:parsed_ean.value});
}else if(parsed_ean.type === 'weight'){
selectedOrder.addProduct(new module.Product(product), {quantity:parsed_ean.value, merge:false});
}else{
selectedOrder.addProduct(new module.Product(product));
}
return true;
},
});
@@ -160,19 +160,17 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
// there's an error.
barcode_product_action: function(ean){
var self = this;
this.pos_widget.scan_product(ean)
.done(function(){
self.pos.proxy.scan_item_success(ean);
if(self.barcode_product_screen){
self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
}
})
.fail(function(){
self.pos.proxy.scan_item_error_unrecognized(ean);
if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
}
});
if(pos_widget.scan_product(ean)){
self.pos.proxy.scan_item_success(ean);
if(self.barcode_product_screen){
self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
}
}else{
self.pos.proxy.scan_item_error_unrecognized(ean);
if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){
self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup);
}
}
},
// what happens when a cashier id barcode is scanned.
@@ -242,7 +242,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
this.renderElement();
},
get_image_url: function() {
return 'data:image/gif;base64,'+this.model.get('image');
return 'web/binary/image?session_id='+instance.connection.session_id+'&model=product.product&field=image&id='+this.model.get('id')+'&cache=1800';
},
renderElement: function() {
this._super();
@@ -471,25 +471,20 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var self = this;
// find all products belonging to the current category
this.pos.db.get_product_by_category(this.category.id, function(products){
// product lists watch for reset events on 'products' to re-render.
// FIXME that means all productlist widget re-render... even the hidden ones !
self.pos.get('products').reset(products);
});
var products = this.pos.db.get_product_by_category(this.category.id);
self.pos.get('products').reset(products);
// filter the products according to the search string
this.$('.searchbox input').keyup(function(){
query = $(this).val().toLowerCase();
if(query){
self.pos.db.search_product_in_category(self.category.id, ['name','ean13'], query, function(products){
self.pos.get('products').reset(products);
self.$('.search-clear').fadeIn();
});
var products = self.pos.db.search_product_in_category(self.category.id, ['name','ean13'], query);
self.pos.get('products').reset(products);
self.$('.search-clear').fadeIn();
}else{
self.pos.db.get_product_by_category(self.category.id, function(products){
self.pos.get('products').reset(products);
self.$('.search-clear').fadeOut();
});
var products = self.pos.db.get_product_by_category(self.category.id);
self.pos.get('products').reset(products);
self.$('.search-clear').fadeOut();
}
});
@@ -497,11 +492,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
//reset the search when clicking on reset
this.$('.search-clear').click(function(){
self.pos.db.get_product_by_category(self.category.id, function(products){
self.pos.get('products').reset(products);
self.$('.searchbox input').val('').focus();
self.$('.search-clear').fadeOut();
});
var products = self.pos.db.get_product_by_category(self.category.id);
self.pos.get('products').reset(products);
self.$('.searchbox input').val('').focus();
self.$('.search-clear').fadeOut();
});
},
});