From 09bf85ae0226b63578feeaaaf09510ee548cfabd Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Mon, 28 Jul 2014 13:28:39 +0200 Subject: [PATCH] [IMP] restaurant: a module that extends the point of sale with restaurant features --- addons/restaurant/__init__.py | 25 ++ addons/restaurant/__openerp__.py | 55 +++++ addons/restaurant/restaurant.py | 55 +++++ addons/restaurant/restaurant_view.xml | 74 ++++++ .../restaurant/security/ir.model.access.csv | 3 + .../restaurant/static/src/css/restaurant.css | 2 + addons/restaurant/static/src/js/main.js | 11 + addons/restaurant/static/src/js/multiprint.js | 215 ++++++++++++++++++ addons/restaurant/static/src/js/printbill.js | 28 +++ addons/restaurant/static/src/js/splitbill.js | 180 +++++++++++++++ .../restaurant/static/src/xml/multiprint.xml | 55 +++++ .../restaurant/static/src/xml/printbill.xml | 143 ++++++++++++ .../restaurant/static/src/xml/splitbill.xml | 88 +++++++ addons/restaurant/views/templates.xml | 23 ++ 14 files changed, 957 insertions(+) create mode 100644 addons/restaurant/__init__.py create mode 100644 addons/restaurant/__openerp__.py create mode 100644 addons/restaurant/restaurant.py create mode 100644 addons/restaurant/restaurant_view.xml create mode 100644 addons/restaurant/security/ir.model.access.csv create mode 100644 addons/restaurant/static/src/css/restaurant.css create mode 100644 addons/restaurant/static/src/js/main.js create mode 100644 addons/restaurant/static/src/js/multiprint.js create mode 100644 addons/restaurant/static/src/js/printbill.js create mode 100644 addons/restaurant/static/src/js/splitbill.js create mode 100644 addons/restaurant/static/src/xml/multiprint.xml create mode 100644 addons/restaurant/static/src/xml/printbill.xml create mode 100644 addons/restaurant/static/src/xml/splitbill.xml create mode 100644 addons/restaurant/views/templates.xml diff --git a/addons/restaurant/__init__.py b/addons/restaurant/__init__.py new file mode 100644 index 00000000000..381469e26fc --- /dev/null +++ b/addons/restaurant/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import restaurant + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/restaurant/__openerp__.py b/addons/restaurant/__openerp__.py new file mode 100644 index 00000000000..dbf735b800e --- /dev/null +++ b/addons/restaurant/__openerp__.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'Restaurant', + 'version': '1.0', + 'category': 'Point of Sale', + 'sequence': 6, + 'summary': 'Restaurant extensions for the Point of Sale ', + 'description': """ + +======================= + +This module adds several restaurant features to the Point of Sale: +- Bill Printing: Allows you to print a receipt before the order is paid +- Bill Splitting: Allows you to split an order into different orders +- Kitchen Order Printing: allows you to print orders updates to kitchen or bar printers + +""", + 'author': 'OpenERP SA', + 'depends': ['point_of_sale'], + 'data': [ + 'restaurant_view.xml', + 'security/ir.model.access.csv', + 'views/templates.xml', + ], + 'qweb':[ + 'static/src/xml/multiprint.xml', + 'static/src/xml/splitbill.xml', + 'static/src/xml/printbill.xml', + ], + 'installable': True, + 'auto_install': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/restaurant/restaurant.py b/addons/restaurant/restaurant.py new file mode 100644 index 00000000000..c7d02a336c1 --- /dev/null +++ b/addons/restaurant/restaurant.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import logging + +import openerp +from openerp import tools +from openerp.osv import fields, osv +from openerp.tools.translate import _ + +_logger = logging.getLogger(__name__) + +class restaurant_printer(osv.osv): + _name = 'restaurant.printer' + + _columns = { + 'name' : fields.char('Printer Name', size=32, required=True, help='An internal identification of the printer'), + 'proxy_ip': fields.char('Proxy IP Address', size=32, help="The IP Address or hostname of the Printer's hardware proxy"), + 'product_categories_ids': fields.many2many('pos.category','printer_category_rel', 'printer_id','category_id',string='Printed Product Categories'), + } + + _defaults = { + 'name' : 'Printer', + } + +class pos_config(osv.osv): + _inherit = 'pos.config' + _columns = { + 'iface_splitbill': fields.boolean('Bill Splitting', help='Enables Bill Splitting in the Point of Sale'), + 'iface_printbill': fields.boolean('Bill Printing', help='Allows to print the Bill before payment'), + 'printer_ids': fields.many2many('restaurant.printer','pos_config_printer_rel', 'config_id','printer_id',string='Order Printers'), + } + _defaults = { + 'iface_splitbill': False, + 'iface_printbill': False, + } + diff --git a/addons/restaurant/restaurant_view.xml b/addons/restaurant/restaurant_view.xml new file mode 100644 index 00000000000..84adc883b94 --- /dev/null +++ b/addons/restaurant/restaurant_view.xml @@ -0,0 +1,74 @@ + + + + + Order Printer + restaurant.printer + +
+ + + + + +
+
+
+ + + Order Printers + ir.actions.act_window + restaurant.printer + form + tree,form + +

+ Click to add a Restaurant Order Printer. +

+ Order Printers are used by restaurants and bars to print the + order updates in the kitchen/bar when the waiter updates the order. +

+ Each Order Printer has an IP Address that defines the PosBox/Hardware + Proxy where the printer can be found, and a list of product categories. + An Order Printer will only print updates for prodcuts belonging to one of + its categories. +

+
+
+ + + Order Printers + restaurant.printer + + + + + + + + + + + + + pos.config.form.view.inherit + pos.config + + + + + + + + + + + + +
+
diff --git a/addons/restaurant/security/ir.model.access.csv b/addons/restaurant/security/ir.model.access.csv new file mode 100644 index 00000000000..31c7f956f86 --- /dev/null +++ b/addons/restaurant/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_restaurant_printer,restaurant.printer.user,model_restaurant_printer,point_of_sale.group_pos_user,1,0,0,0 +access_restaurant_printer_manager,restaurant.printer.manager,model_restaurant_printer,point_of_sale.group_pos_manager,1,0,0,0 diff --git a/addons/restaurant/static/src/css/restaurant.css b/addons/restaurant/static/src/css/restaurant.css new file mode 100644 index 00000000000..0ffebfc0a46 --- /dev/null +++ b/addons/restaurant/static/src/css/restaurant.css @@ -0,0 +1,2 @@ +/* --- Restaurant Specific CSS --- */ + diff --git a/addons/restaurant/static/src/js/main.js b/addons/restaurant/static/src/js/main.js new file mode 100644 index 00000000000..4b8d25368a5 --- /dev/null +++ b/addons/restaurant/static/src/js/main.js @@ -0,0 +1,11 @@ +openerp.restaurant = function(instance){ + + var module = instance.point_of_sale; + + openerp_restaurant_multiprint(instance,module); + + openerp_restaurant_splitbill(instance,module); + + openerp_restaurant_printbill(instance,module); + +}; diff --git a/addons/restaurant/static/src/js/multiprint.js b/addons/restaurant/static/src/js/multiprint.js new file mode 100644 index 00000000000..14e412a5071 --- /dev/null +++ b/addons/restaurant/static/src/js/multiprint.js @@ -0,0 +1,215 @@ +function openerp_restaurant_multiprint(instance,module){ + var QWeb = instance.web.qweb; + var _t = instance.web._t; + + module.Printer = instance.web.Class.extend(openerp.PropertiesMixin,{ + init: function(parent,options){ + openerp.PropertiesMixin.init.call(this,parent); + var self = this; + options = options || {}; + var url = options.url || 'http://localhost:8069'; + this.connection = new instance.web.Session(undefined,url, { use_cors: true}); + this.host = url; + this.receipt_queue = []; + }, + print: function(receipt){ + var self = this; + if(receipt){ + this.receipt_queue.push(receipt); + } + var aborted = false; + function send_printing_job(){ + if(self.receipt_queue.length > 0){ + var r = self.receipt_queue.shift(); + self.connection.rpc('/hw_proxy/print_xml_receipt',{receipt: r},{timeout: 5000}) + .then(function(){ + send_printing_job(); + },function(){ + self.receipt_queue.unshift(r); + }); + } + } + send_printing_job(); + }, + }); + + module.PosModel.prototype.models.push({ + model: 'restaurant.printer', + fields: ['name','proxy_ip','product_categories_ids'], + domain: null, + loaded: function(self,printers){ + var active_printers = {}; + for (var i = 0; i < self.config.printer_ids.length; i++) { + active_printers[self.config.printer_ids[i]] = true; + } + + self.printers = []; + for(var i = 0; i < printers.length; i++){ + if(active_printers[printers[i].id]){ + var printer = new module.Printer(self,{url:'http://'+printers[i].proxy_ip+':8069'}); + printer.config = printers[i]; + self.printers.push(printer); + } + } + }, + }); + + module.Order = module.Order.extend({ + lineResume: function(){ + var resume = {}; + this.get('orderLines').each(function(item){ + var line = item.export_as_JSON(); + if( typeof resume[line.product_id] === 'undefined'){ + resume[line.product_id] = line.qty; + }else{ + resume[line.product_id] += line.qty; + } + }); + return resume; + }, + saveChanges: function(){ + this.old_resume = this.lineResume(); + }, + computeChanges: function(categories){ + var current = this.lineResume(); + var old = this.old_resume || {}; + var json = this.export_as_JSON(); + var add = []; + var rem = []; + + for( product in current){ + if (typeof old[product] === 'undefined'){ + add.push({ + 'id': product, + 'name': this.pos.db.get_product_by_id(product).name, + 'quantity': current[product], + }); + }else if( old[product] < current[product]){ + add.push({ + 'id': product, + 'name': this.pos.db.get_product_by_id(product).name, + 'quantity': current[product] - old[product], + }); + }else if( old[product] > current[product]){ + rem.push({ + 'id': product, + 'name': this.pos.db.get_product_by_id(product).name, + 'quantity': old[product] - current[product], + }); + } + } + + for( product in old){ + if(typeof current[product] === 'undefined'){ + rem.push({ + 'id': product, + 'name': this.pos.db.get_product_by_id(product).name, + 'quantity': old[product], + }); + } + } + + if(categories && categories.length > 0){ + // filter the added and removed orders to only contains + // products that belong to one of the categories supplied as a parameter + + var self = this; + function product_in_category(product_id){ + var cat = self.pos.db.get_product_by_id(product_id).pos_categ_id[0]; + while(cat){ + for(var i = 0; i < categories.length; i++){ + if(cat === categories[i]){ + return true; + } + } + cat = self.pos.db.get_category_parent_id(cat); + } + return false; + } + + var _add = []; + var _rem = []; + + for(var i = 0; i < add.length; i++){ + if(product_in_category(add[i].id)){ + _add.push(add[i]); + } + } + add = _add; + + for(var i = 0; i < rem.length; i++){ + if(product_in_category(rem[i].id)){ + _rem.push(rem[i]); + } + } + rem = _rem; + } + + return { + 'new': add, + 'cancelled': rem, + 'table': json.table || 'unknown table', + 'name': json.name || 'unknown order', + }; + + }, + printChanges: function(){ + var printers = this.pos.printers; + for(var i = 0; i < printers.length; i++){ + var changes = this.computeChanges(printers[i].config.product_categories_ids); + if ( changes['new'].length > 0 || changes['cancelled'].length > 0){ + var receipt = QWeb.render('OrderChangeReceipt',{changes:changes, widget:this}); + printers[i].print(receipt); + } + } + }, + hasChangesToPrint: function(){ + var printers = this.pos.printers; + for(var i = 0; i < printers.length; i++){ + var changes = this.computeChanges(printers[i].config.product_categories_ids); + if ( changes['new'].length > 0 || changes['cancelled'].length > 0){ + return true; + } + } + return false; + }, + }); + + module.PosWidget.include({ + build_widgets: function(){ + var self = this; + this._super(); + + if(this.pos.printers.length){ + var submitorder = $(QWeb.render('SubmitOrderButton')); + + submitorder.click(function(){ + var order = self.pos.get('selectedOrder'); + if(order.hasChangesToPrint()){ + order.printChanges(); + order.saveChanges(); + self.pos_widget.order_widget.update_summary(); + } + }); + + submitorder.appendTo(this.$('.control-buttons')); + this.$('.control-buttons').removeClass('oe_hidden'); + } + }, + + }); + + module.OrderWidget.include({ + update_summary: function(){ + this._super(); + var order = this.pos.get('selectedOrder'); + + if(order.hasChangesToPrint()){ + this.pos_widget.$('.order-submit').addClass('highlight'); + }else{ + this.pos_widget.$('.order-submit').removeClass('highlight'); + } + }, + }); + +} diff --git a/addons/restaurant/static/src/js/printbill.js b/addons/restaurant/static/src/js/printbill.js new file mode 100644 index 00000000000..89bf9db7bc7 --- /dev/null +++ b/addons/restaurant/static/src/js/printbill.js @@ -0,0 +1,28 @@ +function openerp_restaurant_printbill(instance,module){ + var QWeb = instance.web.qweb; + var _t = instance.web._t; + + module.PosWidget.include({ + build_widgets: function(){ + var self = this; + this._super(); + + if(this.pos.config.iface_printbill){ + var printbill = $(QWeb.render('PrintBillButton')); + + printbill.click(function(){ + var order = self.pos.get('selectedOrder'); + if(order.get('orderLines').models.length > 0){ + var receipt = order.export_for_printing(); + self.pos.proxy.print_receipt(QWeb.render('BillReceipt',{ + receipt: receipt, widget: self, + })); + } + }); + + printbill.appendTo(this.$('.control-buttons')); + this.$('.control-buttons').removeClass('oe_hidden'); + } + }, + }); +} diff --git a/addons/restaurant/static/src/js/splitbill.js b/addons/restaurant/static/src/js/splitbill.js new file mode 100644 index 00000000000..dd90d159af5 --- /dev/null +++ b/addons/restaurant/static/src/js/splitbill.js @@ -0,0 +1,180 @@ +function openerp_restaurant_splitbill(instance, module){ + var QWeb = instance.web.qweb; + var _t = instance.web._t; + + module.SplitbillScreenWidget = module.ScreenWidget.extend({ + template: 'SplitbillScreenWidget', + + show_leftpane: false, + previous_screen: 'products', + + renderElement: function(){ + var self = this; + this._super(); + var order = this.pos.get('selectedOrder'); + if(!order){ + return; + } + var orderlines = order.get('orderLines').models; + for(var i = 0; i < orderlines.length; i++){ + var line = orderlines[i]; + linewidget = $(QWeb.render('SplitOrderline',{ + widget:this, + line:line, + selected: false, + quantity: 0, + id: line.id, + })); + linewidget.data('id',line.id); + this.$('.orderlines').append(linewidget); + } + this.$('.back').click(function(){ + self.pos_widget.screen_selector.set_current_screen(self.previous_screen); + }); + }, + + lineselect: function($el,order,neworder,splitlines,line_id){ + var split = splitlines[line_id] || {'quantity': 0, line: null}; + var line = order.getOrderline(line_id); + + if( !line.get_unit().groupable ){ + if( split.quantity !== line.get_quantity()){ + split.quantity = line.get_quantity(); + }else{ + split.quantity = 0; + } + }else{ + if( split.quantity < line.get_quantity()){ + split.quantity += line.get_unit().rounding; + if(split.quantity > line.get_quantity()){ + split.quantity = line.get_quantity(); + } + }else{ + split.quantity = 0; + } + } + + if( split.quantity ){ + if ( !split.line ){ + split.line = line.clone(); + neworder.addOrderline(split.line); + } + split.line.set_quantity(split.quantity); + }else if( split.line ) { + neworder.removeOrderline(split.line); + split.line = null; + } + + splitlines[line_id] = split; + $el.replaceWith($(QWeb.render('SplitOrderline',{ + widget: this, + line: line, + selected: split.quantity !== 0, + quantity: split.quantity, + id: line_id, + }))); + this.$('.order-info .subtotal').text(this.format_currency(neworder.getSubtotal())); + }, + + pay: function($el,order,neworder,splitlines,cashregister_id){ + var orderlines = order.get('orderLines').models; + var empty = true; + var full = true; + + for(var i = 0; i < orderlines.length; i++){ + var id = orderlines[i].id; + var split = splitlines[id]; + if(!split){ + full = false; + }else{ + if(split.quantity){ + empty = false; + if(split.quantity !== orderlines[i].get_quantity()){ + full = false; + } + } + } + } + + if(empty){ + return; + } + + for(var i = 0; i < this.pos.cashregisters.length; i++){ + if(this.pos.cashregisters[i].id === cashregister_id){ + var cashregister = this.pos.cashregisters[i]; + break; + } + } + + if(full){ + order.addPaymentline(cashregister); + this.pos_widget.screen_selector.set_current_screen('payment'); + }else{ + for(var id in splitlines){ + var split = splitlines[id]; + var line = order.getOrderline(parseInt(id)); + line.set_quantity(line.get_quantity() - split.quantity); + if(Math.abs(line.get_quantity()) < 0.00001){ + order.removeOrderline(line); + } + delete splitlines[id]; + } + neworder.addPaymentline(cashregister); + neworder.set_screen_data('screen','payment'); + this.pos.get('orders').add(neworder); + this.pos.set('selectedOrder',neworder); + } + }, + show: function(){ + var self = this; + this._super(); + this.renderElement(); + + var order = this.pos.get('selectedOrder'); + var neworder = new module.Order({ + pos: this.pos, + temporary: true, + }); + neworder.set('client',order.get('client')); + + var splitlines = {}; + + this.$('.orderlines').on('click','.orderline',function(){ + var id = parseInt($(this).data('id')); + var $el = $(this); + self.lineselect($el,order,neworder,splitlines,id); + }); + + this.$('.paymentmethod').click(function(){ + var id = parseInt($(this).data('id')); + var $el = $(this); + self.pay($el,order,neworder,splitlines,id); + }); + }, + }); + + module.PosWidget.include({ + build_widgets: function(){ + var self = this; + this._super(); + + if(this.pos.config.iface_splitbill){ + this.splitbill_screen = new module.SplitbillScreenWidget(this,{}); + this.splitbill_screen.appendTo(this.$('.screens')); + this.screen_selector.add_screen('splitbill',this.splitbill_screen); + + var splitbill = $(QWeb.render('SplitbillButton')); + + splitbill.click(function(){ + if(self.pos.get('selectedOrder').get('orderLines').models.length > 0){ + self.pos_widget.screen_selector.set_current_screen('splitbill'); + } + }); + + splitbill.appendTo(this.$('.control-buttons')); + this.$('.control-buttons').removeClass('oe_hidden'); + } + }, + }); +} diff --git a/addons/restaurant/static/src/xml/multiprint.xml b/addons/restaurant/static/src/xml/multiprint.xml new file mode 100644 index 00000000000..8f3027dd552 --- /dev/null +++ b/addons/restaurant/static/src/xml/multiprint.xml @@ -0,0 +1,55 @@ + + + + + + + Order + + + + + +
+
+
+ +
+
CANCELLED
+
+
+ + + + + + +
+
+
+
+ +
NEW
+
+
+ + + + + + +
+
+
+
+
+ +
diff --git a/addons/restaurant/static/src/xml/printbill.xml b/addons/restaurant/static/src/xml/printbill.xml new file mode 100644 index 00000000000..8727743a2ad --- /dev/null +++ b/addons/restaurant/static/src/xml/printbill.xml @@ -0,0 +1,143 @@ + + + + + + + Bill + + + + + + + +
+
+ +

+
+
+
+ +
+
+ +
+
+ +
Tel:
+
+ +
VAT:
+
+ +
+
+ +
+
+ +
+
+ +
--------------------------------
+
Served by
+
+
+

+ + + +
+ + + + + + + + + + + + Discount: % + + + + + + + + + + x + + + + + + + + + + +
+ + + + + -------- + Subtotal + + + + + + + + + + + -------- + +
        TOTAL
+ +
+

+ + + + + + Discounts + + + + + + + + + + + + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+ +
diff --git a/addons/restaurant/static/src/xml/splitbill.xml b/addons/restaurant/static/src/xml/splitbill.xml new file mode 100644 index 00000000000..d56f01aaa9b --- /dev/null +++ b/addons/restaurant/static/src/xml/splitbill.xml @@ -0,0 +1,88 @@ + + + + + + + Split + + + + + +
  • + + + + + + +
      + +
    • + + + + + / + + + + + + + + + at + + / + +
    • +
      + +
    • + With a + + % + + discount +
    • +
      +
    +
  • +
    + + +
    +
    +
    + + + Back + +

    Bill Splitting

    +
    +
    +
    +
      +
    +
    +
    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +
    +
    +
    + +
    diff --git a/addons/restaurant/views/templates.xml b/addons/restaurant/views/templates.xml new file mode 100644 index 00000000000..a0cb45cb8e5 --- /dev/null +++ b/addons/restaurant/views/templates.xml @@ -0,0 +1,23 @@ + + + + + + + + + + +