--- /dev/null
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import restaurant
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
--- /dev/null
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+
+{
+ '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:
--- /dev/null
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+# 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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+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,
+ }
+
--- /dev/null
+<?xml version="1.0"?>
+<openerp>
+ <data>
+ <record model="ir.ui.view" id="view_restaurant_printer_form">
+ <field name="name">Order Printer</field>
+ <field name="model">restaurant.printer</field>
+ <field name="arch" type="xml">
+ <form string="POS Printer" version="7.0">
+ <group col="2">
+ <field name="name" />
+ <field name="proxy_ip" />
+ <field name="product_categories_ids" />
+ </group>
+ </form>
+ </field>
+ </record>
+
+ <record model="ir.actions.act_window" id="action_restaurant_printer_form">
+ <field name="name">Order Printers</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">restaurant.printer</field>
+ <field name="view_type">form</field>
+ <field name="view_mode">tree,form</field>
+ <field name="help" type="html">
+ <p class="oe_view_nocontent_create">
+ Click to add a Restaurant Order Printer.
+ </p><p>
+ Order Printers are used by restaurants and bars to print the
+ order updates in the kitchen/bar when the waiter updates the order.
+ </p><p>
+ 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.
+ </p>
+ </field>
+ </record>
+
+ <record model="ir.ui.view" id="view_restaurant_printer">
+ <field name="name">Order Printers</field>
+ <field name="model">restaurant.printer</field>
+ <field name="arch" type="xml">
+ <tree string="Restaurant Order Printers">
+ <field name="name" />
+ <field name="proxy_ip" />
+ <field name="product_categories_ids" />
+ </tree>
+ </field>
+ </record>
+
+ <menuitem
+ parent="point_of_sale.menu_point_config_product"
+ action="action_restaurant_printer_form"
+ id="menu_restaurant_printer_all"
+ sequence="30"
+ groups="point_of_sale.group_pos_manager"/>
+
+ <record model="ir.ui.view" id="view_pos_config_form">
+ <field name="name">pos.config.form.view.inherit</field>
+ <field name="model">pos.config</field>
+ <field name="inherit_id" ref="point_of_sale.view_pos_config_form"></field>
+ <field name="arch" type="xml">
+ <sheet position='inside'>
+ <group string="Bar & Restaurant" >
+ <field name="iface_splitbill" />
+ <field name="iface_printbill" />
+ <field name="printer_ids" />
+ </group>
+ </sheet>
+ </field>
+ </record>
+
+ </data>
+</openerp>
--- /dev/null
+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
--- /dev/null
+/* --- Restaurant Specific CSS --- */
+
--- /dev/null
+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);
+
+};
--- /dev/null
+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');
+ }
+ },
+ });
+
+}
--- /dev/null
+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');
+ }
+ },
+ });
+}
--- /dev/null
+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');
+
+ // for the kitchen printer we assume that everything
+ // has already been sent to the kitchen before splitting
+ // the bill. So we save all changes both for the old
+ // order and for the new one. This is not entirely correct
+ // but avoids flooding the kitchen with unnecessary orders.
+ // Not sure what to do in this case.
+
+ if ( neworder.saveChanges ) {
+ order.saveChanges();
+ neworder.saveChanges();
+ }
+
+ 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');
+ }
+ },
+ });
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="SubmitOrderButton">
+ <span class="control-button order-submit">
+ <i class="fa fa-cutlery"></i>
+ Order
+ </span>
+ </t>
+
+ <t t-name="OrderChangeReceipt">
+ <receipt
+ align='center'
+ width='40'
+ size='double-height'
+ line-ratio='0.6'
+ value-decimals='3'
+ value-thousands-separator=''
+ value-autoint='on'
+ >
+ <div><t t-esc="changes.name" /></div>
+ <br />
+ <br />
+ <t t-if="changes.cancelled.length > 0">
+ <div color='red'>
+ <div bold='on' size='double'>CANCELLED</div>
+ <br />
+ <br />
+ <t t-foreach="changes.cancelled" t-as="change">
+ <line>
+ <left><t t-esc="change.name" /></left>
+ <right><value><t t-esc="change.quantity" /></value></right>
+ </line>
+ </t>
+ <br />
+ <br />
+ </div>
+ </t>
+ <t t-if="changes.new.length > 0">
+ <div bold='on' size='double'>NEW</div>
+ <br />
+ <br />
+ <t t-foreach="changes.new" t-as="change">
+ <line>
+ <left><t t-esc="change.name" /></left>
+ <right><value><t t-esc="change.quantity" /></value></right>
+ </line>
+ </t>
+ <br />
+ <br />
+ </t>
+ </receipt>
+ </t>
+
+</templates>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="PrintBillButton">
+ <span class="control-button order-printbill">
+ <i class="fa fa-print"></i>
+ Bill
+ </span>
+ </t>
+
+ <t t-name="BillReceipt">
+ <receipt align='center' width='40' value-thousands-separator='' >
+ <t t-if='receipt.company.logo'>
+ <img t-att-src='receipt.company.logo' />
+ <br/>
+ </t>
+ <t t-if='!receipt.company.logo'>
+ <h1><t t-esc='receipt.company.name' /></h1>
+ <br/>
+ </t>
+ <div font='b'>
+ <t t-if='receipt.shop.name'>
+ <div><t t-esc='receipt.shop.name' /></div>
+ </t>
+ <t t-if='receipt.company.contact_address'>
+ <div><t t-esc='receipt.company.contact_address' /></div>
+ </t>
+ <t t-if='receipt.company.phone'>
+ <div>Tel:<t t-esc='receipt.company.phone' /></div>
+ </t>
+ <t t-if='receipt.company.vat'>
+ <div>VAT:<t t-esc='receipt.company.vat' /></div>
+ </t>
+ <t t-if='receipt.company.email'>
+ <div><t t-esc='receipt.company.email' /></div>
+ </t>
+ <t t-if='receipt.company.website'>
+ <div><t t-esc='receipt.company.website' /></div>
+ </t>
+ <t t-if='receipt.header'>
+ <div><t t-esc='receipt.header' /></div>
+ </t>
+ <t t-if='receipt.cashier'>
+ <div>--------------------------------</div>
+ <div>Served by <t t-esc='receipt.cashier' /></div>
+ </t>
+ </div>
+ <br /><br />
+
+ <!-- Orderlines -->
+
+ <div line-ratio='0.6'>
+ <t t-foreach='receipt.orderlines' t-as='line'>
+ <t t-set='simple' t-value='line.discount === 0 and line.unit_name === "Unit(s)" and line.quantity === 1' />
+ <t t-if='simple'>
+ <line>
+ <left><t t-esc='line.product_name' /></left>
+ <right><value><t t-esc='line.price_display' /></value></right>
+ </line>
+ </t>
+ <t t-if='!simple'>
+ <line><left><t t-esc='line.product_name' /></left></line>
+ <t t-if='line.discount !== 0'>
+ <line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
+ </t>
+ <line indent='1'>
+ <left>
+ <value value-decimals='3' value-autoint='on'>
+ <t t-esc='line.quantity' />
+ </value>
+ <t t-if='line.unit_name !== "Unit(s)"'>
+ <t t-esc='line.unit_name' />
+ </t>
+ x
+ <value value-decimals='2'>
+ <t t-esc='line.price' />
+ </value>
+ </left>
+ <right>
+ <value><t t-esc='line.price_display' /></value>
+ </right>
+ </line>
+ </t>
+ </t>
+ </div>
+
+ <!-- Subtotal -->
+ <t t-set='taxincluded' t-value='Math.abs(receipt.subtotal - receipt.total_with_tax) <= 0.000001' />
+ <t t-if='!taxincluded'>
+ <line><right>--------</right></line>
+ <line><left>Subtotal</left><right> <value><t t-esc="receipt.subtotal" /></value></right></line>
+ <t t-foreach='receipt.tax_details' t-as='tax'>
+ <line>
+ <left><t t-esc='tax.name' /></left>
+ <right><value><t t-esc='tax.amount' /></value></right>
+ </line>
+ </t>
+ </t>
+
+ <!-- Total -->
+
+ <line><right>--------</right></line>
+ <line size='double-height'>
+ <left><pre> TOTAL</pre></left>
+ <right><value><t t-esc='receipt.total_with_tax' /></value></right>
+ </line>
+ <br/><br/>
+
+ <!-- Extra Payment Info -->
+
+ <t t-if='receipt.total_discount'>
+ <line>
+ <left>Discounts</left>
+ <right><value><t t-esc='receipt.total_discount'/></value></right>
+ </line>
+ </t>
+ <t t-if='taxincluded'>
+ <t t-foreach='receipt.tax_details' t-as='tax'>
+ <line>
+ <left><t t-esc='tax.name' /></left>
+ <right><value><t t-esc='tax.amount' /></value></right>
+ </line>
+ </t>
+ </t>
+
+ <!-- Footer -->
+ <t t-if='receipt.footer'>
+ <br/>
+ <pre><t t-esc='receipt.footer' /></pre>
+ <br/>
+ <br/>
+ </t>
+
+ <br/>
+ <div font='b'>
+ <div><t t-esc='receipt.name' /></div>
+ <div><t t-esc='receipt.date.localestring' /></div>
+ </div>
+
+ </receipt>
+ </t>
+
+</templates>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+ <t t-name="SplitbillButton">
+ <span class="control-button order-split">
+ <i class="fa fa-copy"></i>
+ Split
+ </span>
+ </t>
+
+ <t t-name="SplitOrderline">
+
+ <li t-attf-class="orderline #{ selected ? 'selected' : ''} #{ quantity !== line.get_quantity() ? 'partially' : '' }"
+ t-att-data-id="id">
+ <span class="product-name">
+ <t t-esc="line.get_product().name"/>
+ </span>
+ <span class="price">
+ <t t-esc="widget.format_currency(line.get_display_price())"/>
+ </span>
+ <ul class="info-list">
+ <t t-if="line.get_quantity_str() !== '1'">
+ <li class="info">
+ <t t-if='selected and line.get_unit().groupable'>
+ <em class='big'>
+ <t t-esc='quantity' />
+ </em>
+ /
+ <t t-esc="line.get_quantity_str()" />
+ </t>
+ <t t-if='!(selected and line.get_unit().groupable)'>
+ <em>
+ <t t-esc="line.get_quantity_str()" />
+ </em>
+ </t>
+ <t t-esc="line.get_unit().name" />
+ at
+ <t t-esc="widget.format_currency(line.get_unit_price())" />
+ /
+ <t t-esc="line.get_unit().name" />
+ </li>
+ </t>
+ <t t-if="line.get_discount_str() !== '0'">
+ <li class="info">
+ With a
+ <em>
+ <t t-esc="line.get_discount_str()" />%
+ </em>
+ discount
+ </li>
+ </t>
+ </ul>
+ </li>
+ </t>
+
+ <t t-name="SplitbillScreenWidget">
+ <div class='splitbill-screen screen'>
+ <div class='screen-content'>
+ <div class='top-content'>
+ <span class='button back'>
+ <i class='fa fa-angle-double-left'></i>
+ Back
+ </span>
+ <h1>Bill Splitting</h1>
+ </div>
+ <div class='left-content touch-scrollable scrollable-y'>
+ <div class='order'>
+ <ul class='orderlines'>
+ </ul>
+ </div>
+ </div>
+ <div class='right-content touch-scrollable scrollable-y'>
+ <div class='order-info'>
+ <span class='subtotal'><t t-esc='widget.format_currency(0.0)'/></span>
+ </div>
+ <div class='paymentmethods'>
+ <t t-foreach="widget.pos.cashregisters" t-as="cashregister">
+ <div class='button paymentmethod' t-att-data-id="cashregister.id">
+ <t t-esc='cashregister.journal.name' />
+ </div>
+ </t>
+ </div>
+ </div>
+ </div>
+ </div>
+ </t>
+
+</templates>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- vim:fdn=3:
+-->
+<openerp>
+ <data>
+
+ <template id="index" inherit_id='point_of_sale.index' name="Restaurant Index"><!DOCTYPE html>
+ <xpath expr="//link[@id='pos-stylesheet']" position="after">
+ <link rel="stylesheet" href="/restaurant/static/src/css/restaurant.css" />
+ </xpath>
+ </template>
+
+ <template id="assets_frontend" inherit_id="web.assets_common">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/restaurant/static/src/js/multiprint.js"></script>
+ <script type="text/javascript" src="/restaurant/static/src/js/splitbill.js"></script>
+ <script type="text/javascript" src="/restaurant/static/src/js/printbill.js"></script>
+ <script type="text/javascript" src="/restaurant/static/src/js/main.js"></script>
+ </xpath>
+ </template>
+
+ </data>
+</openerp>
+++ /dev/null
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# 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 <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-import restaurant
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# 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 <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-
-{
- '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:
+++ /dev/null
-# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Management Solution
-# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
-#
-# 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 <http://www.gnu.org/licenses/>.
-#
-##############################################################################
-
-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,
- }
-
+++ /dev/null
-<?xml version="1.0"?>
-<openerp>
- <data>
- <record model="ir.ui.view" id="view_restaurant_printer_form">
- <field name="name">Order Printer</field>
- <field name="model">restaurant.printer</field>
- <field name="arch" type="xml">
- <form string="POS Printer" version="7.0">
- <group col="2">
- <field name="name" />
- <field name="proxy_ip" />
- <field name="product_categories_ids" />
- </group>
- </form>
- </field>
- </record>
-
- <record model="ir.actions.act_window" id="action_restaurant_printer_form">
- <field name="name">Order Printers</field>
- <field name="type">ir.actions.act_window</field>
- <field name="res_model">restaurant.printer</field>
- <field name="view_type">form</field>
- <field name="view_mode">tree,form</field>
- <field name="help" type="html">
- <p class="oe_view_nocontent_create">
- Click to add a Restaurant Order Printer.
- </p><p>
- Order Printers are used by restaurants and bars to print the
- order updates in the kitchen/bar when the waiter updates the order.
- </p><p>
- 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.
- </p>
- </field>
- </record>
-
- <record model="ir.ui.view" id="view_restaurant_printer">
- <field name="name">Order Printers</field>
- <field name="model">restaurant.printer</field>
- <field name="arch" type="xml">
- <tree string="Restaurant Order Printers">
- <field name="name" />
- <field name="proxy_ip" />
- <field name="product_categories_ids" />
- </tree>
- </field>
- </record>
-
- <menuitem
- parent="point_of_sale.menu_point_config_product"
- action="action_restaurant_printer_form"
- id="menu_restaurant_printer_all"
- sequence="30"
- groups="point_of_sale.group_pos_manager"/>
-
- <record model="ir.ui.view" id="view_pos_config_form">
- <field name="name">pos.config.form.view.inherit</field>
- <field name="model">pos.config</field>
- <field name="inherit_id" ref="point_of_sale.view_pos_config_form"></field>
- <field name="arch" type="xml">
- <sheet position='inside'>
- <group string="Bar & Restaurant" >
- <field name="iface_splitbill" />
- <field name="iface_printbill" />
- <field name="printer_ids" />
- </group>
- </sheet>
- </field>
- </record>
-
- </data>
-</openerp>
+++ /dev/null
-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
+++ /dev/null
-/* --- Restaurant Specific CSS --- */
-
+++ /dev/null
-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);
-
-};
+++ /dev/null
-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');
- }
- },
- });
-
-}
+++ /dev/null
-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');
- }
- },
- });
-}
+++ /dev/null
-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');
-
- // for the kitchen printer we assume that everything
- // has already been sent to the kitchen before splitting
- // the bill. So we save all changes both for the old
- // order and for the new one. This is not entirely correct
- // but avoids flooding the kitchen with unnecessary orders.
- // Not sure what to do in this case.
-
- if ( neworder.saveChanges ) {
- order.saveChanges();
- neworder.saveChanges();
- }
-
- 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');
- }
- },
- });
-}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
-
- <t t-name="SubmitOrderButton">
- <span class="control-button order-submit">
- <i class="fa fa-cutlery"></i>
- Order
- </span>
- </t>
-
- <t t-name="OrderChangeReceipt">
- <receipt
- align='center'
- width='40'
- size='double-height'
- line-ratio='0.6'
- value-decimals='3'
- value-thousands-separator=''
- value-autoint='on'
- >
- <div><t t-esc="changes.name" /></div>
- <br />
- <br />
- <t t-if="changes.cancelled.length > 0">
- <div color='red'>
- <div bold='on' size='double'>CANCELLED</div>
- <br />
- <br />
- <t t-foreach="changes.cancelled" t-as="change">
- <line>
- <left><t t-esc="change.name" /></left>
- <right><value><t t-esc="change.quantity" /></value></right>
- </line>
- </t>
- <br />
- <br />
- </div>
- </t>
- <t t-if="changes.new.length > 0">
- <div bold='on' size='double'>NEW</div>
- <br />
- <br />
- <t t-foreach="changes.new" t-as="change">
- <line>
- <left><t t-esc="change.name" /></left>
- <right><value><t t-esc="change.quantity" /></value></right>
- </line>
- </t>
- <br />
- <br />
- </t>
- </receipt>
- </t>
-
-</templates>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
-
- <t t-name="PrintBillButton">
- <span class="control-button order-printbill">
- <i class="fa fa-print"></i>
- Bill
- </span>
- </t>
-
- <t t-name="BillReceipt">
- <receipt align='center' width='40' value-thousands-separator='' >
- <t t-if='receipt.company.logo'>
- <img t-att-src='receipt.company.logo' />
- <br/>
- </t>
- <t t-if='!receipt.company.logo'>
- <h1><t t-esc='receipt.company.name' /></h1>
- <br/>
- </t>
- <div font='b'>
- <t t-if='receipt.shop.name'>
- <div><t t-esc='receipt.shop.name' /></div>
- </t>
- <t t-if='receipt.company.contact_address'>
- <div><t t-esc='receipt.company.contact_address' /></div>
- </t>
- <t t-if='receipt.company.phone'>
- <div>Tel:<t t-esc='receipt.company.phone' /></div>
- </t>
- <t t-if='receipt.company.vat'>
- <div>VAT:<t t-esc='receipt.company.vat' /></div>
- </t>
- <t t-if='receipt.company.email'>
- <div><t t-esc='receipt.company.email' /></div>
- </t>
- <t t-if='receipt.company.website'>
- <div><t t-esc='receipt.company.website' /></div>
- </t>
- <t t-if='receipt.header'>
- <div><t t-esc='receipt.header' /></div>
- </t>
- <t t-if='receipt.cashier'>
- <div>--------------------------------</div>
- <div>Served by <t t-esc='receipt.cashier' /></div>
- </t>
- </div>
- <br /><br />
-
- <!-- Orderlines -->
-
- <div line-ratio='0.6'>
- <t t-foreach='receipt.orderlines' t-as='line'>
- <t t-set='simple' t-value='line.discount === 0 and line.unit_name === "Unit(s)" and line.quantity === 1' />
- <t t-if='simple'>
- <line>
- <left><t t-esc='line.product_name' /></left>
- <right><value><t t-esc='line.price_display' /></value></right>
- </line>
- </t>
- <t t-if='!simple'>
- <line><left><t t-esc='line.product_name' /></left></line>
- <t t-if='line.discount !== 0'>
- <line indent='1'><left>Discount: <t t-esc='line.discount' />%</left></line>
- </t>
- <line indent='1'>
- <left>
- <value value-decimals='3' value-autoint='on'>
- <t t-esc='line.quantity' />
- </value>
- <t t-if='line.unit_name !== "Unit(s)"'>
- <t t-esc='line.unit_name' />
- </t>
- x
- <value value-decimals='2'>
- <t t-esc='line.price' />
- </value>
- </left>
- <right>
- <value><t t-esc='line.price_display' /></value>
- </right>
- </line>
- </t>
- </t>
- </div>
-
- <!-- Subtotal -->
- <t t-set='taxincluded' t-value='Math.abs(receipt.subtotal - receipt.total_with_tax) <= 0.000001' />
- <t t-if='!taxincluded'>
- <line><right>--------</right></line>
- <line><left>Subtotal</left><right> <value><t t-esc="receipt.subtotal" /></value></right></line>
- <t t-foreach='receipt.tax_details' t-as='tax'>
- <line>
- <left><t t-esc='tax.name' /></left>
- <right><value><t t-esc='tax.amount' /></value></right>
- </line>
- </t>
- </t>
-
- <!-- Total -->
-
- <line><right>--------</right></line>
- <line size='double-height'>
- <left><pre> TOTAL</pre></left>
- <right><value><t t-esc='receipt.total_with_tax' /></value></right>
- </line>
- <br/><br/>
-
- <!-- Extra Payment Info -->
-
- <t t-if='receipt.total_discount'>
- <line>
- <left>Discounts</left>
- <right><value><t t-esc='receipt.total_discount'/></value></right>
- </line>
- </t>
- <t t-if='taxincluded'>
- <t t-foreach='receipt.tax_details' t-as='tax'>
- <line>
- <left><t t-esc='tax.name' /></left>
- <right><value><t t-esc='tax.amount' /></value></right>
- </line>
- </t>
- </t>
-
- <!-- Footer -->
- <t t-if='receipt.footer'>
- <br/>
- <pre><t t-esc='receipt.footer' /></pre>
- <br/>
- <br/>
- </t>
-
- <br/>
- <div font='b'>
- <div><t t-esc='receipt.name' /></div>
- <div><t t-esc='receipt.date.localestring' /></div>
- </div>
-
- </receipt>
- </t>
-
-</templates>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<templates id="template" xml:space="preserve">
-
- <t t-name="SplitbillButton">
- <span class="control-button order-split">
- <i class="fa fa-copy"></i>
- Split
- </span>
- </t>
-
- <t t-name="SplitOrderline">
-
- <li t-attf-class="orderline #{ selected ? 'selected' : ''} #{ quantity !== line.get_quantity() ? 'partially' : '' }"
- t-att-data-id="id">
- <span class="product-name">
- <t t-esc="line.get_product().name"/>
- </span>
- <span class="price">
- <t t-esc="widget.format_currency(line.get_display_price())"/>
- </span>
- <ul class="info-list">
- <t t-if="line.get_quantity_str() !== '1'">
- <li class="info">
- <t t-if='selected and line.get_unit().groupable'>
- <em class='big'>
- <t t-esc='quantity' />
- </em>
- /
- <t t-esc="line.get_quantity_str()" />
- </t>
- <t t-if='!(selected and line.get_unit().groupable)'>
- <em>
- <t t-esc="line.get_quantity_str()" />
- </em>
- </t>
- <t t-esc="line.get_unit().name" />
- at
- <t t-esc="widget.format_currency(line.get_unit_price())" />
- /
- <t t-esc="line.get_unit().name" />
- </li>
- </t>
- <t t-if="line.get_discount_str() !== '0'">
- <li class="info">
- With a
- <em>
- <t t-esc="line.get_discount_str()" />%
- </em>
- discount
- </li>
- </t>
- </ul>
- </li>
- </t>
-
- <t t-name="SplitbillScreenWidget">
- <div class='splitbill-screen screen'>
- <div class='screen-content'>
- <div class='top-content'>
- <span class='button back'>
- <i class='fa fa-angle-double-left'></i>
- Back
- </span>
- <h1>Bill Splitting</h1>
- </div>
- <div class='left-content touch-scrollable scrollable-y'>
- <div class='order'>
- <ul class='orderlines'>
- </ul>
- </div>
- </div>
- <div class='right-content touch-scrollable scrollable-y'>
- <div class='order-info'>
- <span class='subtotal'><t t-esc='widget.format_currency(0.0)'/></span>
- </div>
- <div class='paymentmethods'>
- <t t-foreach="widget.pos.cashregisters" t-as="cashregister">
- <div class='button paymentmethod' t-att-data-id="cashregister.id">
- <t t-esc='cashregister.journal.name' />
- </div>
- </t>
- </div>
- </div>
- </div>
- </div>
- </t>
-
-</templates>
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- vim:fdn=3:
--->
-<openerp>
- <data>
-
- <template id="index" inherit_id='point_of_sale.index' name="Restaurant Index"><!DOCTYPE html>
- <xpath expr="//link[@id='pos-stylesheet']" position="after">
- <link rel="stylesheet" href="/restaurant/static/src/css/restaurant.css" />
- </xpath>
- </template>
-
- <template id="assets_frontend" inherit_id="web.assets_common">
- <xpath expr="." position="inside">
- <script type="text/javascript" src="/restaurant/static/src/js/multiprint.js"></script>
- <script type="text/javascript" src="/restaurant/static/src/js/splitbill.js"></script>
- <script type="text/javascript" src="/restaurant/static/src/js/printbill.js"></script>
- <script type="text/javascript" src="/restaurant/static/src/js/main.js"></script>
- </xpath>
- </template>
-
- </data>
-</openerp>