diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 49b31d9181d..8fc53f5c1a3 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -44,6 +44,7 @@ html {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
font-family: sans-serif;
}
+
table {
border-spacing: 0px;
border-collapse: collapse;
@@ -501,7 +502,7 @@ td {
/* ********* The leftpane contains the order, numpad and paypad ********* */
-.pos .pos-leftpane {
+.pos .leftpane {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
@@ -513,7 +514,7 @@ td {
bottom:0;
border-right: solid 1px #CECBCB;
}
-.pos .pos-leftpane .pads {
+.pos .leftpane .pads {
background: #F0EEEE;
white-space: nowrap;
}
@@ -640,7 +641,6 @@ td {
.pos .rightpane {
position: absolute;
top: 0;
- /*bottom: 105px;*/
bottom:0;
left: 440px;
right: 0;
@@ -1222,6 +1222,7 @@ td {
}
body {
margin: 0;
+ color: white !important;
}
.oe_leftbar,
.pos .pos-topheader,
@@ -1861,12 +1862,12 @@ td {
box-shadow: 0px 10px 20px rgba(0,0,0,0.4);
z-index:1200;
}
-.pos .popup .comment{
+.pos .popup .body {
font-weight: normal;
font-size: 18px;
margin: 0px 16px 16px 16px;
}
-.pos .popup .comment.traceback {
+.pos .popup .body.traceback {
height: 264px;
overflow: auto;
font-size: 14px;
@@ -1876,17 +1877,6 @@ td {
-moz-user-select: text;
user-select: text;
}
-.pos .popup .comment.traceback {
- height: 264px;
- overflow: auto;
- font-size: 12px;
- text-align: left;
- white-space: pre-wrap;
- font-family: 'Inconsolata';
- -webkit-user-select: text;
- -moz-user-select: text;
- user-select: text;
-}
.pos .popup .footer{
position:absolute;
bottom:0;
@@ -1964,7 +1954,7 @@ td {
outline: none;
box-shadow: 0px 0px 0px 3px #6EC89B;
}
-.pos .popup.popup-selection .message {
+.pos .popup.popup-selection .title {
margin-top: 0;
}
.pos .popup.popup-selection .selection {
@@ -1986,12 +1976,23 @@ td {
.pos .popup.popup-selection .selection-item:nth-child(even) {
background: rgb(247,247,247);
}
+.pos .popup.popup-number {
+ width: 300px;
+ margin-left: -150px;
+}
+.pos .popup-number .footer{
+ text-align: center;
+}
+.pos .popup-number .footer .button{
+ float: none;
+ display: inline-block;
+}
.pos .popup-numpad {
margin: 12px auto;
text-align: left;
- width: 244px;
+ width: 250px;
}
-.pos .popup-number .message {
+.pos .popup-number .title {
margin-top: 0;
}
.pos .popup-numpad .input-button,
@@ -2013,20 +2014,27 @@ td {
}
.pos .popup.popup-password {
- width: 238px;
- margin-left: -119px;
+ width: 250px;
+ margin-left: -125;
}
.pos .popup-password .mode-button.add,
.pos .popup-password .input-button.dot {
display: none;
}
.pos .popup-password .popup-numpad {
- width: 182px;
+ width: 190px;
}
.pos .popup-password .popup-input {
width: 70%;
-webkit-text-security: disc;
}
+.pos .popup-password .footer{
+ text-align: center;
+}
+.pos .popup-password .footer .button{
+ float: none;
+ display: inline-block;
+}
/* ********* The Webkit Scrollbar ********* */
diff --git a/addons/point_of_sale/static/src/img/bancontact.png b/addons/point_of_sale/static/src/img/bancontact.png
deleted file mode 100644
index bd4f63dbb5a..00000000000
Binary files a/addons/point_of_sale/static/src/img/bancontact.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/barcode.png b/addons/point_of_sale/static/src/img/barcode.png
deleted file mode 100644
index fc780b9346b..00000000000
Binary files a/addons/point_of_sale/static/src/img/barcode.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/bg.png b/addons/point_of_sale/static/src/img/bg.png
deleted file mode 100644
index d54b6302d5a..00000000000
Binary files a/addons/point_of_sale/static/src/img/bg.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/grid-purple.png b/addons/point_of_sale/static/src/img/grid-purple.png
deleted file mode 100644
index 2f0f4331e34..00000000000
Binary files a/addons/point_of_sale/static/src/img/grid-purple.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/grid.png b/addons/point_of_sale/static/src/img/grid.png
deleted file mode 100644
index 35dbab00913..00000000000
Binary files a/addons/point_of_sale/static/src/img/grid.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/headerbackground.jpg b/addons/point_of_sale/static/src/img/headerbackground.jpg
deleted file mode 100644
index cec8fa7ccff..00000000000
Binary files a/addons/point_of_sale/static/src/img/headerbackground.jpg and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/bancontact.svg b/addons/point_of_sale/static/src/img/icons/bancontact.svg
deleted file mode 100644
index 70604078014..00000000000
--- a/addons/point_of_sale/static/src/img/icons/bancontact.svg
+++ /dev/null
@@ -1,1114 +0,0 @@
-
-
\ No newline at end of file
diff --git a/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png b/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png
deleted file mode 100644
index 1a57c9e0696..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/go-next.png b/addons/point_of_sale/static/src/img/icons/png48/go-next.png
deleted file mode 100644
index bcd343dde94..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/go-next.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/go-previous.png b/addons/point_of_sale/static/src/img/icons/png48/go-previous.png
deleted file mode 100644
index 0a084ad66eb..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/go-previous.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/help.png b/addons/point_of_sale/static/src/img/icons/png48/help.png
deleted file mode 100644
index 8c56bd4f962..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/help.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/invoice.png b/addons/point_of_sale/static/src/img/icons/png48/invoice.png
deleted file mode 100644
index e65d2ec5a67..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/invoice.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/printer.png b/addons/point_of_sale/static/src/img/icons/png48/printer.png
deleted file mode 100644
index 9747f37ddcc..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/printer.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/scale.png b/addons/point_of_sale/static/src/img/icons/png48/scale.png
deleted file mode 100644
index 3070c56ce1c..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/scale.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/shut-down.png b/addons/point_of_sale/static/src/img/icons/png48/shut-down.png
deleted file mode 100644
index 8478ab19686..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/shut-down.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/system-log-out.png b/addons/point_of_sale/static/src/img/icons/png48/system-log-out.png
deleted file mode 100644
index edd6e3446a0..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/system-log-out.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/png48/validate.png b/addons/point_of_sale/static/src/img/icons/png48/validate.png
deleted file mode 100644
index 75372c89533..00000000000
Binary files a/addons/point_of_sale/static/src/img/icons/png48/validate.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/icons/printer.svg b/addons/point_of_sale/static/src/img/icons/printer.svg
deleted file mode 100644
index 4c702dc48e2..00000000000
--- a/addons/point_of_sale/static/src/img/icons/printer.svg
+++ /dev/null
@@ -1,502 +0,0 @@
-
-
-
diff --git a/addons/point_of_sale/static/src/img/icons/scale.svg b/addons/point_of_sale/static/src/img/icons/scale.svg
deleted file mode 100644
index a04a62adeba..00000000000
--- a/addons/point_of_sale/static/src/img/icons/scale.svg
+++ /dev/null
@@ -1,797 +0,0 @@
-
-
\ No newline at end of file
diff --git a/addons/point_of_sale/static/src/img/icons/scan.svg b/addons/point_of_sale/static/src/img/icons/scan.svg
deleted file mode 100644
index 4d753acf638..00000000000
--- a/addons/point_of_sale/static/src/img/icons/scan.svg
+++ /dev/null
@@ -1,984 +0,0 @@
-
-
\ No newline at end of file
diff --git a/addons/point_of_sale/static/src/img/icons/validate.svg b/addons/point_of_sale/static/src/img/icons/validate.svg
deleted file mode 100644
index be4c67536ad..00000000000
--- a/addons/point_of_sale/static/src/img/icons/validate.svg
+++ /dev/null
@@ -1,201 +0,0 @@
-
-
-
-
diff --git a/addons/point_of_sale/static/src/img/minus.png b/addons/point_of_sale/static/src/img/minus.png
deleted file mode 100644
index 507f20384c6..00000000000
Binary files a/addons/point_of_sale/static/src/img/minus.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/open-cashbox.png b/addons/point_of_sale/static/src/img/open-cashbox.png
deleted file mode 100644
index 1e07b0a9282..00000000000
Binary files a/addons/point_of_sale/static/src/img/open-cashbox.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/plus.png b/addons/point_of_sale/static/src/img/plus.png
deleted file mode 100644
index 041a9d46faf..00000000000
Binary files a/addons/point_of_sale/static/src/img/plus.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/scale.png b/addons/point_of_sale/static/src/img/scale.png
deleted file mode 100644
index 8f66d1f9ead..00000000000
Binary files a/addons/point_of_sale/static/src/img/scale.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/scan.png b/addons/point_of_sale/static/src/img/scan.png
deleted file mode 100644
index c0bc93d7ca1..00000000000
Binary files a/addons/point_of_sale/static/src/img/scan.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/scroll-down.png b/addons/point_of_sale/static/src/img/scroll-down.png
deleted file mode 100644
index 5fd07589157..00000000000
Binary files a/addons/point_of_sale/static/src/img/scroll-down.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/scroll-up.png b/addons/point_of_sale/static/src/img/scroll-up.png
deleted file mode 100644
index b34a90014a2..00000000000
Binary files a/addons/point_of_sale/static/src/img/scroll-up.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/search_reset.gif b/addons/point_of_sale/static/src/img/search_reset.gif
deleted file mode 100644
index 9d4253e018b..00000000000
Binary files a/addons/point_of_sale/static/src/img/search_reset.gif and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/steps-arrow.png b/addons/point_of_sale/static/src/img/steps-arrow.png
deleted file mode 100644
index fe86913d78e..00000000000
Binary files a/addons/point_of_sale/static/src/img/steps-arrow.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/steps-bg.png b/addons/point_of_sale/static/src/img/steps-bg.png
deleted file mode 100644
index cfe810a77a4..00000000000
Binary files a/addons/point_of_sale/static/src/img/steps-bg.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/img/validate-icon.png b/addons/point_of_sale/static/src/img/validate-icon.png
deleted file mode 100644
index a7d02604e43..00000000000
Binary files a/addons/point_of_sale/static/src/img/validate-icon.png and /dev/null differ
diff --git a/addons/point_of_sale/static/src/js/chrome.js b/addons/point_of_sale/static/src/js/chrome.js
new file mode 100644
index 00000000000..ebfb0e8a84c
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/chrome.js
@@ -0,0 +1,634 @@
+openerp.point_of_sale.load_chrome = function load_chrome(instance, module){ //module is instance.point_of_sale
+ "use strict";
+
+ var QWeb = instance.web.qweb;
+ var _t = instance.web._t;
+
+ /* -------- The Order Selector -------- */
+
+ // Allows the cashier to create / delete and
+ // switch between orders.
+
+ module.OrderSelectorWidget = module.PosBaseWidget.extend({
+ template: 'OrderSelectorWidget',
+ init: function(parent, options) {
+ this._super(parent, options);
+ this.pos.get('orders').bind('add remove change',this.renderElement,this);
+ this.pos.bind('change:selectedOrder',this.renderElement,this);
+ },
+ get_order_by_uid: function(uid) {
+ var orders = this.pos.get_order_list();
+ for (var i = 0; i < orders.length; i++) {
+ if (orders[i].uid === uid) {
+ return orders[i];
+ }
+ }
+ return undefined;
+ },
+ order_click_handler: function(event,$el) {
+ var order = this.get_order_by_uid($el.data('uid'));
+ if (order) {
+ this.pos.set_order(order);
+ }
+ },
+ neworder_click_handler: function(event, $el) {
+ this.pos.add_new_order();
+ },
+ deleteorder_click_handler: function(event, $el) {
+ var self = this;
+ var order = this.pos.get_order();
+ if (!order) {
+ return;
+ } else if ( !order.is_empty() ){
+ this.gui.show_popup('confirm',{
+ 'title': _t('Destroy Current Order ?'),
+ 'body': _t('You will lose any data associated with the current order'),
+ confirm: function(){
+ self.pos.delete_current_order();
+ },
+ });
+ } else {
+ this.pos.delete_current_order();
+ }
+ },
+ renderElement: function(){
+ var self = this;
+ this._super();
+ this.$('.order-button.select-order').click(function(event){
+ self.order_click_handler(event,$(this));
+ });
+ this.$('.neworder-button').click(function(event){
+ self.neworder_click_handler(event,$(this));
+ });
+ this.$('.deleteorder-button').click(function(event){
+ self.deleteorder_click_handler(event,$(this));
+ });
+ },
+ });
+
+ /* ------- The User Name Widget ------- */
+
+ // Displays the current cashier's name and allows
+ // to switch between cashiers.
+
+ module.UsernameWidget = module.PosBaseWidget.extend({
+ template: 'UsernameWidget',
+ init: function(parent, options){
+ var options = options || {};
+ this._super(parent,options);
+ },
+ renderElement: function(){
+ var self = this;
+ this._super();
+
+ this.$el.click(function(){
+ self.click_username();
+ });
+ },
+ click_username: function(){
+ var self = this;
+ this.gui.select_user({
+ 'security': true,
+ 'current_user': this.pos.get_cashier(),
+ 'title': _t('Change Cashier'),
+ }).then(function(user){
+ self.pos.set_cashier(user);
+ self.renderElement();
+ });
+ },
+ get_name: function(){
+ var user = this.pos.cashier || this.pos.user;
+ if(user){
+ return user.name;
+ }else{
+ return "";
+ }
+ },
+ });
+
+ /* -------- 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){
+ options = options || {};
+ this._super(parent, options);
+ this.action = options.action;
+ this.label = options.label;
+ },
+ renderElement: function(){
+ var self = this;
+ this._super();
+ if(this.action){
+ this.$el.click(function(){
+ self.action();
+ });
+ }
+ },
+ show: function(){ this.$el.removeClass('oe_hidden'); },
+ hide: function(){ this.$el.addClass('oe_hidden'); },
+ });
+
+ /* --------- 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:{
+ admin_badge: '0410100000006',
+ client_badge: '0420200000004',
+ invalid_ean: '1232456',
+ soda_33cl: '5449000000996',
+ oranges_kg: '2100002031410',
+ lemon_price: '2301000001560',
+ unknown_product: '9900000000004',
+ },
+ events:[
+ 'open_cashbox',
+ 'print_receipt',
+ 'scale_read',
+ ],
+ minimized: false,
+ init: function(parent,options){
+ this._super(parent,options);
+ var self = this;
+
+ this.minimized = false;
+
+ // for dragging the debug widget around
+ this.dragging = false;
+ this.dragpos = {x:0, y:0};
+
+ function eventpos(event){
+ if(event.touches && event.touches[0]){
+ return {x: event.touches[0].screenX, y: event.touches[0].screenY};
+ }else{
+ return {x: event.screenX, y: event.screenY};
+ }
+ }
+
+ this.dragend_handler = function(event){
+ self.dragging = false;
+ };
+ this.dragstart_handler = function(event){
+ self.dragging = true;
+ self.dragpos = eventpos(event);
+ };
+ this.dragmove_handler = function(event){
+ if(self.dragging){
+ var top = this.offsetTop;
+ var left = this.offsetLeft;
+ var pos = eventpos(event);
+ var dx = pos.x - self.dragpos.x;
+ var dy = pos.y - self.dragpos.y;
+
+ self.dragpos = pos;
+
+ this.style.right = 'auto';
+ this.style.bottom = 'auto';
+ this.style.left = left + dx + 'px';
+ this.style.top = top + dy + 'px';
+ }
+ event.preventDefault();
+ event.stopPropagation();
+ };
+ },
+ start: function(){
+ var self = this;
+
+ this.el.addEventListener('mouseleave', this.dragend_handler);
+ this.el.addEventListener('mouseup', this.dragend_handler);
+ this.el.addEventListener('touchend', this.dragend_handler);
+ this.el.addEventListener('touchcancel',this.dragend_handler);
+ this.el.addEventListener('mousedown', this.dragstart_handler);
+ this.el.addEventListener('touchstart', this.dragstart_handler);
+ this.el.addEventListener('mousemove', this.dragmove_handler);
+ this.el.addEventListener('touchmove', this.dragmove_handler);
+
+ this.$('.toggle').click(function(){
+ var content = self.$('.content');
+ var bg = self.$el;
+ if(!self.minimized){
+ content.animate({'height':'0'},200);
+ }else{
+ content.css({'height':'auto'});
+ }
+ self.minimized = !self.minimized;
+ });
+ this.$('.button.set_weight').click(function(){
+ var kg = Number(self.$('input.weight').val());
+ if(!isNaN(kg)){
+ self.pos.proxy.debug_set_weight(kg);
+ }
+ });
+ this.$('.button.reset_weight').click(function(){
+ self.$('input.weight').val('');
+ self.pos.proxy.debug_reset_weight();
+ });
+ this.$('.button.custom_ean').click(function(){
+ var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0');
+ self.$('input.ean').val(ean);
+ self.pos.barcode_reader.scan(ean);
+ });
+ this.$('.button.reference').click(function(){
+ self.pos.barcode_reader.scan(self.$('input.ean').val());
+ });
+ this.$('.button.show_orders').click(function(){
+ self.gui.show_popup('unsent-orders');
+ });
+ this.$('.button.delete_orders').click(function(){
+ self.gui.show_popup('confirm',{
+ 'title': _t('Delete Unsent Orders ?'),
+ 'body': _t('This operation will permanently destroy all unsent orders from the local storage. You will lose all the data. This operation cannot be undone.'),
+ confirm: function(){
+ self.pos.db.remove_all_orders();
+ self.pos.set({synch: { state:'connected', pending: 0 }});
+ },
+ });
+ });
+ this.$('.button.show_unpaid_orders').click(function(){
+ self.gui.show_popup('unpaid-orders');
+ });
+ this.$('.button.delete_unpaid_orders').click(function(){
+ self.gui.show_popup('confirm',{
+ 'title': _t('Delete Unpaid Orders ?'),
+ 'body': _t('This operation will permanently destroy all unpaid orders from all sessions that have been put in the local storage. You will lose all the data and exit the point of sale. This operation cannot be undone.'),
+ confirm: function(){
+ self.pos.db.remove_all_unpaid_orders();
+ window.location = '/';
+ },
+ });
+ });
+ _.each(this.eans, function(ean, name){
+ self.$('.button.'+name).click(function(){
+ self.$('input.ean').val(ean);
+ self.pos.barcode_reader.scan(ean);
+ });
+ });
+ _.each(this.events, function(name){
+ self.pos.proxy.add_notification(name,function(){
+ self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'});
+ self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000);
+ });
+ });
+ },
+ });
+
+ /* --------- 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'],
+ set_status: function(status,msg){
+ var self = this;
+ for(var i = 0; i < this.status.length; i++){
+ this.$('.js_'+this.status[i]).addClass('oe_hidden');
+ }
+ this.$('.js_'+status).removeClass('oe_hidden');
+
+ if(msg){
+ this.$('.js_msg').removeClass('oe_hidden').html(msg);
+ }else{
+ this.$('.js_msg').addClass('oe_hidden').html('');
+ }
+ },
+ });
+
+ /* ------- Synch. Notifications ------- */
+
+ // Displays if there are orders that could
+ // not be submitted, and how many.
+
+ module.SynchNotificationWidget = module.StatusWidget.extend({
+ template: 'SynchNotificationWidget',
+ start: function(){
+ var self = this;
+ this.pos.bind('change:synch', function(pos,synch){
+ self.set_status(synch.state, synch.pending);
+ });
+ this.$el.click(function(){
+ self.pos.push_order();
+ });
+ },
+ });
+
+ /* --------- 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){
+ if(status.status === 'connected'){
+ var warning = false;
+ var msg = ''
+ if(this.pos.config.iface_scan_via_proxy){
+ var scanner = status.drivers.scanner ? status.drivers.scanner.status : false;
+ if( scanner != 'connected' && scanner != 'connecting'){
+ warning = true;
+ msg += _t('Scanner');
+ }
+ }
+ if( this.pos.config.iface_print_via_proxy ||
+ this.pos.config.iface_cashdrawer ){
+ var printer = status.drivers.escpos ? status.drivers.escpos.status : false;
+ if( printer != 'connected' && printer != 'connecting'){
+ warning = true;
+ msg = msg ? msg + ' & ' : msg;
+ msg += _t('Printer');
+ }
+ }
+ if( this.pos.config.iface_electronic_scale ){
+ var scale = status.drivers.scale ? status.drivers.scale.status : false;
+ if( scale != 'connected' && scale != 'connecting' ){
+ warning = true;
+ msg = msg ? msg + ' & ' : msg;
+ msg += _t('Scale');
+ }
+ }
+ msg = msg ? msg + ' ' + _t('Offline') : msg;
+ this.set_status(warning ? 'warning' : 'connected', msg);
+ }else{
+ this.set_status(status.status,'');
+ }
+ },
+ start: function(){
+ var self = this;
+
+ this.set_smart_status(this.pos.proxy.get('status'));
+
+ this.pos.proxy.on('change:status',this,function(eh,status){ //FIXME remove duplicate changes
+ self.set_smart_status(status.newValue);
+ });
+
+ this.$el.click(function(){
+ self.pos.connect_to_proxy();
+ });
+ },
+ });
+
+
+ /*--------------------------------------*\
+ | THE CHROME |
+ \*======================================*/
+
+ // 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)
+ // - popups
+ // - an onscreen keyboard
+ // - .gui which controls the switching between
+ // screens and the showing/closing of popups
+
+ module.Chrome = module.PosBaseWidget.extend({
+ template: 'Chrome',
+ init: function() {
+ var self = this;
+ this._super(arguments[0],{});
+
+ this.started = new $.Deferred(); // resolves when DOM is online
+ this.ready = new $.Deferred(); // resolves when the whole GUI has been loaded
+
+ this.pos = new module.PosModel(this.session,{chrome:this});
+ this.gui = new module.Gui({pos: this.pos, chrome: this});
+ this.chrome = this; // So that chrome's childs have chrome set automatically
+ this.pos.gui = this.gui;
+
+ this.widget = {}; // contains references to subwidgets instances
+
+ this.pos.ready.done(function(){
+ self.build_chrome();
+ self.build_widgets();
+ self.disable_rubberbanding();
+ self.ready.resolve();
+ self.loading_hide();
+ }).fail(function(err){ // error when loading models data from the backend
+ self.loading_error(err);
+ });
+ },
+
+ build_chrome: function() {
+ // remove default webclient handlers that induce click delay
+ $(document).off();
+ $(window).off();
+ $('html').off();
+ $('body').off();
+ $(self.$el).parent().off();
+ $('document').off();
+ $('.oe_web_client').off();
+ $('.openerp_webclient_container').off();
+
+ FastClick.attach(document.body);
+
+ instance.webclient.set_content_full_screen(true);
+
+ this.renderElement();
+
+ if(this.pos.config.iface_big_scrollbars){
+ this.$el.addClass('big-scrollbars');
+ }
+ },
+
+ disable_rubberbanding: function(){
+ // prevent the pos body from being scrollable.
+ document.body.addEventListener('touchmove',function(event){
+ var node = event.target;
+ while(node){
+ if(node.classList && node.classList.contains('touch-scrollable')){
+ return;
+ }
+ node = node.parentNode;
+ }
+ event.preventDefault();
+ });
+ },
+
+ loading_error: function(err){
+ var self = this;
+
+ var title = err.message;
+ var body = err.stack;
+
+ if(err.message === 'XmlHttpRequestError '){
+ title = 'Network Failure (XmlHttpRequestError)';
+ body = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.';
+ }else if(err.message === 'OpenERP Server Error'){
+ title = err.data.message;
+ body = err.data.debug;
+ }
+
+ if( typeof body !== 'string' ){
+ body = 'Traceback not available.';
+ }
+
+ var popup = $(QWeb.render('ErrorTracebackPopupWidget',{
+ widget: { title: title , body: body },
+ }));
+
+ popup.find('.button').click(function(){
+ self.gui.close();
+ });
+
+ popup.css({ zindex: 9001 });
+
+ popup.appendTo(this.$el);
+ },
+ loading_progress: function(fac){
+ this.$('.loader .loader-feedback').removeClass('oe_hidden');
+ this.$('.loader .progress').removeClass('oe_hidden').css({'width': ''+Math.floor(fac*100)+'%'});
+ },
+ loading_message: function(msg,progress){
+ this.$('.loader .loader-feedback').removeClass('oe_hidden');
+ this.$('.loader .message').text(msg);
+ if (typeof progress !== 'undefined') {
+ this.loading_progress(progress);
+ } else {
+ this.$('.loader .progress').addClass('oe_hidden');
+ }
+ },
+ loading_skip: function(callback){
+ if(callback){
+ this.$('.loader .loader-feedback').removeClass('oe_hidden');
+ this.$('.loader .button.skip').removeClass('oe_hidden');
+ this.$('.loader .button.skip').off('click');
+ this.$('.loader .button.skip').click(callback);
+ }else{
+ this.$('.loader .button.skip').addClass('oe_hidden');
+ }
+ },
+ loading_hide: function(){
+ this.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
+ },
+ loading_show: function(){
+ this.$('.loader').removeClass('oe_hidden').animate({opacity:1},150,'swing');
+ },
+
+ widgets: [
+ {
+ 'name': 'order_selector',
+ 'widget': module.OrderSelectorWidget,
+ 'replace': '.placeholder-OrderSelectorWidget',
+ },{
+ 'name': 'proxy_status',
+ 'widget': module.ProxyStatusWidget,
+ 'append': '.pos-rightheader',
+ 'condition': function(){ return this.pos.config.use_proxy },
+ },{
+ 'name': 'notification',
+ 'widget': module.SynchNotificationWidget,
+ 'append': '.pos-rightheader',
+ },{
+ 'name': 'close_button',
+ 'widget': module.HeaderButtonWidget,
+ 'append': '.pos-rightheader',
+ 'args': {
+ label: _t('Close'),
+ action: function(){
+ var self = this;
+ if (!this.confirmed) {
+ this.$el.addClass('confirm');
+ this.$el.text(_t('Confirm'));
+ this.confirmed = setTimeout(function(){
+ self.$el.removeClass('confirm');
+ self.$el.text(_t('Close'));
+ self.confirmed = false;
+ },2000);
+ } else {
+ clearTimeout(this.confirmed);
+ this.gui.close();
+ }
+ },
+ }
+ },{
+ 'name': 'username',
+ 'widget': module.UsernameWidget,
+ 'replace': '.placeholder-UsernameWidget',
+ },{
+ 'name': 'keyboard',
+ 'widget': module.OnscreenKeyboardWidget,
+ 'replace': '.placeholder-OnscreenKeyboardWidget',
+ },{
+ 'name': 'debug',
+ 'widget': module.DebugWidget,
+ 'append': '.pos-content',
+ 'condition': function(){ return this.pos.debug },
+ },
+ ],
+
+ // This method instantiates all the screens, widgets, etc.
+ build_widgets: function() {
+ var self = this;
+
+ for (var i = 0; i < this.widgets.length; i++) {
+ var def = this.widgets[i];
+ if ( !def.condition || def.condition.call(this) ) {
+ var args = typeof def.args === 'function' ? def.args(this) : def.args;
+ var w = new def.widget(this, args || {});
+ if (def.replace) {
+ w.replace(this.$(def.replace));
+ } else if (def.append) {
+ w.appendTo(this.$(def.append));
+ } else if (def.prepend) {
+ w.prependTo(this.$(def.prepend));
+ } else {
+ w.appendTo(this.$el);
+ }
+ this.widget[def.name] = w;
+ }
+ }
+
+ this.screens = {};
+ for (var i = 0; i < this.gui.screen_classes.length; i++) {
+ var classe = this.gui.screen_classes[i];
+ if (!classe.condition || classe.condition.call(this)) {
+ var screen = new classe.widget(this,{});
+ screen.appendTo(this.$('.screens'));
+ this.screens[classe.name] = screen;
+ this.gui.add_screen(classe.name, screen);
+ }
+ }
+
+ this.popups = {};
+ for (var i = 0; i < this.gui.popup_classes.length; i++) {
+ var classe = this.gui.popup_classes[i];
+ if (!classe.condition || classe.condition.call(this)) {
+ var popup = new classe.widget(this,{});
+ popup.appendTo(this.$('.popups'));
+ this.popups[classe.name] = popup;
+ this.gui.add_popup(classe.name, popup);
+ }
+ }
+
+ this.gui.set_startup_screen('products');
+ this.gui.set_default_screen('products');
+
+ },
+
+ destroy: function() {
+ this.pos.destroy();
+ instance.webclient.set_content_full_screen(false);
+ this._super();
+ }
+ });
+}
diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js
index cc44f6f9ce1..2ec7f9f61ed 100644
--- a/addons/point_of_sale/static/src/js/devices.js
+++ b/addons/point_of_sale/static/src/js/devices.js
@@ -416,9 +416,9 @@ openerp.point_of_sale.load_devices = function load_devices(instance,module){ //m
send_printing_job();
},function(error){
if (error) {
- self.pos.pos_widget.screen_selector.show_popup('error-traceback',{
- 'message': _t('Printing Error: ') + error.data.message,
- 'comment': error.data.debug,
+ self.pos.chrome.screen_selector.show_popup('error-traceback',{
+ 'title': _t('Printing Error: ') + error.data.message,
+ 'body': error.data.debug,
});
return;
}
diff --git a/addons/point_of_sale/static/src/js/gui.js b/addons/point_of_sale/static/src/js/gui.js
new file mode 100644
index 00000000000..4627abab60b
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/gui.js
@@ -0,0 +1,340 @@
+
+// this file contains the Gui, which is the pos 'controller'.
+// It contains high level methods to manipulate the interface
+// such as changing between screens, creating popups, etc.
+//
+// it is available to all pos objects trough the '.gui' field.
+
+openerp.point_of_sale.load_gui = function load_gui(instance, module) {
+ "use strict";
+
+ var QWeb = instance.web.qweb;
+ var _t = instance.web._t;
+
+ module.Gui = instance.web.Class.extend({
+ screen_classes: [],
+ popup_classes: [],
+ init: function(options){
+ var self = this;
+ this.pos = options.pos;
+ this.chrome = options.chrome;
+ this.screen_instances = {};
+ this.popup_instances = {};
+ this.default_screen = null;
+ this.startup_screen = null;
+ this.current_popup = null;
+ this.current_screen = null;
+
+ this.chrome.ready.then(function(){
+ var order = self.pos.get_order();
+ if (order) {
+ self.show_saved_screen(order);
+ } else {
+ self.show_screen(self.startup_screen);
+ }
+ self.pos.bind('change:selectedOrder', function(){
+ self.show_saved_screen(self.pos.get_order());
+ });
+ });
+ },
+
+ /* ---- Gui: SCREEN MANIPULATION ---- */
+
+ // register a screen widget to the gui,
+ // it must have been inserted into the dom.
+ add_screen: function(name, screen){
+ screen.hide();
+ this.screen_instances[name] = screen;
+ },
+
+ // sets the screen that will be displayed
+ // for new orders
+ set_default_screen: function(name){
+ this.default_screen = name;
+ },
+
+ // sets the screen that will be displayed
+ // when no orders are present
+ set_startup_screen: function(name) {
+ this.startup_screen = name;
+ },
+
+ // display the screen saved in an order,
+ // called when the user changes the current order
+ // no screen saved ? -> display default_screen
+ // no order ? -> display startup_screen
+ show_saved_screen: function(order,options) {
+ options = options || {};
+ this.close_popup();
+ if (order) {
+ this.show_screen(order.get_screen_data('screen') ||
+ options.default_screen ||
+ this.default_screen,
+ null,'refresh');
+ } else {
+ this.show_screen(this.startup_screen);
+ }
+ },
+
+ // display a screen.
+ // If there is an order, the screen will be saved in the order
+ // - params: used to load a screen with parameters, for
+ // example loading a 'product_details' screen for a specific product.
+ // - refresh: if you want the screen to cycle trough show / hide even
+ // if you are already on the same screen.
+ show_screen: function(screen_name,params,refresh) {
+ var screen = this.screen_instances[screen_name];
+ if (!screen) {
+ console.error("ERROR: show_screen("+screen_name+") : screen not found");
+ }
+
+ this.close_popup();
+
+ var order = this.pos.get_order();
+ if (order) {
+ var old_screen_name = order.get_screen_data('screen');
+
+ order.set_screen_data('screen',screen_name);
+
+ if(params){
+ order.set_screen_data('params',params);
+ }
+
+ if( screen_name !== old_screen_name ){
+ order.set_screen_data('previous-screen',old_screen_name);
+ }
+ }
+
+ if (refresh || screen !== this.current_screen) {
+ if (this.current_screen) {
+ this.current_screen.close();
+ this.current_screen.hide();
+ }
+ this.current_screen = screen;
+ this.current_screen.show();
+ }
+ },
+
+ // returns the current screen.
+ get_current_screen: function() {
+ return this.pos.get_order() ? ( this.pos.get_order().get_screen_data('screen') || this.default_screen ) : this.startup_screen;
+ },
+
+ // goes to the previous screen (as specified in the order). The history only
+ // goes 1 deep ...
+ back: function() {
+ var previous = this.pos.get_order().get_screen_data('previous-screen');
+ if (previous) {
+ this.show_screen(previous);
+ }
+ },
+
+ // returns the parameter specified when this screen was displayed
+ get_current_screen_param: function(param) {
+ if (this.pos.get_order()) {
+ var params = this.pos.get_order().get_screen_data('params');
+ return params ? params[param] : undefined;
+ } else {
+ return undefined;
+ }
+ },
+
+ /* ---- Gui: POPUP MANIPULATION ---- */
+
+ // registers a new popup in the GUI.
+ // the popup must have been previously inserted
+ // into the dom.
+ add_popup: function(name, popup) {
+ popup.hide();
+ this.popup_instances[name] = popup;
+ },
+
+ // displays a popup. Popup do not stack,
+ // are not remembered by the order, and are
+ // closed by screen changes or new popups.
+ show_popup: function(name,options) {
+ if (this.current_popup) {
+ this.close_popup();
+ }
+ this.current_popup = this.popup_instances[name];
+ this.current_popup.show(options);
+ },
+
+ // close the current popup.
+ close_popup: function() {
+ if (this.current_popup) {
+ this.current_popup.close();
+ this.current_popup.hide();
+ this.current_popup = null;
+ }
+ },
+
+ /* ---- Gui: ACCESS CONTROL ---- */
+
+ // A Generic UI that allow to select a user from a list.
+ // It returns a deferred that resolves with the selected user
+ // upon success. Several options are available :
+ // - security: passwords will be asked
+ // - only_managers: restricts the list to managers
+ // - current_user: password will not be asked if this
+ // user is selected.
+ // - title: The title of the user selection list.
+ select_user: function(options){
+ options = options || {};
+ var self = this;
+ var def = new $.Deferred();
+
+ var list = [];
+ for (var i = 0; i < this.pos.users.length; i++) {
+ var user = this.pos.users[i];
+ if (!options.only_managers || user.role === 'manager') {
+ list.push({
+ 'label': user.name,
+ 'item': user,
+ });
+ }
+ }
+
+ this.show_popup('selection',{
+ 'title': options.title || _t('Select User'),
+ list: list,
+ confirm: function(user){ def.resolve(user); },
+ cancel: function(){ def.reject(); },
+ });
+
+ return def.then(function(user){
+ if (options.security && user !== options.current_user && user.pos_security_pin) {
+ return self.ask_password(user.pos_security_pin).then(function(){
+ return user;
+ });
+ } else {
+ return user;
+ }
+ });
+ },
+
+ // Ask for a password, and checks if it this
+ // the same as specified by the function call.
+ // returns a deferred that resolves on success,
+ // fails on failure.
+ ask_password: function(password) {
+ var self = this;
+ var ret = new $.Deferred();
+ if (password) {
+ this.gui.show_popup('password',{
+ 'title': _t('Password ?'),
+ confirm: function(pw) {
+ if (pw !== password) {
+ self.show_popup('error',_t('Incorrect Password'));
+ ret.reject();
+ } else {
+ ret.resolve();
+ }
+ },
+ });
+ } else {
+ ret.resolve();
+ }
+ return ret;
+ },
+
+ // checks if the current user (or the user provided) has manager
+ // access rights. If not, a popup is shown allowing the user to
+ // temporarily login as an administrator.
+ // This method returns a deferred, that succeeds with the
+ // manager user when the login is successfull.
+ sudo: function(user){
+ var def = new $.Deferred();
+ user = user || this.pos.get_cashier();
+
+ if (user.role === 'manager') {
+ return new $.Deferred().resolve(user);
+ } else {
+ return this.select_user({
+ security: true,
+ only_managers: true,
+ title: _t('Login as a Manager'),
+ })
+ }
+ },
+
+ /* ---- Gui: CLOSING THE POINT OF SALE ---- */
+
+ close: function() {
+ var self = this;
+ this.chrome.loading_show();
+ this.chrome.loading_message(_t('Closing ...'));
+
+ this.pos.push_order().then(function(){
+ return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id'])
+ .pipe(function(res) {
+ window.location = '/web#action=' + res[0]['res_id'];
+ },function(err,event) {
+ event.preventDefault();
+ self.show_popup('error',{
+ 'title': _t('Could not close the point of sale.'),
+ 'body': _t('Your internet connection is probably down.'),
+ });
+ self.chrome.widget.close_button.renderElement();
+ });
+ });
+ },
+
+ /* ---- Gui: KEYBOARD INPUT ---- */
+
+ // This is a helper to handle numpad keyboard input.
+ // - buffer: an empty or number string
+ // - input: '[0-9],'+','-','.','CLEAR','BACKSPACE'
+ // - options: 'firstinput' -> will clear buffer if
+ // input is '[0-9]' or '.'
+ // returns the new buffer containing the modifications
+ // (the original is not touched)
+ numpad_input: function(buffer, input, options) {
+ var options = options || {};
+ var newbuf = buffer.slice(0);
+
+ if (input === '.') {
+ if (options.firstinput) {
+ newbuf = "0.";
+ }else if (!newbuf.length || newbuf === '-') {
+ newbuf += "0.";
+ } else if (newbuf.indexOf('.') < 0){
+ newbuf = newbuf + '.';
+ }
+ } else if (input === 'CLEAR') {
+ newbuf = "";
+ } else if (input === 'BACKSPACE') {
+ newbuf = newbuf.substring(0,newbuf.length - 1);
+ } else if (input === '+') {
+ if ( newbuf[0] === '-' ) {
+ newbuf = newbuf.substring(1,newbuf.length);
+ }
+ } else if (input === '-') {
+ if ( newbuf[0] === '-' ) {
+ newbuf = newbuf.substring(1,newbuf.length);
+ } else {
+ newbuf = '-' + newbuf;
+ }
+ } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
+ newbuf = '' + ((parseFloat(newbuf) || 0) + parseFloat(input));
+ } else if (!isNaN(parseInt(input))) {
+ if (options.firstinput) {
+ newbuf = '' + input;
+ } else {
+ newbuf += input;
+ }
+ }
+
+ return newbuf;
+ },
+ });
+
+ module.Gui.define_screen = function (classe) {
+ module.Gui.prototype.screen_classes.push(classe);
+ };
+
+ module.Gui.define_popup = function (classe) {
+ module.Gui.prototype.popup_classes.push(classe);
+ };
+};
+
diff --git a/addons/point_of_sale/static/src/js/widget_keyboard.js b/addons/point_of_sale/static/src/js/keyboard.js
similarity index 98%
rename from addons/point_of_sale/static/src/js/widget_keyboard.js
rename to addons/point_of_sale/static/src/js/keyboard.js
index 2abc5af9288..1249810c4ba 100644
--- a/addons/point_of_sale/static/src/js/widget_keyboard.js
+++ b/addons/point_of_sale/static/src/js/keyboard.js
@@ -5,7 +5,7 @@ openerp.point_of_sale.load_keyboard = function load_keyboard(instance, module){
// A Widget that displays an onscreen keyboard.
// There are two options when creating the widget :
//
- // * 'keyboard_model' : 'simple' | 'full' (default)
+ // * 'keyboard_model' : 'simple' (default) | 'full'
// The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one.
//
// * 'input_selector : (default: '.searchbox input')
@@ -21,7 +21,7 @@ openerp.point_of_sale.load_keyboard = function load_keyboard(instance, module){
this._super(parent,options);
options = options || {};
- this.keyboard_model = options.keyboard_model || 'full';
+ this.keyboard_model = options.keyboard_model || 'simple';
if(this.keyboard_model === 'full'){
this.template = 'OnscreenKeyboardFull';
}
diff --git a/addons/point_of_sale/static/src/js/main.js b/addons/point_of_sale/static/src/js/main.js
index fd0e9bd773e..10e087c446d 100644
--- a/addons/point_of_sale/static/src/js/main.js
+++ b/addons/point_of_sale/static/src/js/main.js
@@ -4,21 +4,24 @@ openerp.point_of_sale = function(openerp) {
var module = openerp.point_of_sale;
- openerp.point_of_sale.load_db(openerp,module); // import db.js
+ openerp.point_of_sale.load_db(openerp,module);
- openerp.point_of_sale.load_models(openerp,module); // import pos_models.js
+ openerp.point_of_sale.load_models(openerp,module);
- openerp.point_of_sale.load_basewidget(openerp,module); // import pos_basewidget.js
+ openerp.point_of_sale.load_basewidget(openerp,module);
- openerp.point_of_sale.load_keyboard(openerp,module); // import pos_keyboard_widget.js
+ openerp.point_of_sale.load_keyboard(openerp,module);
- openerp.point_of_sale.load_screens(openerp,module); // import pos_screens.js
+ openerp.point_of_sale.load_gui(openerp,module);
- openerp.point_of_sale.load_devices(openerp,module); // import pos_devices.js
+ openerp.point_of_sale.load_popups(openerp,module);
- openerp.point_of_sale.load_widgets(openerp,module); // import pos_widgets.js
+ openerp.point_of_sale.load_screens(openerp,module);
- openerp.web.client_actions.add('pos.ui', 'openerp.point_of_sale.PosWidget');
+ openerp.point_of_sale.load_devices(openerp,module);
+
+ openerp.point_of_sale.load_chrome(openerp,module);
+
+ openerp.web.client_actions.add('pos.ui', 'openerp.point_of_sale.Chrome');
};
-
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 946c7f16988..dbb71e79bf8 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -3,7 +3,6 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
var QWeb = instance.web.qweb;
var _t = instance.web._t;
- var barcode_parser_module = instance.barcodes;
var round_di = instance.web.round_decimals;
var round_pr = instance.web.round_precision
@@ -24,7 +23,8 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
var self = this;
this.session = session;
this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time
- this.pos_widget = attributes.pos_widget;
+ this.chrome = attributes.chrome;
+ this.gui = attributes.gui;
this.proxy = new module.ProxyDevice(this); // used to communicate to the hardware devices via a local proxy
this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy});
@@ -34,7 +34,6 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode
// Business data; loaded from the server at launch
- this.accounting_precision = 2; //TODO
this.company_logo = null;
this.company_logo_base64 = '';
this.currency = null;
@@ -73,18 +72,23 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
this.get('orders').bind('remove', function(order,_unused_,options){
self.on_removed_order(order,options.index,options.reason);
});
-
+
// We fetch the backend data on the server asynchronously. this is done only when the pos user interface is launched,
// Any change on this data made on the server is thus not reflected on the point of sale until it is relaunched.
// when all the data has loaded, we compute some stuff, and declare the Pos ready to be used.
- this.ready = this.load_server_data()
- .then(function(){
- if(self.config.use_proxy){
- return self.connect_to_proxy();
- }
- }); // used to read barcodes);
+ this.ready = this.load_server_data().then(function(){
+ return self.after_load_server_data();
+ });;
+ },
+ after_load_server_data: function(){
+ this.barcode_reader.connect();
+ this.load_orders();
+ this.set_start_order();
+ this.push_order();
+ if(this.config.use_proxy){
+ return this.connect_to_proxy();
+ }
},
-
// releases ressources holds by the model at the end of life of the posmodel
destroy: function(){
// FIXME, should wait for flushing, return a deferred to indicate successfull destruction
@@ -93,18 +97,19 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
this.barcode_reader.disconnect();
this.barcode_reader.disconnect_from_proxy();
},
+
connect_to_proxy: function(){
var self = this;
var done = new $.Deferred();
this.barcode_reader.disconnect_from_proxy();
- this.pos_widget.loading_message(_t('Connecting to the PosBox'),0);
- this.pos_widget.loading_skip(function(){
+ this.chrome.loading_message(_t('Connecting to the PosBox'),0);
+ this.chrome.loading_skip(function(){
self.proxy.stop_searching();
});
this.proxy.autoconnect({
force_ip: self.config.proxy_ip || undefined,
progress: function(prog){
- self.pos_widget.loading_progress(prog);
+ self.chrome.loading_progress(prog);
},
}).then(function(){
if(self.config.iface_scan_via_proxy){
@@ -116,13 +121,6 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
return done;
},
- // helper function to load data from the server. Obsolete use the models loader below.
- fetch: function(model, fields, domain, ctx){
- this._load_progress = (this._load_progress || 0) + 0.05;
- this.pos_widget.loading_message(_t('Loading')+' '+model,this._load_progress);
- return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
- },
-
// Server side model loaders. This is the list of the models that need to be loaded from
// the server. The models are loaded one by one by this list's order. The 'loaded' callback
// is used to store the data in the appropriate place once it has been loaded. This callback
@@ -397,7 +395,7 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
}, {
label: 'barcodes',
loaded: function(self) {
- var barcode_parser = new barcode_parser_module.BarcodeParser({'nomenclature_id': self.config.barcode_nomenclature_id});
+ var barcode_parser = new instance.barcodes.BarcodeParser({'nomenclature_id': self.config.barcode_nomenclature_id});
self.barcode_reader.set_barcode_parser(barcode_parser);
return barcode_parser.is_loaded();
},
@@ -417,7 +415,7 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
loaded.resolve();
}else{
var model = self.models[index];
- self.pos_widget.loading_message(_t('Loading')+' '+(model.label || model.model || ''), progress);
+ self.chrome.loading_message(_t('Loading')+' '+(model.label || model.model || ''), progress);
var cond = typeof model.condition === 'function' ? model.condition(self,tmp) : true;
if (!cond) {
@@ -593,7 +591,6 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
var self = this;
if(order){
- this.proxy.log('push_order',order.export_as_JSON());
this.db.add_order(order.export_as_JSON());
}
@@ -649,7 +646,7 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
transfer.pipe(function(order_server_id){
// generate the pdf and download it
- self.pos_widget.do_action('point_of_sale.pos_invoice_report',{additional_context:{
+ self.chrome.do_action('point_of_sale.pos_invoice_report',{additional_context:{
active_ids:order_server_id,
}});
@@ -722,9 +719,9 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
if (error.data.exception_type == 'warning') {
delete error.data.debug;
}
- self.pos_widget.screen_selector.show_popup('error-traceback',{
- message: error.data.message,
- comment: error.data.debug
+ self.gui.show_popup('error-traceback',{
+ 'title': error.data.message,
+ 'body': error.data.debug
});
}
// prevent an error popup creation by the rpc failure
@@ -756,6 +753,96 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
},
});
+ // Add fields to the list of read fields when a model is loaded
+ // by the point of sale.
+ // e.g: module.load_fields("product.product",['price','category'])
+
+ module.load_fields = function(model_name, fields) {
+ if (!(fields instanceof Array)) {
+ fields = [fields];
+ }
+
+ var models = module.PosModel.prototype.models;
+ for (var i = 0; i < models.length; i++) {
+ var model = models[i];
+ if (model.model === model_name) {
+ // if 'fields' is empty all fields are loaded, so we do not need
+ // to modify the array
+ if ((model.fields instanceof Array) && model.fields.length > 0) {
+ model.fields = model.fields.concat(fields || []);
+ }
+ }
+ }
+ };
+
+ // Loads openerp models at the point of sale startup.
+ // load_models take an array of model loader declarations.
+ // - The models will be loaded in the array order.
+ // - If no openerp model name is provided, no server data
+ // will be loaded, but the system can be used to preprocess
+ // data before load.
+ // - loader arguments can be functions that return a dynamic
+ // value. The function takes the PosModel as the first argument
+ // and a temporary object that is shared by all models, and can
+ // be used to store transient information between model loads.
+ // - There is no dependency management. The models must be loaded
+ // in the right order. Newly added models are loaded at the end
+ // but the after / before options can be used to load directly
+ // before / after another model.
+ //
+ // models: [{
+ // model: [string] the name of the openerp model to load.
+ // label: [string] The label displayed during load.
+ // fields: [[string]|function] the list of fields to be loaded.
+ // Empty Array / Null loads all fields.
+ // order: [[string]|function] the models will be ordered by
+ // the provided fields
+ // domain: [domain|function] the domain that determines what
+ // models need to be loaded. Null loads everything
+ // ids: [[id]|function] the id list of the models that must
+ // be loaded. Overrides domain.
+ // context: [Dict|function] the openerp context for the model read
+ // condition: [function] do not load the models if it evaluates to
+ // false.
+ // loaded: [function(self,model)] this function is called once the
+ // models have been loaded, with the data as second argument
+ // if the function returns a deferred, the next model will
+ // wait until it resolves before loading.
+ // }]
+ //
+ // options:
+ // before: [string] The model will be loaded before the named models
+ // (applies to both model name and label)
+ // after: [string] The model will be loaded after the (last loaded)
+ // named model. (applies to both model name and label)
+ //
+ module.load_models = function(models,options) {
+ options = options || {};
+ if (!(models instanceof Array)) {
+ models = [models];
+ }
+
+ var pmodels = module.PosModel.prototype.models;
+ var index = pmodels.length;
+ if (options.before) {
+ for (var i = 0; i < pmodels.length; i++) {
+ if ( pmodels[i].model === options.before ||
+ pmodels[i].label === options.before ){
+ index = i;
+ break;
+ }
+ }
+ } else if (options.after) {
+ for (var i = 0; i < pmodels.length; i++) {
+ if ( pmodels[i].model === options.after ||
+ pmodels[i].label === options.after ){
+ index = i + 1;
+ }
+ }
+ }
+ pmodels.splice.apply(pmodels,[index,0].concat(models));
+ };
+
var orderline_id = 1;
// An orderline represent one element of the content of a client's shopping cart.
@@ -1107,7 +1194,7 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
this.pos = options.pos;
this.selected_orderline = undefined;
this.selected_paymentline = undefined;
- this.screen_data = {}; // see ScreenSelector
+ this.screen_data = {}; // see Gui
this.temporary = options.temporary || false;
this.creation_date = new Date();
this.to_invoice = false;
@@ -1236,7 +1323,7 @@ openerp.point_of_sale.load_models = function load_models(instance, module){ //mo
qweb.default_dict = _.clone(QWeb.default_dict);
qweb.add_template(''+subreceipt+'');
- return qweb.render('subreceipt',{'pos':self.pos,'widget':self.pos.pos_widget,'order':self, 'receipt': receipt}) ;
+ return qweb.render('subreceipt',{'pos':self.pos,'widget':self.pos.chrome,'order':self, 'receipt': receipt}) ;
}
}
diff --git a/addons/point_of_sale/static/src/js/popups.js b/addons/point_of_sale/static/src/js/popups.js
new file mode 100644
index 00000000000..e52d9be9669
--- /dev/null
+++ b/addons/point_of_sale/static/src/js/popups.js
@@ -0,0 +1,246 @@
+
+// This file contains the Popups.
+// Popups must be loaded and named in chrome.js.
+// They are instanciated / destroyed with the .gui.show_popup()
+// and .gui.close_popup() methods.
+
+openerp.point_of_sale.load_popups = function load_popups(instance, module) {
+ "use strict";
+
+ module.PopupWidget = module.PosBaseWidget.extend({
+ init: function(parent, args) {
+ this._super(parent, args);
+ this.options = {};
+ },
+ events: {
+ 'click .button.cancel': 'click_cancel',
+ 'click .button.confirm': 'click_confirm',
+ 'click .selection-item': 'click_item',
+ 'click .input-button': 'click_numpad',
+ 'click .mode-button': 'click_numpad',
+ },
+
+ // show the popup !
+ show: function(options){
+ if(this.$el){
+ this.$el.removeClass('oe_hidden');
+ }
+
+ if (typeof options === 'string') {
+ this.options = {title: options};
+ } else {
+ this.options = options || {};
+ }
+
+ this.renderElement();
+
+ // popups block the barcode reader ...
+ if (this.pos.barcode_reader) {
+ this.pos.barcode_reader.save_callbacks();
+ this.pos.barcode_reader.reset_action_callbacks();
+ }
+ },
+
+ // called before hide, when a popup is closed.
+ // extend this if you want a custom action when the
+ // popup is closed.
+ close: function(){
+ if (this.pos.barcode_reader) {
+ this.pos.barcode_reader.restore_callbacks();
+ }
+ },
+
+ // hides the popup. keep in mind that this is called in
+ // the initialization pass of the pos instantiation,
+ // so you don't want to do anything fancy in here
+ hide: function(){
+ if (this.$el) {
+ this.$el.addClass('oe_hidden');
+ }
+ },
+
+ // what happens when we click cancel
+ // ( it should close the popup and do nothing )
+ click_cancel: function(){
+ this.gui.close_popup();
+ if (this.options.cancel) {
+ this.options.cancel.call(this);
+ }
+ },
+
+ // what happens when we confirm the action
+ click_confirm: function(){
+ this.gui.close_popup();
+ if (this.options.confirm) {
+ this.options.confirm.call(this);
+ }
+ },
+
+ // Since Widget does not support extending the events declaration
+ // we declared them all in the top class.
+ click_item: function(){},
+ click_numad: function(){},
+ });
+
+
+ module.ErrorPopupWidget = module.PopupWidget.extend({
+ template:'ErrorPopupWidget',
+ show: function(options){
+ this._super(options);
+
+ $('body').append('');
+
+ },
+ });
+ module.Gui.define_popup({name:'error', widget:module.ErrorPopupWidget});
+
+
+ module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
+ template:'ErrorTracebackPopupWidget',
+ });
+ module.Gui.define_popup({name:'error-traceback', widget:module.ErrorTracebackPopupWidget});
+
+
+ module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({
+ template:'ErrorBarcodePopupWidget',
+ show: function(barcode){
+ this._super({barcode: barcode});
+ },
+ });
+ module.Gui.define_popup({name:'error-barcode', widget:module.ErrorBarcodePopupWidget});
+
+
+ module.ConfirmPopupWidget = module.PopupWidget.extend({
+ template: 'ConfirmPopupWidget',
+ });
+ module.Gui.define_popup({name:'confirm', widget:module.ConfirmPopupWidget});
+
+ /**
+ * A popup that allows the user to select one item from a list.
+ *
+ * show_popup('selection',{
+ * title: "Popup Title",
+ * list: [
+ * { label: 'foobar', item: 45 },
+ * { label: 'bar foo', item: 'stuff' },
+ * ],
+ * confirm: function(item) {
+ * // get the item selected by the user.
+ * },
+ * cancel: function(){
+ * // user chose nothing
+ * }
+ * });
+ */
+
+ module.SelectionPopupWidget = module.PopupWidget.extend({
+ template: 'SelectionPopupWidget',
+ show: function(options){
+ options = options || {};
+ var self = this;
+ this._super();
+
+ this.list = options.list || [];
+ this.renderElement();
+
+ this.$('.selection-item').click(function(){
+ if (options.confirm) {
+ var item = self.list[parseInt($(this).data('item-index'))];
+ item = item ? item.item : item;
+ options.confirm.call(self,item);
+ }
+ });
+ },
+ click_item : function(event) {
+ this.gui.close_popup();
+ if (this.options.confirm) {
+ var item = self.list[parseInt($(event.target).data('item-index'))];
+ item = item ? item.item : item;
+ }
+ }
+ });
+ module.Gui.define_popup({name:'selection', widget:module.SelectionPopupWidget});
+
+
+ module.TextInputPopupWidget = module.PopupWidget.extend({
+ template: 'TextInputPopupWidget',
+ show: function(options){
+ options = options || {};
+ var self = this;
+ this._super();
+
+ this.renderElement();
+ this.$('input,textarea').focus();
+
+ this.$('.button.confirm').click(function(){
+ });
+ },
+ click_confirm: function(){
+ var value = self.$('input,textarea').val();
+ self.gui.close_popup();
+ if( this.options.confirm ){
+ this.options.confirm.call(self,value);
+ }
+ },
+ });
+ module.Gui.define_popup({name:'textinput', widget:module.TextInputPopupWidget});
+
+
+ module.TextAreaPopupWidget = module.TextInputPopupWidget.extend({
+ template: 'TextAreaPopupWidget',
+ });
+ module.Gui.define_popup({name:'textarea', widget:module.TextAreaPopupWidget});
+
+
+ module.NumberPopupWidget = module.PopupWidget.extend({
+ template: 'NumberPopupWidget',
+ show: function(options){
+ options = options || {};
+ var self = this;
+ this._super();
+
+ this.inputbuffer = options.value || '';
+ this.renderElement();
+ this.firstinput = true;
+ },
+ click_numpad: function(event){
+ var newbuf = this.gui.numpad_input(
+ this.inputbuffer,
+ $(event.target).data('action'),
+ {'firstinput': this.firstinput});
+
+ this.firstinput = (newbuf.length === 0);
+
+ if (newbuf !== this.inputbuffer) {
+ this.inputbuffer = newbuf;
+ this.$('.value').text(this.inputbuffer);
+ }
+ },
+ click_confirm: function(event){
+ this.gui.close_popup();
+ if( this.options.confirm ){
+ this.options.confirm.call(this,this.inputbuffer);
+ }
+ },
+ });
+ module.Gui.define_popup({name:'number', widget:module.NumberPopupWidget});
+
+ module.PasswordPopupWidget = module.NumberPopupWidget.extend({
+ renderElement: function(){
+ this._super();
+ this.$('.popup').addClass('popup-password');
+ },
+ });
+ module.Gui.define_popup({name:'password', widget:module.PasswordPopupWidget});
+
+ module.UnsentOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+ template: 'UnsentOrdersPopupWidget',
+ });
+ module.Gui.define_popup({name:'unsent-orders', widget:module.UnsentOrdersPopupWidget});
+
+ module.UnpaidOrdersPopupWidget = module.ConfirmPopupWidget.extend({
+ template: 'UnpaidOrdersPopupWidget',
+ });
+ module.Gui.define_popup({name:'unpaid-orders', widget:module.UnpaidOrdersPopupWidget});
+};
+
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 092c85a0d47..82fe865d1d2 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -1,174 +1,65 @@
-// this file contains the screens definitions. Screens are the
+// This file contains the Screens definitions. Screens are the
// content of the right pane of the pos, containing the main functionalities.
-// screens are contained in the PosWidget, in pos_widget.js
-// all screens are present in the dom at all time, but only one is shown at the
-// same time.
//
-// transition between screens is made possible by the use of the screen_selector,
-// which is responsible of hiding and showing the screens, as well as maintaining
-// the state of the screens between different orders.
+// Screens must be defined and named in chrome.js before use.
//
-// all screens inherit from ScreenWidget. the only addition from the base widgets
+// Screens transitions are controlled by the Gui.
+// gui.set_startup_screen() sets the screen displayed at startup
+// gui.set_default_screen() sets the screen displayed for new orders
+// gui.show_screen() shows a screen
+// gui.back() goes to the previous screen
+//
+// Screen state is saved in the order. When a new order is selected,
+// a screen is displayed based on the state previously saved in the order.
+// this is also done in the Gui with:
+// gui.show_saved_screen()
+//
+// All screens inherit from ScreenWidget. The only addition from the base widgets
// are show() and hide() which shows and hides the screen but are also used to
-// bind and unbind actions on widgets and devices. The screen_selector guarantees
+// bind and unbind actions on widgets and devices. The gui guarantees
// that only one screen is shown at the same time and that show() is called after all
// hide()s
+//
+// Each Screens must be independant from each other, and should have no
+// persistent state outside the models. Screen state variables are reset at
+// each screen display. A screen can be called with parameters, which are
+// to be used for the duration of the screen only.
openerp.point_of_sale.load_screens = function load_screens(instance, module){ //module is instance.point_of_sale
"use strict";
- var QWeb = instance.web.qweb,
- _t = instance.web._t;
+ var QWeb = instance.web.qweb;
+ var _t = instance.web._t;
var round_pr = instance.web.round_precision
- module.ScreenSelector = instance.web.Class.extend({
- init: function(options){
- this.pos = options.pos;
- this.screen_set = options.screen_set || {};
- this.popup_set = options.popup_set || {};
- this.default_screen = options.default_screen;
- this.startup_screen = options.startup_screen;
- this.current_popup = null;
- this.current_mode = options.default_mode || 'cashier';
- this.current_screen = null;
-
- for (var screen_name in this.screen_set) {
- this.screen_set[screen_name].hide();
- }
-
- for (var popup_name in this.popup_set) {
- this.popup_set[popup_name].hide();
- }
-
- if (this.pos.get_order()) {
- this.pos.get_order().set_screen_data({
- 'screen': this.default_screen,
- });
- }
-
- this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
- },
- add_screen: function(screen_name, screen){
- screen.hide();
- this.screen_set[screen_name] = screen;
- return this;
- },
- show_popup: function(name,options){
- if(this.current_popup){
- this.close_popup();
- }
- this.current_popup = this.popup_set[name];
- this.current_popup.show(options);
- },
- close_popup: function(){
- if(this.current_popup){
- this.current_popup.close();
- this.current_popup.hide();
- this.current_popup = null;
- }
- },
- load_saved_screen: function(options){
- options = options || {};
- this.close_popup();
- var selectedOrder = this.pos.get_order();
- // FIXME : this changing screen behaviour is sometimes confusing ...
- this.set_current_screen(selectedOrder.get_screen_data('screen') || options.default_screen || this.default_screen,null,'refresh');
- //this.set_current_screen(this.default_screen,null,'refresh');
-
- },
- set_user_mode: function(user_mode){
- if(user_mode !== this.current_mode){
- this.close_popup();
- this.current_mode = user_mode;
- this.load_saved_screen();
- }
- },
- get_user_mode: function(){
- return this.current_mode;
- },
- set_current_screen: function(screen_name,params,refresh){
- var screen = this.screen_set[screen_name];
- if(!screen){
- console.error("ERROR: set_current_screen("+screen_name+") : screen not found");
- }
-
- this.close_popup();
-
- var order = this.pos.get_order();
- if (order) {
- var old_screen_name = order.get_screen_data('screen');
-
- order.set_screen_data('screen',screen_name);
-
- if(params){
- order.set_screen_data('params',params);
- }
-
- if( screen_name !== old_screen_name ){
- order.set_screen_data('previous-screen',old_screen_name);
- }
- }
-
- if ( refresh || screen !== this.current_screen){
- if(this.current_screen){
- this.current_screen.close();
- this.current_screen.hide();
- }
- this.current_screen = screen;
- this.current_screen.show();
- }
- },
- get_current_screen: function(){
- return this.pos.get_order().get_screen_data('screen') || this.default_screen;
- },
- back: function(){
- var previous = this.pos.get_order().get_screen_data('previous-screen');
- if(previous){
- this.set_current_screen(previous);
- }
- },
- get_current_screen_param: function(param){
- var params = this.pos.get_order().get_screen_data('params');
- return params ? params[param] : undefined;
- },
- set_default_screen: function(){
- this.set_current_screen(this.default_screen);
- },
- change_default_screen: function(screen){
- this.default_screen = screen;
- },
- });
+ /*--------------------------------------*\
+ | THE SCREEN WIDGET |
+ \*======================================*/
+
+ // The screen widget is the base class inherited
+ // by all screens.
module.ScreenWidget = module.PosBaseWidget.extend({
- show_numpad: true,
- show_leftpane: true,
-
init: function(parent,options){
this._super(parent,options);
this.hidden = false;
},
- help_button_action: function(){
- this.pos_widget.screen_selector.show_popup('help');
- },
-
barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned
- hotkeys_handlers: {},
-
// what happens when a product is scanned :
// it will add the product to the order and go to barcode_product_screen.
barcode_product_action: function(code){
var self = this;
if(self.pos.scan_product(code)){
if(self.barcode_product_screen){
- self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen);
+ self.gui.show_screen(self.barcode_product_screen);
}
}else{
- self.pos_widget.screen_selector.show_popup('error-barcode',code.code);
+ self.gui.show_popup('error-barcode',code.code);
}
},
@@ -181,11 +72,11 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
for(var i = 0, len = users.length; i < len; i++){
if(users[i].barcode === code.code){
this.pos.set_cashier(users[i]);
- this.pos_widget.username.renderElement();
+ this.chrome.widget.username.renderElement();
return true;
}
}
- this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
+ this.gui.show_popup('error-barcode',code.code);
return false;
},
@@ -199,7 +90,7 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this.pos.get_order().set_client(partner);
return true;
}
- this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
+ this.gui.show_popup('error-barcode',code.code);
return false;
},
@@ -213,7 +104,7 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
// What happens when an invalid barcode is scanned : shows an error popup.
barcode_error_action: function(code){
- this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
+ this.gui.show_popup('error-barcode',code.code);
},
// this method shows the screen and sets up all the widget related to this screen. Extend this method
@@ -226,12 +117,6 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this.$el.removeClass('oe_hidden');
}
- var self = this;
-
- this.pos_widget.set_numpad_visible(this.show_numpad);
- this.pos_widget.set_leftpane_visible(this.show_leftpane);
-
- this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
this.pos.barcode_reader.set_action_callback({
'cashier': self.barcode_cashier_action ? function(code){ self.barcode_cashier_action(code); } : undefined ,
'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined ,
@@ -272,293 +157,69 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
});
- module.PopUpWidget = module.PosBaseWidget.extend({
- show: function(){
- if(this.$el){
- this.$el.removeClass('oe_hidden');
- }
- },
- /* called before hide, when a popup is closed */
- close: function(){
- },
- /* hides the popup. keep in mind that this is called in the initialization pass of the
- * pos instantiation, so you don't want to do anything fancy in here */
- hide: function(){
- if(this.$el){
- this.$el.addClass('oe_hidden');
- }
- },
- });
+ /*--------------------------------------*\
+ | THE DOM CACHE |
+ \*======================================*/
- module.FullscreenPopup = module.PopUpWidget.extend({
- template:'FullscreenPopupWidget',
- show: function(){
- var self = this;
- this._super();
- this.renderElement();
- this.$('.button.fullscreen').off('click').click(function(){
- window.document.body.webkitRequestFullscreen();
- self.pos_widget.screen_selector.close_popup();
- });
- this.$('.button.cancel').off('click').click(function(){
- self.pos_widget.screen_selector.close_popup();
- });
- },
- ismobile: function(){
- return typeof window.orientation !== 'undefined';
- }
- });
+ // 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.ErrorPopupWidget = module.PopUpWidget.extend({
- template:'ErrorPopupWidget',
- show: function(options){
+ module.DomCache = instance.web.Class.extend({
+ init: function(options){
options = options || {};
- var self = this;
- this._super();
+ this.max_size = options.max_size || 2000;
- $('body').append('');
-
- this.message = options.message || _t('Error');
- this.comment = options.comment || '';
-
- this.renderElement();
-
- this.pos.barcode_reader.save_callbacks();
- this.pos.barcode_reader.reset_action_callbacks();
-
- this.$('.footer .button').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if ( options.confirm ) {
- options.confirm.call(self);
- }
- });
+ this.cache = {};
+ this.access_time = {};
+ this.size = 0;
},
- close:function(){
- this._super();
- this.pos.barcode_reader.restore_callbacks();
- },
- });
-
- module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
- template:'ErrorTracebackPopupWidget',
- });
-
- module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({
- template:'ErrorBarcodePopupWidget',
- show: function(barcode){
- this.barcode = barcode;
- this._super();
- },
- });
-
- module.ConfirmPopupWidget = module.PopUpWidget.extend({
- template: 'ConfirmPopupWidget',
- show: function(options){
- options = options || {};
- var self = this;
- this._super();
-
- this.message = options.message || '';
- this.comment = options.comment || '';
- this.renderElement();
-
- this.$('.button.cancel').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if( options.cancel ){
- options.cancel.call(self);
- }
- });
-
- this.$('.button.confirm').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if( options.confirm ){
- options.confirm.call(self);
- }
- });
- },
- });
-
- /**
- * A popup that allows the user to select one item from a list.
- *
- * show_popup('selection',{
- * message: 'Pick an Option',
- * message: "Popup Title",
- * list: [
- * { label: 'foobar', item: 45 },
- * { label: 'bar foo', item: 'stuff' },
- * ],
- * confirm: function(item) {
- * // get the item selected by the user.
- * },
- * cancel: function(){
- * // user chose nothing
- * }
- * });
- */
-
- module.SelectionPopupWidget = module.PopUpWidget.extend({
- template: 'SelectionPopupWidget',
- show: function(options){
- options = options || {};
- var self = this;
- this._super();
-
- this.message = options.message || '';
- this.list = options.list || [];
- this.renderElement();
-
- this.$('.button.cancel').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if (options.cancel){
- options.cancel.call(self);
- }
- });
-
- this.$('.selection-item').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if (options.confirm) {
- var item = self.list[parseInt($(this).data('item-index'))];
- item = item ? item.item : item;
- options.confirm.call(self,item);
- }
- });
- },
- });
-
- module.TextInputPopupWidget = module.PopUpWidget.extend({
- template: 'TextInputPopupWidget',
- show: function(options){
- options = options || {};
- var self = this;
- this._super();
-
- this.message = options.message || '';
- this.comment = options.comment || '';
- this.value = options.value || '';
- this.renderElement();
- this.$('input,textarea').focus();
-
- this.$('.button.cancel').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if( options.cancel ){
- options.cancel.call(self);
- }
- });
-
- this.$('.button.confirm').click(function(){
- self.pos_widget.screen_selector.close_popup();
- var value = self.$('input,textarea').val();
- if( options.confirm ){
- options.confirm.call(self,value);
- }
- });
- },
- });
-
- module.TextAreaPopupWidget = module.TextInputPopupWidget.extend({
- template: 'TextAreaPopupWidget',
- });
-
- module.NumberPopupWidget = module.PopUpWidget.extend({
- template: 'NumberPopupWidget',
- click_numpad_button: function($el,event){
- this.numpad_input($el.data('action'));
- },
- numpad_input: function(input) { //FIXME -> Deduplicate code
- var oldbuf = this.inputbuffer.slice(0);
-
- if (input === '.') {
- if (this.firstinput) {
- this.inputbuffer = "0.";
- }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
- this.inputbuffer += "0.";
- } else if (this.inputbuffer.indexOf('.') < 0){
- this.inputbuffer = this.inputbuffer + '.';
- }
- } else if (input === 'CLEAR') {
- this.inputbuffer = "";
- } else if (input === 'BACKSPACE') {
- this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
- } else if (input === '+') {
- if ( this.inputbuffer[0] === '-' ) {
- this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
- }
- } else if (input === '-') {
- if ( this.inputbuffer[0] === '-' ) {
- this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
- } else {
- this.inputbuffer = '-' + this.inputbuffer;
- }
- } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
- this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
- } else if (!isNaN(parseInt(input))) {
- if (this.firstinput) {
- this.inputbuffer = '' + input;
- } else {
- this.inputbuffer += input;
+ 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--;
}
}
-
- this.firstinput = this.inputbuffer.length === 0;
-
- if (this.inputbuffer !== oldbuf) {
- this.$('.value').text(this.inputbuffer);
+ return node;
+ },
+ get_node: function(key){
+ var cached = this.cache[key];
+ if(cached){
+ this.access_time[key] = new Date().getTime();
}
- },
- show: function(options){
- options = options || {};
- var self = this;
- this._super();
-
- this.message = options.message || '';
- this.comment = options.comment || '';
- this.inputbuffer = options.value || '';
- this.renderElement();
- this.firstinput = true;
-
- this.$('.input-button,.mode-button').click(function(event){
- self.click_numpad_button($(this),event);
- });
- this.$('.button.cancel').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if( options.cancel ){
- options.cancel.call(self);
- }
- });
-
- this.$('.button.confirm').click(function(){
- self.pos_widget.screen_selector.close_popup();
- if( options.confirm ){
- options.confirm.call(self,self.inputbuffer);
- }
- });
+ return cached;
},
});
- module.PasswordPopupWidget = module.NumberPopupWidget.extend({
- renderElement: function(){
- this._super();
- this.$('.popup').addClass('popup-password'); // HELLO HACK !
- },
- });
+ /*--------------------------------------*\
+ | THE SCALE SCREEN |
+ \*======================================*/
- module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
- template: 'ErrorNoClientPopupWidget',
- });
-
- module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
- template: 'ErrorInvoiceTransferPopupWidget',
- });
-
- module.UnsentOrdersPopupWidget = module.ConfirmPopupWidget.extend({
- template: 'UnsentOrdersPopupWidget',
- });
-
- module.UnpaidOrdersPopupWidget = module.ConfirmPopupWidget.extend({
- template: 'UnpaidOrdersPopupWidget',
- });
+ // The scale screen displays the weight of
+ // a product on the electronic scale.
module.ScaleScreenWidget = module.ScreenWidget.extend({
template:'ScaleScreenWidget',
@@ -566,8 +227,6 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
next_screen: 'products',
previous_screen: 'products',
- show_leftpane: false,
-
show: function(){
this._super();
var self = this;
@@ -579,21 +238,21 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this.hotkey_handler = function(event){
if(event.which === 13){
self.order_product();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
+ self.gui.show_screen(self.next_screen);
}else if(event.which === 27){
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
+ self.gui.show_screen(self.previous_screen);
}
};
$('body').on('keyup',this.hotkey_handler);
this.$('.back').click(function(){
- self.pos_widget.screen_selector.set_current_screen(self.previous_screen);
+ self.gui.show_screen(self.previous_screen);
});
this.$('.next,.buy-product').click(function(){
self.order_product();
- self.pos_widget.screen_selector.set_current_screen(self.next_screen);
+ self.gui.show_screen(self.next_screen);
});
queue.schedule(function(){
@@ -604,12 +263,7 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
},
get_product: function(){
- var ss = this.pos_widget.screen_selector;
- if(ss){
- return ss.get_current_screen_param('product');
- }else{
- return undefined;
- }
+ return this.gui.get_current_screen_param('product');
},
order_product: function(){
this.pos.get_order().add_product(this.get_product(),{ quantity: this.weight });
@@ -654,24 +308,555 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this.pos.proxy_queue.clear();
},
});
+ module.Gui.define_screen({name: 'scale', widget: module.ScaleScreenWidget});
+ /*--------------------------------------*\
+ | 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();
+ 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.numpad_state = options.numpad_state;
+ this.numpad_state.reset();
+ this.numpad_state.bind('set_value', this.set_value, this);
+
+ this.pos.bind('change:selectedOrder', this.change_selected_order, this);
+
+ this.line_click_handler = function(event){
+ self.pos.get_order().select_orderline(this.orderline);
+ self.numpad_state.reset();
+ };
+
+ if (this.pos.get_order()) {
+ this.bind_order_events();
+ }
+
+ },
+ set_value: function(val) {
+ var order = this.pos.get_order();
+ if (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.numpad_state.reset();
+ 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.update_summary, this);
+ order.bind('change:client', this.update_summary, this);
+ order.unbind('change', this.update_summary, this);
+ order.bind('change', this.update_summary, this);
+
+ 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){
+ 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 Action Buttons -------- */
+
+ // Above the numpad and the actionpad, buttons
+ // for extra actions and controls by point of
+ // sale extensions modules.
+
+ module.action_button_classes = [];
+ module.define_action_button = function(classe, options){
+ options = options || {};
+
+ var classes = module.action_button_classes;
+ var index = classes.length;
+
+ if (options.after) {
+ for (var i = 0; i < classes.length; i++) {
+ if (classes[i].name === options.after) {
+ index = i + 1;
+ }
+ }
+ } else if (options.before) {
+ for (var i = 0; i < classes.length; i++) {
+ if (classes[i].name === options.after) {
+ index = i;
+ break;
+ }
+ }
+ }
+ classes.splice(i,0,classe);
+ };
+
+ module.ActionButtonWidget = module.PosBaseWidget.extend({
+ template: 'ActionButtonWidget',
+ label: _t('Button'),
+ renderElement: function(){
+ var self = this;
+ this._super();
+ this.$el.click(function(){
+ self.button_click();
+ });
+ },
+ button_click: function(){},
+ });
+
+ /* -------- The Product Screen -------- */
+
module.ProductScreenWidget = module.ScreenWidget.extend({
template:'ProductScreenWidget',
- show_numpad: true,
- show_leftpane: true,
+ start: function(){
- start: function(){ //FIXME this should work as renderElement... but then the categories aren't properly set. explore why
var self = this;
+ this.actionpad = new module.ActionpadWidget(this,{});
+ this.actionpad.replace(this.$('.placeholder-ActionpadWidget'));
+
+ this.numpad = new module.NumpadWidget(this,{});
+ this.numpad.replace(this.$('.placeholder-NumpadWidget'));
+
+ this.order_widget = new module.OrderWidget(this,{
+ numpad_state: this.numpad.state,
+ });
+ this.order_widget.replace(this.$('.placeholder-OrderWidget'));
+
this.product_list_widget = new module.ProductListWidget(this,{
- click_product_action: function(product){
- if(product.to_weight && self.pos.config.iface_electronic_scale){
- self.pos_widget.screen_selector.set_current_screen('scale',{product: product});
- }else{
- self.pos.get_order().add_product(product);
- }
- },
+ click_product_action: function(product){ self.click_product(product); },
product_list: this.pos.db.get_product_by_category(0)
});
this.product_list_widget.replace(this.$('.placeholder-ProductListWidget'));
@@ -680,28 +865,53 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
product_list_widget: this.product_list_widget,
});
this.product_categories_widget.replace(this.$('.placeholder-ProductCategoriesWidget'));
+
+ this.action_buttons = {};
+ var classes = module.action_button_classes;
+ for (var i = 0; i < classes.length; i++) {
+ var classe = classes[i];
+ if ( !classe.condition || classe.condition.call(this) ) {
+ var widget = new classe.widget(this,{});
+ widget.appendTo(this.$('.control-buttons'));
+ this.action_buttons[classe.name] = widget;
+ }
+ }
+ if (_.size(this.action_buttons)) {
+ this.$('.control-buttons').removeClass('oe_hidden');
+ }
+ },
+
+ click_product: function(product) {
+ if(product.to_weight && this.pos.config.iface_electronic_scale){
+ this.gui.show_screen('scale',{product: product});
+ }else{
+ this.pos.get_order().add_product(product);
+ }
},
show: function(){
this._super();
- var self = this;
-
this.product_categories_widget.reset_category();
-
- this.pos_widget.order_widget.set_editable(true);
+ this.numpad.state.reset();
},
close: function(){
this._super();
-
- this.pos_widget.order_widget.set_editable(false);
-
- if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
- this.pos_widget.onscreen_keyboard.hide();
+ if(this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard){
+ this.chrome.widget.keyboard.hide();
}
},
});
+ module.Gui.define_screen({name:'products', widget:module.ProductScreenWidget});
+ /*--------------------------------------*\
+ | 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',
@@ -710,8 +920,6 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this.partner_cache = new module.DomCache();
},
- show_leftpane: false,
-
auto_back: true,
show: function(){
@@ -724,12 +932,12 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this.new_client = this.old_client;
this.$('.back').click(function(){
- self.pos_widget.screen_selector.back();
+ self.gui.back();
});
- this.$('.next').click(function(){
+ this.$('.next').click(function(){
self.save_changes();
- self.pos_widget.screen_selector.back();
+ self.gui.back(); // FIXME HUH ?
});
this.$('.new-customer').click(function(){
@@ -753,8 +961,8 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
var search_timeout = null;
- if(this.pos.config.iface_vkeyboard && this.pos_widget.onscreen_keyboard){
- this.pos_widget.onscreen_keyboard.connect(this.$('.searchbox input'));
+ if(this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard){
+ this.chrome.widget.keyboard.connect(this.$('.searchbox input'));
}
this.$('.searchbox input').on('keyup',function(event){
@@ -785,7 +993,7 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
if ( associate_result && customers.length === 1){
this.new_client = customers[0];
this.save_changes();
- this.pos_widget.screen_selector.back();
+ this.gui.back();
}
this.render_list(customers);
}else{
@@ -896,9 +1104,7 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
});
if (!fields.name) {
- this.pos_widget.screen_selector.show_popup('error',{
- message: _t('A Customer Name Is Required'),
- });
+ this.gui.show_popup('error',_t('A Customer Name Is Required'));
return;
}
@@ -914,9 +1120,9 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
self.saved_client_details(partner_id);
},function(err,event){
event.preventDefault();
- self.pos_widget.screen_selector.show_popup('error',{
- 'message':_t('Error: Could not Save Changes'),
- 'comment':_t('Your Internet connection is probably down.'),
+ self.gui.show_popup('error',{
+ 'title': _t('Error: Could not Save Changes'),
+ 'body': _t('Your Internet connection is probably down.'),
});
});
},
@@ -971,9 +1177,9 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
load_image_file: function(file, callback){
var self = this;
if (!file.type.match(/image.*/)) {
- this.pos_widget.screen_selector.show_popup('error',{
- message:_t('Unsupported File Format'),
- comment:_t('Only web-compatible Image formats such as .png or .jpeg are supported'),
+ this.gui.show_popup('error',{
+ title: _t('Unsupported File Format'),
+ body: _t('Only web-compatible Image formats such as .png or .jpeg are supported'),
});
return;
}
@@ -986,9 +1192,9 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
self.resize_image_to_dataurl(img,800,600,callback);
}
reader.onerror = function(){
- self.pos_widget.screen_selector.show_popup('error',{
- message:_t('Could Not Read Image'),
- comment:_t('The provided file could not be read due to an unknown error'),
+ self.gui.show_popup('error',{
+ title :_t('Could Not Read Image'),
+ body :_t('The provided file could not be read due to an unknown error'),
});
};
reader.readAsDataURL(file);
@@ -1082,11 +1288,20 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this._super();
},
});
+ module.Gui.define_screen({name:'clientlist', widget: module.ClientListScreenWidget});
+
+ /*--------------------------------------*\
+ | 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,
- show_leftpane: false,
show: function(){
this._super();
@@ -1157,13 +1372,19 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
}));
},
});
+ module.Gui.define_screen({name:'receipt', widget:module.ReceiptScreenWidget});
+
+ /*--------------------------------------*\
+ | 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',
next_screen: 'receipt',
- show_leftpane: false,
- show_numpad: false,
init: function(parent, options) {
var self = this;
this._super(parent, options);
@@ -1215,42 +1436,12 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
// a string that represents the key pressed.
payment_input: function(input) {
var oldbuf = this.inputbuffer.slice(0);
+ var newbuf = this.gui.numpad_input(this.inputbuffer, input, {'firstinput': this.firstinput});
- if (input === '.') {
- if (this.firstinput) {
- this.inputbuffer = "0.";
- }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
- this.inputbuffer += "0.";
- } else if (this.inputbuffer.indexOf('.') < 0){
- this.inputbuffer = this.inputbuffer + '.';
- }
- } else if (input === 'CLEAR') {
- this.inputbuffer = "";
- } else if (input === 'BACKSPACE') {
- this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
- } else if (input === '+') {
- if ( this.inputbuffer[0] === '-' ) {
- this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
- }
- } else if (input === '-') {
- if ( this.inputbuffer[0] === '-' ) {
- this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
- } else {
- this.inputbuffer = '-' + this.inputbuffer;
- }
- } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
- this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
- } else if (!isNaN(parseInt(input))) {
- if (this.firstinput) {
- this.inputbuffer = '' + input;
- } else {
- this.inputbuffer += input;
- }
- }
-
- this.firstinput = this.inputbuffer.length === 0;
-
- if (this.inputbuffer !== oldbuf) {
+ this.firstinput = (newbuf.length === 0);
+
+ if (newbuf !== this.inputbuffer) {
+ this.inputbuffer = newbuf;
var order = this.pos.get_order();
if (order.selected_paymentline) {
order.selected_paymentline.set_amount(parseFloat(this.inputbuffer));
@@ -1349,10 +1540,10 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
}
},
click_set_customer: function(){
- this.pos_widget.screen_selector.set_current_screen('clientlist');
+ this.gui.show_screen('clientlist');
},
click_back: function(){
- this.pos_widget.screen_selector.set_current_screen('products');
+ this.gui.show_screen('products');
},
renderElement: function() {
var self = this;
@@ -1443,9 +1634,9 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
// FIXME: this check is there because the backend is unable to
// process empty orders. This is not the right place to fix it.
if (order.get_orderlines().length === 0) {
- this.pos_widget.screen_selector.show_popup('error',{
- 'message': _t('Empty Order'),
- 'comment': _t('There must be at least one product in your order before it can be validated'),
+ this.gui.show_popup('error',{
+ 'title': _t('Empty Order'),
+ 'body': _t('There must be at least one product in your order before it can be validated'),
});
return;
}
@@ -1461,9 +1652,9 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
cash = cash || (this.pos.cashregisters[i].journal.type === 'cash');
}
if (!cash) {
- this.pos_widget.screen_selector.show_popup('error',{
- message: _t('Cannot return change without a cash payment method'),
- comment: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'),
+ this.gui.show_popup('error',{
+ title: _t('Cannot return change without a cash payment method'),
+ body: _t('There is no cash payment method available in this point of sale to handle the change.\n\n Please pay the exact amount or add a cash payment method in the point of sale configuration'),
});
return;
}
@@ -1481,17 +1672,17 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
invoiced.fail(function(error){
self.invoicing = false;
if (error === 'error-no-client') {
- self.pos_widget.screen_selector.show_popup('confirm',{
- message: _t('Please select the Customer'),
- comment: _t('You need to select the customer before you can invoice an order.'),
+ self.gui.show_popup('confirm',{
+ 'title': _t('Please select the Customer'),
+ 'body': _t('You need to select the customer before you can invoice an order.'),
confirm: function(){
- self.pos_widget.screen_selector.set_current_screen('clientlist');
+ self.gui.show_screen('clientlist');
},
});
} else {
- self.pos_widget.screen_selector.show_popup('error',{
- message: _t('The order could not be sent'),
- comment: _t('Check your internet connection and try again.'),
+ self.gui.show_popup('error',{
+ 'title': _t('The order could not be sent'),
+ 'body': _t('Check your internet connection and try again.'),
});
}
});
@@ -1506,11 +1697,12 @@ openerp.point_of_sale.load_screens = function load_screens(instance, module){ //
this.print_escpos_receipt();
order.finalize(); //finish order and go back to scan screen
} else {
- this.pos_widget.screen_selector.set_current_screen(this.next_screen);
+ this.gui.show_screen(this.next_screen);
}
}
},
});
+ module.Gui.define_screen({name:'payment', widget:module.PaymentScreenWidget});
-}
+};
diff --git a/addons/point_of_sale/static/src/js/widget_base.js b/addons/point_of_sale/static/src/js/widget_base.js
index 9f44effebb4..a391b3d19d3 100644
--- a/addons/point_of_sale/static/src/js/widget_base.js
+++ b/addons/point_of_sale/static/src/js/widget_base.js
@@ -17,8 +17,9 @@ openerp.point_of_sale.load_basewidget = function load_basewidget(instance, modul
init:function(parent,options){
this._super(parent);
options = options || {};
- this.pos = options.pos || (parent ? parent.pos : undefined);
- this.pos_widget = options.pos_widget || (parent ? parent.pos_widget : undefined);
+ this.pos = options.pos || (parent ? parent.pos : undefined);
+ this.chrome = options.chrome || (parent ? parent.chrome : undefined);
+ this.gui = options.gui || (parent ? parent.gui : undefined);
},
format_currency: function(amount,precision){
var currency = (this.pos && this.pos.currency) ? this.pos.currency : {symbol:'$', position: 'after', rounding: 0.01, decimals: 2};
diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js
deleted file mode 100644
index 51f7d196998..00000000000
--- a/addons/point_of_sale/static/src/js/widgets.js
+++ /dev/null
@@ -1,1292 +0,0 @@
-openerp.point_of_sale.load_widgets = function load_widgets(instance, module){ //module is instance.point_of_sale
- "use strict";
-
- 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.pos.pos_widget.screen_selector.set_current_screen('payment');
- });
- this.$('.set-customer').click(function(){
- self.pos.pos_widget.screen_selector.set_current_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.pos_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.pos_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_editable: function(editable){
- var order = this.pos.get_order();
- if (order) {
- this.editable = editable;
- if (editable) {
- this.enable_numpad();
- }else{
- this.disable_numpad();
- order.deselect_orderline();
- }
- }
- },
- 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.pos_widget.numpad.state.reset();
-
- 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);
-
- },
- });
-
- module.OrderSelectorWidget = module.PosBaseWidget.extend({
- template: 'OrderSelectorWidget',
- init: function(parent, options) {
- this._super(parent, options);
- this.pos.get('orders').bind('add remove change',this.renderElement,this);
- this.pos.bind('change:selectedOrder',this.renderElement,this);
- },
- get_order_by_uid: function(uid) {
- var orders = this.pos.get_order_list();
- for (var i = 0; i < orders.length; i++) {
- if (orders[i].uid === uid) {
- return orders[i];
- }
- }
- return undefined;
- },
- order_click_handler: function(event,$el) {
- var order = this.get_order_by_uid($el.data('uid'));
- if (order) {
- this.pos.set_order(order);
- }
- },
- neworder_click_handler: function(event, $el) {
- this.pos.add_new_order();
- },
- deleteorder_click_handler: function(event, $el) {
- var self = this;
- var order = this.pos.get_order();
- if (!order) {
- return;
- } else if ( !order.is_empty() ){
- this.pos_widget.screen_selector.show_popup('confirm',{
- message: _t('Destroy Current Order ?'),
- comment: _t('You will lose any data associated with the current order'),
- confirm: function(){
- self.pos.delete_current_order();
- },
- });
- } else {
- this.pos.delete_current_order();
- }
- },
- renderElement: function(){
- var self = this;
- this._super();
- this.$('.order-button.select-order').click(function(event){
- self.order_click_handler(event,$(this));
- });
- this.$('.neworder-button').click(function(event){
- self.neworder_click_handler(event,$(this));
- });
- this.$('.deleteorder-button').click(function(event){
- self.deleteorder_click_handler(event,$(this));
- });
- },
- });
-
- 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.pos_widget.onscreen_keyboard){
- this.pos_widget.onscreen_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);
- };
- },
- });
-
- module.UsernameWidget = module.PosBaseWidget.extend({
- template: 'UsernameWidget',
- init: function(parent, options){
- var options = options || {};
- this._super(parent,options);
- },
- set_user_mode: function(mode){
- this.mode = mode;
- this.renderElement();
- },
- renderElement: function(){
- var self = this;
- this._super();
-
- this.$el.click(function(){
- self.click_username();
- });
- },
- click_username: function(){
- var self = this;
- this.pos_widget.select_user({
- 'security': true,
- 'current_user': this.pos.get_cashier(),
- 'message': _t('Change Cashier'),
- }).then(function(user){
- self.pos.set_cashier(user);
- self.renderElement();
- });
- },
- get_name: function(){
- var user = this.pos.cashier || this.pos.user;
- if(user){
- return user.name;
- }else{
- return "";
- }
- },
- });
-
- module.HeaderButtonWidget = module.PosBaseWidget.extend({
- template: 'HeaderButtonWidget',
- init: function(parent, options){
- options = options || {};
- this._super(parent, options);
- this.action = options.action;
- this.label = options.label;
- },
- renderElement: function(){
- var self = this;
- this._super();
- if(this.action){
- this.$el.click(function(){
- self.action();
- });
- }
- },
- show: function(){ this.$el.removeClass('oe_hidden'); },
- 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
- module.DebugWidget = module.PosBaseWidget.extend({
- template: "DebugWidget",
- eans:{
- admin_badge: '0410100000006',
- client_badge: '0420200000004',
- invalid_ean: '1232456',
- soda_33cl: '5449000000996',
- oranges_kg: '2100002031410',
- lemon_price: '2301000001560',
- unknown_product: '9900000000004',
- },
- events:[
- 'open_cashbox',
- 'print_receipt',
- 'scale_read',
- ],
- minimized: false,
- init: function(parent,options){
- this._super(parent,options);
- var self = this;
-
- this.minimized = false;
-
- // for dragging the debug widget around
- this.dragging = false;
- this.dragpos = {x:0, y:0};
-
- function eventpos(event){
- if(event.touches && event.touches[0]){
- return {x: event.touches[0].screenX, y: event.touches[0].screenY};
- }else{
- return {x: event.screenX, y: event.screenY};
- }
- }
-
- this.dragend_handler = function(event){
- self.dragging = false;
- };
- this.dragstart_handler = function(event){
- self.dragging = true;
- self.dragpos = eventpos(event);
- };
- this.dragmove_handler = function(event){
- if(self.dragging){
- var top = this.offsetTop;
- var left = this.offsetLeft;
- var pos = eventpos(event);
- var dx = pos.x - self.dragpos.x;
- var dy = pos.y - self.dragpos.y;
-
- self.dragpos = pos;
-
- this.style.right = 'auto';
- this.style.bottom = 'auto';
- this.style.left = left + dx + 'px';
- this.style.top = top + dy + 'px';
- }
- event.preventDefault();
- event.stopPropagation();
- };
- },
- start: function(){
- var self = this;
-
- this.el.addEventListener('mouseleave', this.dragend_handler);
- this.el.addEventListener('mouseup', this.dragend_handler);
- this.el.addEventListener('touchend', this.dragend_handler);
- this.el.addEventListener('touchcancel',this.dragend_handler);
- this.el.addEventListener('mousedown', this.dragstart_handler);
- this.el.addEventListener('touchstart', this.dragstart_handler);
- this.el.addEventListener('mousemove', this.dragmove_handler);
- this.el.addEventListener('touchmove', this.dragmove_handler);
-
- this.$('.toggle').click(function(){
- var content = self.$('.content');
- var bg = self.$el;
- if(!self.minimized){
- content.animate({'height':'0'},200);
- }else{
- content.css({'height':'auto'});
- }
- self.minimized = !self.minimized;
- });
- this.$('.button.set_weight').click(function(){
- var kg = Number(self.$('input.weight').val());
- if(!isNaN(kg)){
- self.pos.proxy.debug_set_weight(kg);
- }
- });
- this.$('.button.reset_weight').click(function(){
- self.$('input.weight').val('');
- self.pos.proxy.debug_reset_weight();
- });
- this.$('.button.custom_ean').click(function(){
- var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0');
- self.$('input.ean').val(ean);
- self.pos.barcode_reader.scan(ean);
- });
- this.$('.button.reference').click(function(){
- self.pos.barcode_reader.scan(self.$('input.ean').val());
- });
- this.$('.button.show_orders').click(function(){
- self.pos.pos_widget.screen_selector.show_popup('unsent-orders');
- });
- this.$('.button.delete_orders').click(function(){
- self.pos.pos_widget.screen_selector.show_popup('confirm',{
- message: _t('Delete Unsent Orders ?'),
- comment: _t('This operation will permanently destroy all unsent orders from the local storage. You will lose all the data. This operation cannot be undone.'),
- confirm: function(){
- self.pos.db.remove_all_orders();
- self.pos.set({synch: { state:'connected', pending: 0 }});
- },
- });
- });
- this.$('.button.show_unpaid_orders').click(function(){
- self.pos.pos_widget.screen_selector.show_popup('unpaid-orders');
- });
- this.$('.button.delete_unpaid_orders').click(function(){
- self.pos.pos_widget.screen_selector.show_popup('confirm',{
- message: _t('Delete Unpaid Orders ?'),
- comment: _t('This operation will permanently destroy all unpaid orders from all sessions that have been put in the local storage. You will lose all the data and exit the point of sale. This operation cannot be undone.'),
- confirm: function(){
- self.pos.db.remove_all_unpaid_orders();
- window.location = '/';
- },
- });
- });
- _.each(this.eans, function(ean, name){
- self.$('.button.'+name).click(function(){
- self.$('input.ean').val(ean);
- self.pos.barcode_reader.scan(ean);
- });
- });
- _.each(this.events, function(name){
- self.pos.proxy.add_notification(name,function(){
- self.$('.event.'+name).stop().clearQueue().css({'background-color':'#6CD11D'});
- self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000);
- });
- });
- },
- });
-
-// ---------- Main Point of Sale Widget ----------
-
- module.StatusWidget = module.PosBaseWidget.extend({
- status: ['connected','connecting','disconnected','warning'],
- set_status: function(status,msg){
- var self = this;
- for(var i = 0; i < this.status.length; i++){
- this.$('.js_'+this.status[i]).addClass('oe_hidden');
- }
- this.$('.js_'+status).removeClass('oe_hidden');
-
- if(msg){
- this.$('.js_msg').removeClass('oe_hidden').html(msg);
- }else{
- this.$('.js_msg').addClass('oe_hidden').html('');
- }
- },
- });
-
- // this is used to notify the user that data is being synchronized on the network
- module.SynchNotificationWidget = module.StatusWidget.extend({
- template: 'SynchNotificationWidget',
- start: function(){
- var self = this;
- this.pos.bind('change:synch', function(pos,synch){
- self.set_status(synch.state, synch.pending);
- });
- this.$el.click(function(){
- self.pos.push_order();
- });
- },
- });
-
- // this is used to notify the user if the pos is connected to the proxy
- module.ProxyStatusWidget = module.StatusWidget.extend({
- template: 'ProxyStatusWidget',
- set_smart_status: function(status){
- if(status.status === 'connected'){
- var warning = false;
- var msg = ''
- if(this.pos.config.iface_scan_via_proxy){
- var scanner = status.drivers.scanner ? status.drivers.scanner.status : false;
- if( scanner != 'connected' && scanner != 'connecting'){
- warning = true;
- msg += _t('Scanner');
- }
- }
- if( this.pos.config.iface_print_via_proxy ||
- this.pos.config.iface_cashdrawer ){
- var printer = status.drivers.escpos ? status.drivers.escpos.status : false;
- if( printer != 'connected' && printer != 'connecting'){
- warning = true;
- msg = msg ? msg + ' & ' : msg;
- msg += _t('Printer');
- }
- }
- if( this.pos.config.iface_electronic_scale ){
- var scale = status.drivers.scale ? status.drivers.scale.status : false;
- if( scale != 'connected' && scale != 'connecting' ){
- warning = true;
- msg = msg ? msg + ' & ' : msg;
- msg += _t('Scale');
- }
- }
- msg = msg ? msg + ' ' + _t('Offline') : msg;
- this.set_status(warning ? 'warning' : 'connected', msg);
- }else{
- this.set_status(status.status,'');
- }
- },
- start: function(){
- var self = this;
-
- this.set_smart_status(this.pos.proxy.get('status'));
-
- this.pos.proxy.on('change:status',this,function(eh,status){ //FIXME remove duplicate changes
- self.set_smart_status(status.newValue);
- });
-
- this.$el.click(function(){
- self.pos.connect_to_proxy();
- });
- },
- });
-
-
- // The PosWidget is the main widget that contains all other widgets in the PointOfSale.
- // 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)
- // - popups
- // - an onscreen keyboard
- // a screen_selector which controls the switching between screens and the showing/closing of popups
-
- module.PosWidget = module.PosBaseWidget.extend({
- template: 'PosWidget',
- init: function() {
- this._super(arguments[0],{});
-
- this.pos = new module.PosModel(this.session,{pos_widget:this});
- this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically
-
- this.numpad_visible = true;
- this.leftpane_visible = true;
- this.leftpane_width = '440px';
- this.cashier_controls_visible = true;
-
- FastClick.attach(document.body);
-
- },
-
- disable_rubberbanding: function(){
- // prevent the pos body from being scrollable.
- document.body.addEventListener('touchmove',function(event){
- var node = event.target;
- while(node){
- if(node.classList && node.classList.contains('touch-scrollable')){
- return;
- }
- node = node.parentNode;
- }
- event.preventDefault();
- });
- },
-
- start: function() {
- var self = this;
- return self.pos.ready.done(function() {
- // remove default webclient handlers that induce click delay
- $(document).off();
- $(window).off();
- $('html').off();
- $('body').off();
- $(self.$el).parent().off();
- $('document').off();
- $('.oe_web_client').off();
- $('.openerp_webclient_container').off();
-
- self.renderElement();
-
- self.pos.load_orders();
- self.pos.set_start_order();
-
- self.build_widgets();
-
- if(self.pos.config.iface_big_scrollbars){
- self.$el.addClass('big-scrollbars');
- }
-
- self.screen_selector.set_default_screen();
-
- self.pos.barcode_reader.connect();
-
- instance.webclient.set_content_full_screen(true);
-
- if(self.pos.config.iface_fullscreen && document.body.webkitRequestFullscreen && (
- window.screen.availWidth > window.innerWidth ||
- window.screen.availHeight > window.innerHeight )){
- self.screen_selector.show_popup('fullscreen');
- }
- self.loading_hide();
-
- self.pos.push_order();
-
- }).fail(function(err){ // error when loading models data from the backend
- self.loading_error(err);
- });
- },
- loading_error: function(err){
- var self = this;
-
- var message = err.message;
- var comment = err.stack;
-
- if(err.message === 'XmlHttpRequestError '){
- message = 'Network Failure (XmlHttpRequestError)';
- comment = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.';
- }else if(err.message === 'OpenERP Server Error'){
- message = err.data.message;
- comment = err.data.debug;
- }
-
- if( typeof comment !== 'string' ){
- comment = 'Traceback not available.';
- }
-
- var popup = $(QWeb.render('ErrorTracebackPopupWidget',{
- widget: { message: message, comment: comment },
- }));
-
- popup.find('.button').click(function(){
- self.close();
- });
-
- popup.css({ zindex: 9001 });
-
- popup.appendTo(this.$el);
- },
- loading_progress: function(fac){
- this.$('.loader .loader-feedback').removeClass('oe_hidden');
- this.$('.loader .progress').removeClass('oe_hidden').css({'width': ''+Math.floor(fac*100)+'%'});
- },
- loading_message: function(msg,progress){
- this.$('.loader .loader-feedback').removeClass('oe_hidden');
- this.$('.loader .message').text(msg);
- if (typeof progress !== 'undefined') {
- this.loading_progress(progress);
- } else {
- this.$('.loader .progress').addClass('oe_hidden');
- }
- },
- loading_skip: function(callback){
- if(callback){
- this.$('.loader .loader-feedback').removeClass('oe_hidden');
- this.$('.loader .button.skip').removeClass('oe_hidden');
- this.$('.loader .button.skip').off('click');
- this.$('.loader .button.skip').click(callback);
- }else{
- this.$('.loader .button.skip').addClass('oe_hidden');
- }
- },
- loading_hide: function(){
- this.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
- },
- loading_show: function(){
- this.$('.loader').removeClass('oe_hidden').animate({opacity:1},150,'swing');
- },
-
- // A Generic UI that allow to select a user from a list.
- // It returns a deferred that resolves with the selected user
- // upon success. Several options are available :
- // - security: passwords will be asked
- // - only_managers: restricts the list to managers
- // - current_user: password will not be asked if this
- // user is selected.
- // - message: The title of the user selection list.
- select_user: function(options){
- options = options || {};
- var self = this;
- var def = new $.Deferred();
-
- var list = [];
- for (var i = 0; i < this.pos.users.length; i++) {
- var user = this.pos.users[i];
- if (!options.only_managers || user.role === 'manager') {
- list.push({
- 'label': user.name,
- 'item': user,
- });
- }
- }
-
- this.pos_widget.screen_selector.show_popup('selection',{
- 'message': options.message || _t('Select User'),
- list: list,
- confirm: function(user){ def.resolve(user); },
- cancel: function(){ def.reject(); },
- });
-
- return def.then(function(user){
- if (options.security && user !== options.current_user && user.pos_security_pin) {
- var ret = new $.Deferred();
-
- self.pos_widget.screen_selector.show_popup('password',{
- 'message': _t('Password'),
- confirm: function(password) {
- if (password !== user.pos_security_pin) {
- this.pos_widget.screen_selector.show_popup('error',{
- 'message':_t('Password Incorrect'),
- });
- ret.reject();
- } else {
- ret.resolve(user);
- }
- },
- cancel: function(){ ret.reject(); },
- });
-
- return ret;
- } else {
- return user;
- }
- });
- },
-
- // checks if the current user (or the user provided) has manager
- // access rights. If not, a popup is shown allowing the user to
- // temporarily login as an administrator.
- // This method returns a defferred, that succeeds with the
- // manager user when the login is successfull.
- sudo: function(user){
- var def = new $.Deferred();
- user = user || this.pos.get_cashier();
-
- if (user.role === 'manager') {
- return new $.Deferred().resolve(user);
- } else {
- return this.select_user({
- security: true,
- only_managers: true,
- message: _t('Login as a Manager'),
- })
- }
- },
-
- // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
- // startup screen, etc, override this method.
- build_widgets: function() {
- var self = this;
-
- // -------- Screens ---------
-
- this.product_screen = new module.ProductScreenWidget(this,{});
- this.product_screen.appendTo(this.$('.screens'));
-
- this.receipt_screen = new module.ReceiptScreenWidget(this, {});
- this.receipt_screen.appendTo(this.$('.screens'));
-
- this.payment_screen = new module.PaymentScreenWidget(this, {});
- this.payment_screen.appendTo(this.$('.screens'));
-
- this.clientlist_screen = new module.ClientListScreenWidget(this, {});
- this.clientlist_screen.appendTo(this.$('.screens'));
-
- this.scale_screen = new module.ScaleScreenWidget(this,{});
- this.scale_screen.appendTo(this.$('.screens'));
-
-
- // -------- Popups ---------
-
- this.error_popup = new module.ErrorPopupWidget(this, {});
- this.error_popup.appendTo(this.$el);
-
- this.error_barcode_popup = new module.ErrorBarcodePopupWidget(this, {});
- this.error_barcode_popup.appendTo(this.$el);
-
- this.error_traceback_popup = new module.ErrorTracebackPopupWidget(this,{});
- this.error_traceback_popup.appendTo(this.$el);
-
- this.confirm_popup = new module.ConfirmPopupWidget(this,{});
- this.confirm_popup.appendTo(this.$el);
-
- this.fullscreen_popup = new module.FullscreenPopup(this,{});
- this.fullscreen_popup.appendTo(this.$el);
-
- this.selection_popup = new module.SelectionPopupWidget(this,{});
- this.selection_popup.appendTo(this.$el);
-
- this.textinput_popup = new module.TextInputPopupWidget(this,{});
- this.textinput_popup.appendTo(this.$el);
-
- this.textarea_popup = new module.TextAreaPopupWidget(this,{});
- this.textarea_popup.appendTo(this.$el);
-
- this.number_popup = new module.NumberPopupWidget(this,{});
- this.number_popup.appendTo(this.$el);
-
- this.password_popup = new module.PasswordPopupWidget(this,{});
- this.password_popup.appendTo(this.$el);
-
- this.unsent_orders_popup = new module.UnsentOrdersPopupWidget(this,{});
- this.unsent_orders_popup.appendTo(this.$el);
-
- this.unpaid_orders_popup = new module.UnpaidOrdersPopupWidget(this,{});
- this.unpaid_orders_popup.appendTo(this.$el);
-
- // -------- Misc ---------
-
- this.order_selector = new module.OrderSelectorWidget(this,{});
- this.order_selector.replace(this.$('.placeholder-OrderSelectorWidget'));
-
- if(this.pos.config.use_proxy){
- this.proxy_status = new module.ProxyStatusWidget(this,{});
- this.proxy_status.appendTo(this.$('.pos-rightheader'));
- }
-
- this.notification = new module.SynchNotificationWidget(this,{});
- this.notification.appendTo(this.$('.pos-rightheader'));
-
- this.close_button = new module.HeaderButtonWidget(this,{
- label: _t('Close'),
- action: function(){
- var self = this;
- if (!this.confirmed) {
- this.$el.addClass('confirm');
- this.$el.text(_t('Confirm'));
- this.confirmed = setTimeout(function(){
- self.$el.removeClass('confirm');
- self.$el.text(_t('Close'));
- self.confirmed = false;
- },2000);
- } else {
- clearTimeout(this.confirmed);
- this.pos_widget.close();
- }
- },
- });
- this.close_button.appendTo(this.$('.pos-rightheader'));
-
-
-
- this.username = new module.UsernameWidget(this,{});
- this.username.replace(this.$('.placeholder-UsernameWidget'));
-
- this.actionpad = new module.ActionpadWidget(this, {});
- this.actionpad.replace(this.$('.placeholder-ActionpadWidget'));
-
- this.numpad = new module.NumpadWidget(this);
- this.numpad.replace(this.$('.placeholder-NumpadWidget'));
-
- this.order_widget = new module.OrderWidget(this, {});
- this.order_widget.replace(this.$('.placeholder-OrderWidget'));
-
- this.onscreen_keyboard = new module.OnscreenKeyboardWidget(this, {
- 'keyboard_model': 'simple'
- });
- this.onscreen_keyboard.replace(this.$('.placeholder-OnscreenKeyboardWidget'));
-
- // -------- Screen Selector ---------
-
- this.screen_selector = new module.ScreenSelector({
- pos: this.pos,
- screen_set:{
- 'products': this.product_screen,
- 'payment' : this.payment_screen,
- 'scale': this.scale_screen,
- 'receipt' : this.receipt_screen,
- 'clientlist': this.clientlist_screen,
- },
- popup_set:{
- 'error': this.error_popup,
- 'error-barcode': this.error_barcode_popup,
- 'error-traceback': this.error_traceback_popup,
- 'textinput': this.textinput_popup,
- 'textarea': this.textarea_popup,
- 'number': this.number_popup,
- 'password': this.password_popup,
- 'confirm': this.confirm_popup,
- 'fullscreen': this.fullscreen_popup,
- 'selection': this.selection_popup,
- 'unsent-orders': this.unsent_orders_popup,
- 'unpaid-orders': this.unpaid_orders_popup,
- },
- default_screen: 'products',
- default_mode: 'cashier',
- });
-
- if(this.pos.debug){
- this.debug_widget = new module.DebugWidget(this);
- this.debug_widget.appendTo(this.$('.pos-content'));
- }
-
- this.disable_rubberbanding();
-
- },
-
- changed_pending_operations: function () {
- var self = this;
- this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length);
- },
- // shows or hide the numpad and related controls like the paypad.
- set_numpad_visible: function(visible){
- if(visible !== this.numpad_visible){
- this.numpad_visible = visible;
- if(visible){
- this.numpad.show();
- this.actionpad.show();
- }else{
- this.numpad.hide();
- this.actionpad.hide();
- }
- }
- },
- //shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.)
- set_leftpane_visible: function(visible){
- if(visible !== this.leftpane_visible){
- this.leftpane_visible = visible;
- if(visible){
- this.$('.pos-leftpane').removeClass('oe_hidden');
- this.$('.rightpane').css({'left':this.leftpane_width});
- }else{
- this.$('.pos-leftpane').addClass('oe_hidden');
- this.$('.rightpane').css({'left':'0px'});
- }
- }
- },
- close: function() {
- var self = this;
- self.loading_show();
- self.loading_message(_t('Closing ...'));
-
- self.pos.push_order().then(function(){
- return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id'])
- .pipe(function(res) {
- window.location = '/web#action=' + res[0]['res_id'];
- },function(err,event) {
- event.preventDefault();
- self.screen_selector.show_popup('error',{
- 'message': _t('Could not close the point of sale.'),
- 'comment': _t('Your internet connection is probably down.'),
- });
- self.close_button.renderElement();
- });
- });
-
- },
- destroy: function() {
- this.pos.destroy();
- instance.webclient.set_content_full_screen(false);
- this._super();
- }
- });
-}
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 87f6302932f..f960872abc0 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -3,7 +3,7 @@
-->
-
+
@@ -18,35 +18,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
@@ -55,6 +31,10 @@
+
+
+
+
Loading
@@ -67,18 +47,6 @@
- <!--[if IE]>
-
-
-
-
- The Point of Sale is not supported by Microsoft Internet Explorer. Please use
- a modern browser like Mozilla Firefox
- or Google Chrome.
-
-
-
- <![endif]-->
@@ -125,6 +93,12 @@
There are pending operations that could not be saved into the database, are you sure you want to exit?
+
+
+
+
+
+
@@ -224,23 +198,47 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -797,72 +795,13 @@
-
-
-
-
Fullscreen Setup
-
-
-
- The best way to make the point of sale fullscreen on mobile
- devices is to add the point of sale to your home screen. On
- iPhone and iPad this is done by tapping
- and then Add to Homescreen
-
-
- This also works on Android with the Chrome Beta Browser, using the Add to Homescreen option
- in the browser's menu.
-
-
- If you want to work in fullscreen just this time tap the Go Fullscreen button.
-
-
-
-
-
- The best way to make the point of sale fullscreen on desktop
- and laptops is to launch your browser in kiosk mode. Please
- refer to your browser's documentation for the specific
- instructions.
-
-
- If you want to work in fullscreen just this time, click the Go Fullscreen button.
-
-
-
-
-
-
-
-
-
-
-
-
The Order could not be sent to the server for invoicing. Invoices cannot be generated
- in offline mode. Please check your internet connection and try again.