[WIP] point_of_sale: moved the screen related widgets from chrome.js to screens.js

This commit is contained in:
Frédéric van der Essen
2014-12-31 15:34:31 +01:00
parent 5e3600224b
commit 2f08e67e1c
2 changed files with 647 additions and 506 deletions
+53 -506
View File
@@ -4,255 +4,10 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
var QWeb = instance.web.qweb;
var _t = instance.web._t;
module.DomCache = instance.web.Class.extend({
init: function(options){
options = options || {};
this.max_size = options.max_size || 2000;
this.cache = {};
this.access_time = {};
this.size = 0;
},
cache_node: function(key,node){
var cached = this.cache[key];
this.cache[key] = node;
this.access_time[key] = new Date().getTime();
if(!cached){
this.size++;
while(this.size >= this.max_size){
var oldest_key = null;
var oldest_time = new Date().getTime();
for(var key in this.cache){
var time = this.access_time[key];
if(time <= oldest_time){
oldest_time = time;
oldest_key = key;
}
}
if(oldest_key){
delete this.cache[oldest_key];
delete this.access_time[oldest_key];
}
this.size--;
}
}
return node;
},
get_node: function(key){
var cached = this.cache[key];
if(cached){
this.access_time[key] = new Date().getTime();
}
return cached;
},
});
module.NumpadWidget = module.PosBaseWidget.extend({
template:'NumpadWidget',
init: function(parent, options) {
this._super(parent);
this.state = new module.NumpadState();
window.numpadstate = this.state;
var self = this;
},
start: function() {
this.state.bind('change:mode', this.changedMode, this);
this.changedMode();
this.$el.find('.numpad-backspace').click(_.bind(this.clickDeleteLastChar, this));
this.$el.find('.numpad-minus').click(_.bind(this.clickSwitchSign, this));
this.$el.find('.number-char').click(_.bind(this.clickAppendNewChar, this));
this.$el.find('.mode-button').click(_.bind(this.clickChangeMode, this));
},
clickDeleteLastChar: function() {
return this.state.deleteLastChar();
},
clickSwitchSign: function() {
return this.state.switchSign();
},
clickAppendNewChar: function(event) {
var newChar;
newChar = event.currentTarget.innerText || event.currentTarget.textContent;
return this.state.appendNewChar(newChar);
},
clickChangeMode: function(event) {
var newMode = event.currentTarget.attributes['data-mode'].nodeValue;
return this.state.changeMode(newMode);
},
changedMode: function() {
var mode = this.state.get('mode');
$('.selected-mode').removeClass('selected-mode');
$(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$el).addClass('selected-mode');
},
});
// The action pad contains the payment button and the customer selection button
module.ActionpadWidget = module.PosBaseWidget.extend({
template: 'ActionpadWidget',
renderElement: function() {
var self = this;
this._super();
this.$('.pay').click(function(){
self.gui.show_screen('payment');
});
this.$('.set-customer').click(function(){
self.gui.show_screen('clientlist');
});
}
});
module.OrderWidget = module.PosBaseWidget.extend({
template:'OrderWidget',
init: function(parent, options) {
var self = this;
this._super(parent,options);
this.editable = false;
this.pos.bind('change:selectedOrder', this.change_selected_order, this);
this.line_click_handler = function(event){
if(!self.editable){
return;
}
self.pos.get_order().select_orderline(this.orderline);
self.chrome.widget.numpad.state.reset();
};
this.client_change_handler = function(event){
self.update_summary();
}
if (this.pos.get_order()) {
this.bind_order_events();
}
},
enable_numpad: function(){
this.disable_numpad(); //ensure we don't register the callbacks twice
this.numpad_state = this.chrome.widget.numpad.state;
if(this.numpad_state){
this.numpad_state.reset();
this.numpad_state.bind('set_value', this.set_value, this);
}
},
disable_numpad: function(){
if(this.numpad_state){
this.numpad_state.unbind('set_value', this.set_value);
this.numpad_state.reset();
}
},
set_value: function(val) {
var order = this.pos.get_order();
if (this.editable && order.get_selected_orderline()) {
var mode = this.numpad_state.get('mode');
if( mode === 'quantity'){
order.get_selected_orderline().set_quantity(val);
}else if( mode === 'discount'){
order.get_selected_orderline().set_discount(val);
}else if( mode === 'price'){
order.get_selected_orderline().set_unit_price(val);
}
}
},
change_selected_order: function() {
if (this.pos.get_order()) {
this.bind_order_events();
this.renderElement();
}
},
orderline_add: function(){
this.numpad_state.reset();
this.renderElement('and_scroll_to_bottom');
},
orderline_remove: function(line){
this.remove_orderline(line);
this.numpad_state.reset();
this.update_summary();
},
orderline_change: function(line){
this.rerender_orderline(line);
this.update_summary();
},
bind_order_events: function() {
var order = this.pos.get_order();
order.unbind('change:client', this.client_change_handler);
order.bind('change:client', this.client_change_handler);
var lines = order.orderlines;
lines.unbind('add', this.orderline_add, this);
lines.bind('add', this.orderline_add, this);
lines.unbind('remove', this.orderline_remove, this);
lines.bind('remove', this.orderline_remove, this);
lines.unbind('change', this.orderline_change, this);
lines.bind('change', this.orderline_change, this);
},
render_orderline: function(orderline){
var el_str = openerp.qweb.render('Orderline',{widget:this, line:orderline});
var el_node = document.createElement('div');
el_node.innerHTML = _.str.trim(el_str);
el_node = el_node.childNodes[0];
el_node.orderline = orderline;
el_node.addEventListener('click',this.line_click_handler);
orderline.node = el_node;
return el_node;
},
remove_orderline: function(order_line){
if(this.pos.get_order().get_orderlines().length === 0){
this.renderElement();
}else{
order_line.node.parentNode.removeChild(order_line.node);
}
},
rerender_orderline: function(order_line){
var node = order_line.node;
var replacement_line = this.render_orderline(order_line);
node.parentNode.replaceChild(replacement_line,node);
},
// overriding the openerp framework replace method for performance reasons
replace: function($target){
this.renderElement();
var target = $target[0];
target.parentNode.replaceChild(this.el,target);
},
renderElement: function(scrollbottom){
this.chrome.widget.numpad.state.reset(); //FIXME WTF
var order = this.pos.get_order();
if (!order) {
return;
}
var orderlines = order.get_orderlines();
var el_str = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines});
var el_node = document.createElement('div');
el_node.innerHTML = _.str.trim(el_str);
el_node = el_node.childNodes[0];
var list_container = el_node.querySelector('.orderlines');
for(var i = 0, len = orderlines.length; i < len; i++){
var orderline = this.render_orderline(orderlines[i]);
list_container.appendChild(orderline);
}
if(this.el && this.el.parentNode){
this.el.parentNode.replaceChild(el_node,this.el);
}
this.el = el_node;
this.update_summary();
if(scrollbottom){
this.el.querySelector('.order-scroller').scrollTop = 100 * orderlines.length;
}
},
update_summary: function(){
var order = this.pos.get_order();
var total = order ? order.get_total_with_tax() : 0;
var taxes = order ? total - order.get_total_without_tax() : 0;
this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
},
});
/* -------- The Order Selector -------- */
// Allows the cashier to create / delete and
// switch between orders.
module.OrderSelectorWidget = module.PosBaseWidget.extend({
template: 'OrderSelectorWidget',
@@ -311,254 +66,10 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
},
});
module.ProductCategoriesWidget = module.PosBaseWidget.extend({
template: 'ProductCategoriesWidget',
init: function(parent, options){
var self = this;
this._super(parent,options);
this.product_type = options.product_type || 'all'; // 'all' | 'weightable'
this.onlyWeightable = options.onlyWeightable || false;
this.category = this.pos.root_category;
this.breadcrumb = [];
this.subcategories = [];
this.product_list_widget = options.product_list_widget || null;
this.category_cache = new module.DomCache();
this.set_category();
this.switch_category_handler = function(event){
self.set_category(self.pos.db.get_category_by_id(Number(this.dataset['categoryId'])));
self.renderElement();
};
this.clear_search_handler = function(event){
self.clear_search();
};
var search_timeout = null;
this.search_handler = function(event){
clearTimeout(search_timeout);
var query = this.value;
search_timeout = setTimeout(function(){
self.perform_search(self.category, query, event.which === 13);
},70);
};
},
// changes the category. if undefined, sets to root category
set_category : function(category){
var db = this.pos.db;
if(!category){
this.category = db.get_category_by_id(db.root_category_id);
}else{
this.category = category;
}
this.breadcrumb = [];
var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
for(var i = 1; i < ancestors_ids.length; i++){
this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
}
if(this.category.id !== db.root_category_id){
this.breadcrumb.push(this.category);
}
this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
},
get_image_url: function(category){
return window.location.origin + '/web/binary/image?model=pos.category&field=image_medium&id='+category.id;
},
render_category: function( category, with_image ){
var cached = this.category_cache.get_node(category.id);
if(!cached){
if(with_image){
var image_url = this.get_image_url(category);
var category_html = QWeb.render('CategoryButton',{
widget: this,
category: category,
image_url: this.get_image_url(category),
});
category_html = _.str.trim(category_html);
var category_node = document.createElement('div');
category_node.innerHTML = category_html;
category_node = category_node.childNodes[0];
}else{
var category_html = QWeb.render('CategorySimpleButton',{
widget: this,
category: category,
});
category_html = _.str.trim(category_html);
var category_node = document.createElement('div');
category_node.innerHTML = category_html;
category_node = category_node.childNodes[0];
}
this.category_cache.cache_node(category.id,category_node);
return category_node;
}
return cached;
},
replace: function($target){
this.renderElement();
var target = $target[0];
target.parentNode.replaceChild(this.el,target);
},
renderElement: function(){
var self = this;
var el_str = openerp.qweb.render(this.template, {widget: this});
var el_node = document.createElement('div');
el_node.innerHTML = el_str;
el_node = el_node.childNodes[1];
if(this.el && this.el.parentNode){
this.el.parentNode.replaceChild(el_node,this.el);
}
this.el = el_node;
var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons
for(var i = 0; i < this.subcategories.length; i++){
if(this.subcategories[i].image){
hasimages = true;
break;
}
}
var list_container = el_node.querySelector('.category-list');
if (list_container) {
if (!hasimages) {
list_container.classList.add('simple');
} else {
list_container.classList.remove('simple');
}
for(var i = 0, len = this.subcategories.length; i < len; i++){
list_container.appendChild(this.render_category(this.subcategories[i],hasimages));
};
}
var buttons = el_node.querySelectorAll('.js-category-switch');
for(var i = 0; i < buttons.length; i++){
buttons[i].addEventListener('click',this.switch_category_handler);
}
var products = this.pos.db.get_product_by_category(this.category.id);
this.product_list_widget.set_product_list(products);
this.el.querySelector('.searchbox input').addEventListener('keyup',this.search_handler);
this.el.querySelector('.search-clear').addEventListener('click',this.clear_search_handler);
if(this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard){
this.chrome.widget.keyboard.connect($(this.el.querySelector('.searchbox input')));
}
},
// resets the current category to the root category
reset_category: function(){
this.set_category();
this.renderElement();
},
// empties the content of the search box
clear_search: function(){
var products = this.pos.db.get_product_by_category(this.category.id);
this.product_list_widget.set_product_list(products);
var input = this.el.querySelector('.searchbox input');
input.value = '';
input.focus();
},
perform_search: function(category, query, buy_result){
if(query){
var products = this.pos.db.search_product_in_category(category.id,query)
if(buy_result && products.length === 1){
this.pos.get_order().add_product(products[0]);
this.clear_search();
}else{
this.product_list_widget.set_product_list(products);
}
}else{
var products = this.pos.db.get_product_by_category(this.category.id);
this.product_list_widget.set_product_list(products);
}
},
});
module.ProductListWidget = module.PosBaseWidget.extend({
template:'ProductListWidget',
init: function(parent, options) {
var self = this;
this._super(parent,options);
this.model = options.model;
this.productwidgets = [];
this.weight = options.weight || 0;
this.show_scale = options.show_scale || false;
this.next_screen = options.next_screen || false;
this.click_product_handler = function(event){
var product = self.pos.db.get_product_by_id(this.dataset['productId']);
options.click_product_action(product);
};
this.product_list = options.product_list || [];
this.product_cache = new module.DomCache();
},
set_product_list: function(product_list){
this.product_list = product_list;
this.renderElement();
},
get_product_image_url: function(product){
return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id;
},
replace: function($target){
this.renderElement();
var target = $target[0];
target.parentNode.replaceChild(this.el,target);
},
render_product: function(product){
var cached = this.product_cache.get_node(product.id);
if(!cached){
var image_url = this.get_product_image_url(product);
var product_html = QWeb.render('Product',{
widget: this,
product: product,
image_url: this.get_product_image_url(product),
});
var product_node = document.createElement('div');
product_node.innerHTML = product_html;
product_node = product_node.childNodes[1];
this.product_cache.cache_node(product.id,product_node);
return product_node;
}
return cached;
},
renderElement: function() {
var self = this;
// this._super()
var el_str = openerp.qweb.render(this.template, {widget: this});
var el_node = document.createElement('div');
el_node.innerHTML = el_str;
el_node = el_node.childNodes[1];
if(this.el && this.el.parentNode){
this.el.parentNode.replaceChild(el_node,this.el);
}
this.el = el_node;
var list_container = el_node.querySelector('.product-list');
for(var i = 0, len = this.product_list.length; i < len; i++){
var product_node = this.render_product(this.product_list[i]);
product_node.addEventListener('click',this.click_product_handler);
list_container.appendChild(product_node);
};
},
});
/* ------- The User Name Widget ------- */
// Displays the current cashier's name and allows
// to switch between cashiers.
module.UsernameWidget = module.PosBaseWidget.extend({
template: 'UsernameWidget',
@@ -595,6 +106,12 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
},
});
/* -------- The Header Button --------- */
// Used to quickly add buttons with simple
// labels and actions to the point of sale
// header.
module.HeaderButtonWidget = module.PosBaseWidget.extend({
template: 'HeaderButtonWidget',
init: function(parent, options){
@@ -616,8 +133,14 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
hide: function(){ this.$el.addClass('oe_hidden'); },
});
// The debug widget lets the user control and monitor the hardware and software status
// without the use of the proxy
/* --------- The Debug Widget --------- */
// The debug widget lets the user control
// and monitor the hardware and software status
// without the use of the proxy, or to access
// the raw locally stored db values, useful
// for debugging
module.DebugWidget = module.PosBaseWidget.extend({
template: "DebugWidget",
eans:{
@@ -760,7 +283,10 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
},
});
// ---------- Main Point of Sale Widget ----------
/* --------- The Status Widget -------- */
// Base class for widgets that want to display
// status in the point of sale header.
module.StatusWidget = module.PosBaseWidget.extend({
status: ['connected','connecting','disconnected','warning'],
@@ -779,7 +305,11 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
},
});
// this is used to notify the user that data is being synchronized on the network
/* ------- Synch. Notifications ------- */
// Displays if there are orders that could
// not be submitted, and how many.
module.SynchNotificationWidget = module.StatusWidget.extend({
template: 'SynchNotificationWidget',
start: function(){
@@ -793,7 +323,11 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
},
});
// this is used to notify the user if the pos is connected to the proxy
/* --------- The Proxy Status --------- */
// Displays the status of the hardware proxy
// (connected, disconnected, errors ... )
module.ProxyStatusWidget = module.StatusWidget.extend({
template: 'ProxyStatusWidget',
set_smart_status: function(status){
@@ -846,14 +380,27 @@ openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //mo
});
// The Chrome is the main widget that contains all other widgets in the PointOfSale.
/*--------------------------------------*\
| THE CHROME |
\*======================================*/
// Displays the status of the hardware proxy
// The Chrome is the main widget that contains
// all other widgets in the PointOfSale.
//
// It is the first object instanciated and the
// starting point of the point of sale code.
//
// It is mainly composed of :
// - a header, containing the list of orders
// - a leftpane, containing the list of bought products (orderlines)
// - a rightpane, containing the screens (see pos_screens.js)
// - a leftpane, containing the list of bought
// products (orderlines)
// - a rightpane, containing the screens
// (see pos_screens.js)
// - popups
// - an onscreen keyboard
// a gui which controls the switching between screens and the showing/closing of popups
// - .gui which controls the switching between
// screens and the showing/closing of popups
module.Chrome = module.PosBaseWidget.extend({
template: 'Chrome',
@@ -34,6 +34,13 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
var round_pr = instance.web.round_precision
/*--------------------------------------*\
| THE SCREEN WIDGET |
\*======================================*/
// The screen widget is the base class inherited
// by all screens.
module.ScreenWidget = module.PosBaseWidget.extend({
show_numpad: true,
@@ -158,6 +165,69 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
});
/*--------------------------------------*\
| THE DOM CACHE |
\*======================================*/
// The Dom Cache is used by various screens to improve
// their performances when displaying many time the
// same piece of DOM.
//
// It is a simple map from string 'keys' to DOM Nodes.
//
// The cache empties itself based on usage frequency
// stats, so you may not always get back what
// you put in.
module.DomCache = instance.web.Class.extend({
init: function(options){
options = options || {};
this.max_size = options.max_size || 2000;
this.cache = {};
this.access_time = {};
this.size = 0;
},
cache_node: function(key,node){
var cached = this.cache[key];
this.cache[key] = node;
this.access_time[key] = new Date().getTime();
if(!cached){
this.size++;
while(this.size >= this.max_size){
var oldest_key = null;
var oldest_time = new Date().getTime();
for(var key in this.cache){
var time = this.access_time[key];
if(time <= oldest_time){
oldest_time = time;
oldest_key = key;
}
}
if(oldest_key){
delete this.cache[oldest_key];
delete this.access_time[oldest_key];
}
this.size--;
}
}
return node;
},
get_node: function(key){
var cached = this.cache[key];
if(cached){
this.access_time[key] = new Date().getTime();
}
return cached;
},
});
/*--------------------------------------*\
| THE SCALE SCREEN |
\*======================================*/
// The scale screen displays the weight of
// a product on the electronic scale.
module.ScaleScreenWidget = module.ScreenWidget.extend({
template:'ScaleScreenWidget',
@@ -249,6 +319,505 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
});
/*--------------------------------------*\
| THE PRODUCT SCREEN |
\*======================================*/
// The product screen contains the list of products,
// The category selector and the order display.
// It is the default screen for orders and the
// startup screen for shops.
//
// There product screens uses many sub-widgets,
// the code follows.
/* ------------ The Numpad ------------ */
// The numpad that edits the order lines.
module.NumpadWidget = module.PosBaseWidget.extend({
template:'NumpadWidget',
init: function(parent, options) {
this._super(parent);
this.state = new module.NumpadState();
window.numpadstate = this.state;
var self = this;
},
start: function() {
this.state.bind('change:mode', this.changedMode, this);
this.changedMode();
this.$el.find('.numpad-backspace').click(_.bind(this.clickDeleteLastChar, this));
this.$el.find('.numpad-minus').click(_.bind(this.clickSwitchSign, this));
this.$el.find('.number-char').click(_.bind(this.clickAppendNewChar, this));
this.$el.find('.mode-button').click(_.bind(this.clickChangeMode, this));
},
clickDeleteLastChar: function() {
return this.state.deleteLastChar();
},
clickSwitchSign: function() {
return this.state.switchSign();
},
clickAppendNewChar: function(event) {
var newChar;
newChar = event.currentTarget.innerText || event.currentTarget.textContent;
return this.state.appendNewChar(newChar);
},
clickChangeMode: function(event) {
var newMode = event.currentTarget.attributes['data-mode'].nodeValue;
return this.state.changeMode(newMode);
},
changedMode: function() {
var mode = this.state.get('mode');
$('.selected-mode').removeClass('selected-mode');
$(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$el).addClass('selected-mode');
},
});
/* ---------- The Action Pad ---------- */
// The action pad contains the payment button and the
// customer selection button
module.ActionpadWidget = module.PosBaseWidget.extend({
template: 'ActionpadWidget',
renderElement: function() {
var self = this;
this._super();
this.$('.pay').click(function(){
self.gui.show_screen('payment');
});
this.$('.set-customer').click(function(){
self.gui.show_screen('clientlist');
});
}
});
/* --------- The Order Widget --------- */
// Displays the current Order.
module.OrderWidget = module.PosBaseWidget.extend({
template:'OrderWidget',
init: function(parent, options) {
var self = this;
this._super(parent,options);
this.editable = false;
this.pos.bind('change:selectedOrder', this.change_selected_order, this);
this.line_click_handler = function(event){
if(!self.editable){
return;
}
self.pos.get_order().select_orderline(this.orderline);
self.chrome.widget.numpad.state.reset();
};
this.client_change_handler = function(event){
self.update_summary();
}
if (this.pos.get_order()) {
this.bind_order_events();
}
},
enable_numpad: function(){
this.disable_numpad(); //ensure we don't register the callbacks twice
this.numpad_state = this.chrome.widget.numpad.state;
if(this.numpad_state){
this.numpad_state.reset();
this.numpad_state.bind('set_value', this.set_value, this);
}
},
disable_numpad: function(){
if(this.numpad_state){
this.numpad_state.unbind('set_value', this.set_value);
this.numpad_state.reset();
}
},
set_value: function(val) {
var order = this.pos.get_order();
if (this.editable && order.get_selected_orderline()) {
var mode = this.numpad_state.get('mode');
if( mode === 'quantity'){
order.get_selected_orderline().set_quantity(val);
}else if( mode === 'discount'){
order.get_selected_orderline().set_discount(val);
}else if( mode === 'price'){
order.get_selected_orderline().set_unit_price(val);
}
}
},
change_selected_order: function() {
if (this.pos.get_order()) {
this.bind_order_events();
this.renderElement();
}
},
orderline_add: function(){
this.numpad_state.reset();
this.renderElement('and_scroll_to_bottom');
},
orderline_remove: function(line){
this.remove_orderline(line);
this.numpad_state.reset();
this.update_summary();
},
orderline_change: function(line){
this.rerender_orderline(line);
this.update_summary();
},
bind_order_events: function() {
var order = this.pos.get_order();
order.unbind('change:client', this.client_change_handler);
order.bind('change:client', this.client_change_handler);
var lines = order.orderlines;
lines.unbind('add', this.orderline_add, this);
lines.bind('add', this.orderline_add, this);
lines.unbind('remove', this.orderline_remove, this);
lines.bind('remove', this.orderline_remove, this);
lines.unbind('change', this.orderline_change, this);
lines.bind('change', this.orderline_change, this);
},
render_orderline: function(orderline){
var el_str = openerp.qweb.render('Orderline',{widget:this, line:orderline});
var el_node = document.createElement('div');
el_node.innerHTML = _.str.trim(el_str);
el_node = el_node.childNodes[0];
el_node.orderline = orderline;
el_node.addEventListener('click',this.line_click_handler);
orderline.node = el_node;
return el_node;
},
remove_orderline: function(order_line){
if(this.pos.get_order().get_orderlines().length === 0){
this.renderElement();
}else{
order_line.node.parentNode.removeChild(order_line.node);
}
},
rerender_orderline: function(order_line){
var node = order_line.node;
var replacement_line = this.render_orderline(order_line);
node.parentNode.replaceChild(replacement_line,node);
},
// overriding the openerp framework replace method for performance reasons
replace: function($target){
this.renderElement();
var target = $target[0];
target.parentNode.replaceChild(this.el,target);
},
renderElement: function(scrollbottom){
this.chrome.widget.numpad.state.reset(); //FIXME WTF
var order = this.pos.get_order();
if (!order) {
return;
}
var orderlines = order.get_orderlines();
var el_str = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines});
var el_node = document.createElement('div');
el_node.innerHTML = _.str.trim(el_str);
el_node = el_node.childNodes[0];
var list_container = el_node.querySelector('.orderlines');
for(var i = 0, len = orderlines.length; i < len; i++){
var orderline = this.render_orderline(orderlines[i]);
list_container.appendChild(orderline);
}
if(this.el && this.el.parentNode){
this.el.parentNode.replaceChild(el_node,this.el);
}
this.el = el_node;
this.update_summary();
if(scrollbottom){
this.el.querySelector('.order-scroller').scrollTop = 100 * orderlines.length;
}
},
update_summary: function(){
var order = this.pos.get_order();
var total = order ? order.get_total_with_tax() : 0;
var taxes = order ? total - order.get_total_without_tax() : 0;
this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
},
});
/* ------ The Product Categories ------ */
// Display and navigate the product categories.
// Also handles searches.
// - set_category() to change the displayed category
// - reset_category() to go to the root category
// - perform_search() to search for products
// - clear_search() does what it says.
module.ProductCategoriesWidget = module.PosBaseWidget.extend({
template: 'ProductCategoriesWidget',
init: function(parent, options){
var self = this;
this._super(parent,options);
this.product_type = options.product_type || 'all'; // 'all' | 'weightable'
this.onlyWeightable = options.onlyWeightable || false;
this.category = this.pos.root_category;
this.breadcrumb = [];
this.subcategories = [];
this.product_list_widget = options.product_list_widget || null;
this.category_cache = new module.DomCache();
this.set_category();
this.switch_category_handler = function(event){
self.set_category(self.pos.db.get_category_by_id(Number(this.dataset['categoryId'])));
self.renderElement();
};
this.clear_search_handler = function(event){
self.clear_search();
};
var search_timeout = null;
this.search_handler = function(event){
clearTimeout(search_timeout);
var query = this.value;
search_timeout = setTimeout(function(){
self.perform_search(self.category, query, event.which === 13);
},70);
};
},
// changes the category. if undefined, sets to root category
set_category : function(category){
var db = this.pos.db;
if(!category){
this.category = db.get_category_by_id(db.root_category_id);
}else{
this.category = category;
}
this.breadcrumb = [];
var ancestors_ids = db.get_category_ancestors_ids(this.category.id);
for(var i = 1; i < ancestors_ids.length; i++){
this.breadcrumb.push(db.get_category_by_id(ancestors_ids[i]));
}
if(this.category.id !== db.root_category_id){
this.breadcrumb.push(this.category);
}
this.subcategories = db.get_category_by_id(db.get_category_childs_ids(this.category.id));
},
get_image_url: function(category){
return window.location.origin + '/web/binary/image?model=pos.category&field=image_medium&id='+category.id;
},
render_category: function( category, with_image ){
var cached = this.category_cache.get_node(category.id);
if(!cached){
if(with_image){
var image_url = this.get_image_url(category);
var category_html = QWeb.render('CategoryButton',{
widget: this,
category: category,
image_url: this.get_image_url(category),
});
category_html = _.str.trim(category_html);
var category_node = document.createElement('div');
category_node.innerHTML = category_html;
category_node = category_node.childNodes[0];
}else{
var category_html = QWeb.render('CategorySimpleButton',{
widget: this,
category: category,
});
category_html = _.str.trim(category_html);
var category_node = document.createElement('div');
category_node.innerHTML = category_html;
category_node = category_node.childNodes[0];
}
this.category_cache.cache_node(category.id,category_node);
return category_node;
}
return cached;
},
replace: function($target){
this.renderElement();
var target = $target[0];
target.parentNode.replaceChild(this.el,target);
},
renderElement: function(){
var self = this;
var el_str = openerp.qweb.render(this.template, {widget: this});
var el_node = document.createElement('div');
el_node.innerHTML = el_str;
el_node = el_node.childNodes[1];
if(this.el && this.el.parentNode){
this.el.parentNode.replaceChild(el_node,this.el);
}
this.el = el_node;
var hasimages = false; //if none of the subcategories have images, we don't display buttons with icons
for(var i = 0; i < this.subcategories.length; i++){
if(this.subcategories[i].image){
hasimages = true;
break;
}
}
var list_container = el_node.querySelector('.category-list');
if (list_container) {
if (!hasimages) {
list_container.classList.add('simple');
} else {
list_container.classList.remove('simple');
}
for(var i = 0, len = this.subcategories.length; i < len; i++){
list_container.appendChild(this.render_category(this.subcategories[i],hasimages));
};
}
var buttons = el_node.querySelectorAll('.js-category-switch');
for(var i = 0; i < buttons.length; i++){
buttons[i].addEventListener('click',this.switch_category_handler);
}
var products = this.pos.db.get_product_by_category(this.category.id);
this.product_list_widget.set_product_list(products); // FIXME: this should be moved elsewhere ...
this.el.querySelector('.searchbox input').addEventListener('keyup',this.search_handler);
this.el.querySelector('.search-clear').addEventListener('click',this.clear_search_handler);
if(this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard){
this.chrome.widget.keyboard.connect($(this.el.querySelector('.searchbox input')));
}
},
// resets the current category to the root category
reset_category: function(){
this.set_category();
this.renderElement();
},
// empties the content of the search box
clear_search: function(){
var products = this.pos.db.get_product_by_category(this.category.id);
this.product_list_widget.set_product_list(products);
var input = this.el.querySelector('.searchbox input');
input.value = '';
input.focus();
},
perform_search: function(category, query, buy_result){
if(query){
var products = this.pos.db.search_product_in_category(category.id,query)
if(buy_result && products.length === 1){
this.pos.get_order().add_product(products[0]);
this.clear_search();
}else{
this.product_list_widget.set_product_list(products);
}
}else{
var products = this.pos.db.get_product_by_category(this.category.id);
this.product_list_widget.set_product_list(products);
}
},
});
/* --------- The Product List --------- */
// Display the list of products.
// - change the list with .set_product_list()
// - click_product_action(), passed as an option, tells
// what to do when a product is clicked.
module.ProductListWidget = module.PosBaseWidget.extend({
template:'ProductListWidget',
init: function(parent, options) {
var self = this;
this._super(parent,options);
this.model = options.model;
this.productwidgets = [];
this.weight = options.weight || 0;
this.show_scale = options.show_scale || false;
this.next_screen = options.next_screen || false;
this.click_product_handler = function(event){
var product = self.pos.db.get_product_by_id(this.dataset['productId']);
options.click_product_action(product);
};
this.product_list = options.product_list || [];
this.product_cache = new module.DomCache();
},
set_product_list: function(product_list){
this.product_list = product_list;
this.renderElement();
},
get_product_image_url: function(product){
return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id;
},
replace: function($target){
this.renderElement();
var target = $target[0];
target.parentNode.replaceChild(this.el,target);
},
render_product: function(product){
var cached = this.product_cache.get_node(product.id);
if(!cached){
var image_url = this.get_product_image_url(product);
var product_html = QWeb.render('Product',{
widget: this,
product: product,
image_url: this.get_product_image_url(product),
});
var product_node = document.createElement('div');
product_node.innerHTML = product_html;
product_node = product_node.childNodes[1];
this.product_cache.cache_node(product.id,product_node);
return product_node;
}
return cached;
},
renderElement: function() {
var self = this;
// this._super()
var el_str = openerp.qweb.render(this.template, {widget: this});
var el_node = document.createElement('div');
el_node.innerHTML = el_str;
el_node = el_node.childNodes[1];
if(this.el && this.el.parentNode){
this.el.parentNode.replaceChild(el_node,this.el);
}
this.el = el_node;
var list_container = el_node.querySelector('.product-list');
for(var i = 0, len = this.product_list.length; i < len; i++){
var product_node = this.render_product(this.product_list[i]);
product_node.addEventListener('click',this.click_product_handler);
list_container.appendChild(product_node);
};
},
});
/* -------- The Product Screen -------- */
module.ProductScreenWidget = module.ScreenWidget.extend({
template:'ProductScreenWidget',
@@ -290,6 +859,14 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
});
/*--------------------------------------*\
| THE CLIENT LIST |
\*======================================*/
// The clientlist displays the list of customer,
// and allows the cashier to create, edit and assign
// customers.
module.ClientListScreenWidget = module.ScreenWidget.extend({
template: 'ClientListScreenWidget',
@@ -669,6 +1246,16 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
});
/*--------------------------------------*\
| THE RECEIPT SCREEN |
\*======================================*/
// The receipt screen displays the order's
// receipt and allows it to be printed in a web browser.
// The receipt screen is not shown if the point of sale
// is set up to print with the proxy. Altough it could
// be useful to do so...
module.ReceiptScreenWidget = module.ScreenWidget.extend({
template: 'ReceiptScreenWidget',
show_numpad: false,
@@ -744,6 +1331,13 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
});
/*--------------------------------------*\
| THE PAYMENT SCREEN |
\*======================================*/
// The Payment Screen handles the payments, and
// it is unfortunately quite complicated.
module.PaymentScreenWidget = module.ScreenWidget.extend({
template: 'PaymentScreenWidget',
back_screen: 'product',