[WIP] pos_restaurant, point_of_sale: almost done
authorFrederic van der Essen <fva@openerp.com / fvdessen+o@gmail.com>
Fri, 26 Sep 2014 14:40:29 +0000 (16:40 +0200)
committerFrédéric van der Essen <fvdessen@gmail.com>
Tue, 25 Nov 2014 17:21:38 +0000 (18:21 +0100)
Conflicts:
addons/point_of_sale/static/src/js/screens.js
addons/point_of_sale/static/src/xml/pos.xml

addons/point_of_sale/static/src/js/models.js
addons/point_of_sale/static/src/js/screens.js
addons/point_of_sale/static/src/js/widgets.js
addons/point_of_sale/static/src/xml/pos.xml
addons/pos_restaurant/static/src/css/restaurant.css
addons/pos_restaurant/static/src/js/floors.js
addons/pos_restaurant/static/src/xml/floors.xml

index e7b401e..d22c35c 100644 (file)
@@ -447,7 +447,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
         // order and a valid selected order
         on_removed_order: function(removed_order,index,reason){
             var order_list = this.get_order_list();
-            if( (reason === 'abandon' || removed_order.temporary) && this.order_list.length > 0){
+            if( (reason === 'abandon' || removed_order.temporary) && order_list.length > 0){
                 // when we intentionally remove an unfinished order, and there is another existing one
                 this.set_order(order_list[index] || order_list[order_list.length -1]);
             }else{
@@ -482,7 +482,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
 
         //removes the current order
         delete_current_order: function(){
-            this.get('selectedOrder').destroy({'reason':'abandon'});
+            var order = this.get_order();
+            if (order) {
+                order.destroy({'reason':'abandon'});
+            }
         },
 
         // saves the order locally and try to send it to the backend. 
index 6219969..46614f1 100644 (file)
@@ -24,17 +24,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
     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.screen_set     = options.screen_set || {};
+            this.popup_set      = options.popup_set || {};
             this.default_screen = options.default_screen;
-
-            this.current_popup = null;
-
-            this.current_mode = options.default_mode || 'cashier';
-
+            this.startup_screen = options.startup_screen;
+            this.current_popup  = null;
+            this.current_mode   = options.default_mode || 'cashier';
             this.current_screen = null; 
 
             for(screen_name in this.screen_set){
@@ -45,9 +40,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
                 this.popup_set[popup_name].hide();
             }
 
-            this.pos.get_order().set_screen_data({
-                'screen': this.default_screen,
-            });
+            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);
         },
index 4f2c069..dba0328 100644 (file)
@@ -115,7 +115,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.client_change_handler = function(event){
                 self.update_summary();
             }
-            this.bind_order_events();
+            if (this.pos.get_order()) {
+                this.bind_order_events();
+            }
         },
         enable_numpad: function(){
             this.disable_numpad();  //ensure we don't register the callbacks twice
@@ -133,12 +135,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             }
         },
         set_editable: function(editable){
-            this.editable = editable;
-            if(editable){
-                this.enable_numpad();
-            }else{
-                this.disable_numpad();
-                this.pos.get_order().deselectLine();
+            var order = this.pos.get_order();
+            if (order) {
+                this.editable = editable;
+                if (editable) {
+                    this.enable_numpad();
+                }else{
+                    this.disable_numpad();
+                    order.deselectLine();
+                }
             }
         },
         set_value: function(val) {
@@ -215,6 +220,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.pos_widget.numpad.state.reset();
 
             var order  = this.pos.get_order();
+            if (!order) {
+                return;
+            }
             var orderlines = order.get('orderLines').models;
 
             var el_str  = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines});
@@ -251,6 +259,64 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
         },
     });
 
+    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.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;
+            console.log('Re-Rendering Orders...',this.pos.get_order_list());
+            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.OrderButtonWidget = module.PosBaseWidget.extend({
         template:'OrderButtonWidget',
         init: function(parent, options) {
@@ -864,34 +930,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
 
                 self.renderElement();
                 
-                self.$('.neworder-button').click(function(){
-                    self.pos.add_new_order();
-                });
-
-                self.$('.deleteorder-button').click(function(){
-                    if( !self.pos.get_order().is_empty() ){
-                        self.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{
-                        self.pos.delete_current_order();
-                    }
-                });
-                
-                //when a new order is created, add an order button widget
-                self.pos.get('orders').bind('add', function(new_order){
-                    var new_order_button = new module.OrderButtonWidget(null, {
-                        order: new_order,
-                        pos: self.pos
-                    });
-                    new_order_button.appendTo(this.$('.orders'));
-                    new_order_button.selectOrder();
-                }, self);
-
                 self.pos.add_new_order();
 
                 self.build_widgets();
@@ -1018,6 +1056,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
 
             // --------  Misc ---------
 
+            this.order_selector = new module.OrderSelectorWidget(this,{});
+            this.order_selector.replace(this.$('.placeholder-OrderSelectorWidget'));
+
             this.notification = new module.SynchNotificationWidget(this,{});
             this.notification.appendTo(this.$('.pos-rightheader'));
 
index 85f6bad..c98c9eb 100644 (file)
                     <span class="placeholder-UsernameWidget"></span>
                 </div>
                 <div class="pos-rightheader">
-                    <div class="order-selector">
-                        <span class="orders touch-scrollable"></span>
-                        <span class="order-button square neworder-button"><i class='fa fa-plus' /></span>
-                        <span class="order-button square deleteorder-button"><i class='fa fa-minus' /></span>
-                    </div>
+                    <span class="placeholder-OrderSelectorWidget"></span>
                     <!-- here goes header buttons -->
                 </div>
             </div>
         </tr>
     </t>
 
-    <t t-name="OrderButtonWidget">
-        <span class="order-button select-order">
-            <t t-if='widget.selected'>
-                <span class='order-sequence'>
-                    <t t-esc='widget.order.sequence_number' />
-                </span>
-                <t t-if="widget.order.get_client()">
-                    <i class='fa fa-user'/>
+    <t t-name="OrderSelectorWidget">
+        <div class="order-selector">
+            <span class="orders touch-scrollable">
+
+                <t t-foreach="widget.pos.get_order_list()" t-as="order">
+                    <t t-if='order === widget.pos.get_order()'>
+                        <span class="order-button select-order selected" t-att-data-uid="order.uid">
+                            <span class='order-sequence'>
+                                <t t-esc='order.sequence_number' />
+                            </span>
+                            <t t-if="order.get_client()">
+                                <i class='fa fa-user'/>
+                            </t>
+                            <t t-esc="(order.get_client() ? order.get_client_name()+' : ':'Unknown Customer: ') + order.get('creationDate').toString('t')"/>
+                        </span>
+                    </t>
+                    <t t-if='order !== widget.pos.get_order()'>
+                        <span class="order-button select-order" t-att-data-uid="order.uid">
+                            <span class='order-sequence'>
+                                <t t-esc='order.sequence_number' />
+                            </span>
+                        </span>
+                    </t>
                 </t>
-                <t t-esc="(widget.order.get_client() ? widget.order.get_client_name()+' : ':'Unknown Customer: ') + moment(widget.order.get('creationDate')).format('LT')"/>
-            </t>
-            <t t-if='!widget.selected'>
-                <span class='order-sequence'>
-                    <t t-esc='widget.order.sequence_number' />
-                </span>
-            </t>
-        </span>
+            
+            </span>
+            <span class="order-button square neworder-button">
+                <i class='fa fa-plus' />
+            </span>
+            <span class="order-button square deleteorder-button">
+                <i class='fa fa-minus' />
+            </span>
+        </div>
     </t>
 
     <t t-name="UsernameWidget">
index 162b29c..8e7a353 100644 (file)
 .floor-map .table .table-handle.left {     left: 0;        }
 .floor-map .table .table-handle.right {    left: 100%;     }
 
+.floor-map .table .order-count {
+    position: absolute;
+    top: 0;
+    left: 50%;
+    background: black;
+    width: 20px;
+    margin-top: -10px;
+    margin-left: -10px;
+    height: 20px;
+    line-height: 20px;
+    border-radius: 10px;
+}
+
 
+/* ------ FLOOR BUTTON IN THE ORDER SELECTOR ------- */
+
+.pos .order-button.floor-button {
+    background: #35D374;
+    font-weight: bold;
+    font-size: 16px;
+    min-width: 128px;
+    padding-left: 16px;
+    padding-right: 16px;
+}
+.pos .order-button.floor-button .fa{
+    font-size: 24px;
+}
 
 
 
index f661de5..e55ba6e 100644 (file)
@@ -1,4 +1,5 @@
 function openerp_restaurant_floors(instance,module){
+    var QWeb = instance.web.qweb;
     var _t = instance.web._t;
 
     // At POS Startup, load the floors, and add them to the pos model
@@ -26,6 +27,7 @@ function openerp_restaurant_floors(instance,module){
                 var floor = self.floors_by_id[tables[i].floor_id[0]];
                 if (floor) {
                     floor.tables.push(tables[i]);
+                    tables[i].floor = floor;
                 }
             }
         },
@@ -212,7 +214,17 @@ function openerp_restaurant_floors(instance,module){
             var model  = new instance.web.Model('restaurant.table');
             var fields = _.find(this.pos.models,function(model){ return model.model === 'restaurant.table'; }).fields;
 
-            model.call('create_from_ui',[this.table]).then(function(table_id){
+            // we need a serializable copy of the table, containing only the fields defined on the server
+            var serializable_table = {};
+            for (var i = 0; i < fields.length; i++) {
+                if (typeof this.table[fields[i]] !== 'undefined') {
+                    serializable_table[fields[i]] = this.table[fields[i]];
+                }
+            }
+            // and the id ...
+            serializable_table.id = this.table.id
+
+            model.call('create_from_ui',[serializable_table]).then(function(table_id){
                 model.query(fields).filter([['id','=',table_id]]).first().then(function(table){
                     for (field in table) {
                         self.table[field] = table[field];
@@ -253,6 +265,7 @@ function openerp_restaurant_floors(instance,module){
         },
         renderElement: function(){
             var self = this;
+            this.order_count = this.pos.get_table_orders(this.table).length;
             this._super();
 
             this.$el.on('mouseup',      function(event){ self.click_handler(event,$(this)); });
@@ -281,16 +294,17 @@ function openerp_restaurant_floors(instance,module){
             this.selected_table = null;
             this.editing = false;
         },
-        show: function(){
-            this._super();
-            this.pos_widget.$('.order-selector').addClass('oe_invisible');
-        },
         hide: function(){
             this._super();
             if (this.editing) { 
                 this.toggle_editing();
             }
-            this.pos_widget.$('.order-selector').removeClass('oe_invisible');
+        },
+        show: function(){
+            this._super();
+            for (var i = 0; i < this.table_widgets.length; i++) { 
+                this.table_widgets[i].renderElement();
+            }
         },
         click_floor_button: function(event,$el){
             var floor = this.pos.floors_by_id[$el.data('id')];
@@ -559,13 +573,35 @@ function openerp_restaurant_floors(instance,module){
         }
     });
 
+    // We need to modify the OrderSelector to hide itself when we're on
+    // the floor plan
+    module.OrderSelectorWidget.include({
+        floor_button_click_handler: function(){
+            this.pos.set_table(null);
+        },
+        renderElement: function(){
+            var self = this;
+            this._super();
+            if (this.pos.get_order()) {
+                if (this.pos.table && this.pos.table.floor) {
+                    this.$('.orders').prepend(QWeb.render('BackToFloorButton',{floor:this.pos.table.floor}));
+                    this.$('.floor-button').click(function(){
+                        self.floor_button_click_handler();
+                    });
+                }
+                this.$el.removeClass('oe_invisible');
+            } else {
+                this.$el.addClass('oe_invisible');
+            }
+        },
+    });
+
     // We need to change the way the regular UI sees the orders, it
     // needs to only see the orders associated with the current table,
     // and when an order is validated, it needs to go back to the floor map.
     //
     // And when we change the table, we must create an order for that table
     // if there is none. 
-
     var _super_posmodel = module.PosModel.prototype;
     module.PosModel = module.PosModel.extend({
         initialize: function(session, attributes) {
@@ -588,6 +624,16 @@ function openerp_restaurant_floors(instance,module){
             }
         },
 
+        // we need to prevent the creation of orders when there is no
+        // table selected.
+        add_new_order: function() {
+            if (this.table) {
+                _super_posmodel.add_new_order.call(this);
+            } else {
+                console.warn("WARNING: orders cannot be created when there is no active table in restaurant mode");
+            }
+        },
+
         // get the list of unpaid orders (associated to the current table)
         get_order_list: function() {    
             var orders = _super_posmodel.get_order_list.call(this);  
@@ -604,12 +650,24 @@ function openerp_restaurant_floors(instance,module){
             }
         },
 
+        // get the list of orders associated to a table. FIXME: should be O(1)
+        get_table_orders: function(table) {
+            var orders   = _super_posmodel.get_order_list.call(this);
+            var t_orders = [];
+            for (var i = 0; i < orders.length; i++) {
+                if (orders[i].table === table) {
+                    t_orders.push(orders[i]);
+                }
+            }
+            return t_orders;
+        },
+
         // When we validate an order we go back to the floor plan. 
         // When we cancel an order and there is multiple orders 
         // on the table, stay on the table.
         on_removed_order: function(removed_order,index,reason){
             var order_list = this.get_order_list();
-            if( (reason === 'abandon' || removed_order.temporary) && this.order_list.length > 0){
+            if( (reason === 'abandon' || removed_order.temporary) && order_list.length > 0){
                 this.set_order(order_list[index] || order_list[order_list.length -1]);
             }else{
                 // back to the floor plan
index 8b2e2a5..16efe1f 100644 (file)
@@ -4,6 +4,9 @@
     <t t-name="TableWidget">
         <t t-if='!widget.selected'>
             <div class='table' t-att-style='widget.table_style_str()'>
+                <t t-if='widget.order_count'>
+                    <span class='order-count'><t t-esc='widget.order_count'/></span>
+                </t>
                 <span class='label'>
                     <t t-esc='widget.table.name' />
                 </span>
         </t>
     </t>
 
+    <t t-name="BackToFloorButton">
+        <span class="order-button floor-button">
+            <i class='fa fa-angle-double-left'/>
+            <t t-esc="floor.name"/>
+        </span>
+    </t>
+
     <t t-name="FloorScreenWidget">
         <div class='floor-screen screen'>
             <div class='screen-content-flexbox'>