diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py
index ba00cf5709428a55b923982a6d8235c5ae790217..46e4438a12adedd2ad3fd939f856584c94085d68 100644
--- a/addons/point_of_sale/point_of_sale.py
+++ b/addons/point_of_sale/point_of_sale.py
@@ -394,7 +394,7 @@ class pos_session(osv.osv):
         for obj in self.browse(cr, uid, ids, context=context):
             for statement in obj.statement_ids:
                 statement.unlink(context=context)
-        return True
+        return super(pos_session, self).unlink(cr, uid, ids, context=context)
 
 
     def open_cb(self, cr, uid, ids, context=None):
@@ -553,6 +553,7 @@ class pos_order(osv.osv):
         orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
 
         order_ids = []
+
         for tmp_order in orders_to_save:
             to_invoice = tmp_order['to_invoice']
             order = tmp_order['data']
diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml
index 51fa07380767748b751d5c350844441d446c7597..520c22db9c773d44547966080c8758802c2a9716 100644
--- a/addons/point_of_sale/point_of_sale_view.xml
+++ b/addons/point_of_sale/point_of_sale_view.xml
@@ -679,13 +679,33 @@
                             <field name="receipt_header" placeholder="A custom receipt header message"/>
                             <field name="receipt_footer" placeholder="A custom receipt header footage"/>
                         </group>
-                        <group string="Barcode Types" col="4">
-                            <field name="barcode_product" />
-                            <field name="barcode_cashier" />
-                            <field name="barcode_customer" />
-                            <field name="barcode_weight" />
-                            <field name="barcode_discount" />
-                            <field name="barcode_price" />
+                        <group string="Barcode Types" col="1">
+                            <p>
+                                Barcode Patterns allow to match barcodes to actions or to embed information such as price and quantity in the barcode.
+                                Barcode Patterns only work with EAN13 barcodes.
+                            </p>
+                            <p> 
+                                Each type of barcode accepts a list of patterns seprated by commas. A scanned 
+                                barcode will be attributed to a type if it matches one of its patterns. 
+                                The patterns take the form of EAN13 barcodes. Numbers in the pattern must match
+                                the number in the scanned barcode. A 'x' or a '*' in a pattern will match
+                                any one number. If the patterns are shorter than EAN13 barcodes, they are assumed
+                                to be prefixes and match at the beginning. Weight, Price and Discount patterns also
+                                tell how the weight, price or discount is encoded in the barcode. 'N' indicate the
+                                positions where the integer part is en encoded, and 'D' where the decimals are encoded.
+                                If multiple pattern match one barcode, the longest pattern with the less 'x' or '*' is
+                                considered the matching one. If a barcode matches no pattern it will not be found in
+                                the POS.
+                            </p>
+                            <group col="4">
+
+                                <field name="barcode_product" />
+                                <field name="barcode_cashier" />
+                                <field name="barcode_customer" />
+                                <field name="barcode_weight" />
+                                <field name="barcode_discount" />
+                                <field name="barcode_price" />
+                            </group>
                         </group>
                     </sheet>
 
diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css
index 30939636b23b30f4b34fe2bb0683a1b0e3fa4480..0c44d3db3f007ad7e91bc1719ee442cfba179c8f 100644
--- a/addons/point_of_sale/static/src/css/pos.css
+++ b/addons/point_of_sale/static/src/css/pos.css
@@ -1665,6 +1665,16 @@ td {
     font-size: 18px;
     margin: 0px 16px;
 }
+.pos .popup .comment.traceback {
+    height: 264px;
+    overflow: auto;
+    font-size: 14px;
+    text-align: left;
+    font-family: 'Inconsolata';
+    -webkit-user-select: text;
+       -moz-user-select: text;
+            user-select: text;
+}
 .pos .popup .footer{
     position:absolute;
     bottom:0;
diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js
index 38230fb0ca24c8840f5438b7884b02ee1d3c071b..08d4f879968a08be7dc66041e2e9aa5c9b4fc5ba 100644
--- a/addons/point_of_sale/static/src/js/db.js
+++ b/addons/point_of_sale/static/src/js/db.js
@@ -345,6 +345,9 @@ function openerp_pos_db(instance, module){
             });
             this.save('orders',orders);
         },
+        remove_all_orders: function(){
+            this.save('orders',[]);
+        },
         get_orders: function(){
             return this.load('orders',[]);
         },
diff --git a/addons/point_of_sale/static/src/js/main.js b/addons/point_of_sale/static/src/js/main.js
index 90fa3567cda2abe2b6fff50275ff8f27456b0fa8..373a5f66761b37a6ac87792671adc42d1fb96e91 100644
--- a/addons/point_of_sale/static/src/js/main.js
+++ b/addons/point_of_sale/static/src/js/main.js
@@ -19,8 +19,6 @@ openerp.point_of_sale = function(instance) {
     
     openerp_pos_widgets(instance,module);    // import pos_widgets.js
 
-    openerp_pos_tests(instance,module);      // import pos_tests.js
-
     instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.PosWidget');
 };
 
diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js
index 92276db60ded76145cb981b30e7f42187124b6fe..d351fee5ff109031bb58320bd9a0b67230d58152 100644
--- a/addons/point_of_sale/static/src/js/models.js
+++ b/addons/point_of_sale/static/src/js/models.js
@@ -114,180 +114,250 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
             return done;
         },
 
-        // helper function to load data from the server
+        // helper function to load data from the server. Obsolete use the models loader below.
         fetch: function(model, fields, domain, ctx){
             this._load_progress = (this._load_progress || 0) + 0.05; 
             this.pos_widget.loading_message(_t('Loading')+' '+model,this._load_progress);
             return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all()
         },
 
-        // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded. 
-        load_server_data: function(){
-            var self = this;
-
-            var loaded = self.fetch('res.users',['name','company_id'],[['id','=',this.session.uid]]) 
-                .then(function(users){
-                    self.user = users[0];
-
-                    return self.fetch('res.company',
-                    [
-                        'currency_id',
-                        'email',
-                        'website',
-                        'company_registry',
-                        'vat',
-                        'name',
-                        'phone',
-                        'partner_id',
-                    ],
-                    [['id','=',users[0].company_id[0]]],
-                    {show_address_only: true});
-                }).then(function(companies){
-                    self.company = companies[0];
-
-                    return self.fetch('product.uom', null, null);
-                }).then(function(units){
-                    self.units = units;
-                    var units_by_id = {};
-                    for(var i = 0, len = units.length; i < len; i++){
-                        units_by_id[units[i].id] = units[i];
-                        units[i].groupable = ( units[i].category_id[0] === 1 );
-                        units[i].is_unit   = ( units[i].id === 1 );
+        // Server side model loaders. This is the list of the models that need to be loaded from
+        // the server. The models are loaded one by one by this list's order. The 'loaded' callback
+        // is used to store the data in the appropriate place once it has been loaded. This callback
+        // can return a deferred that will pause the loading of the next module. 
+        // a shared temporary dictionary is available for loaders to communicate private variables
+        // used during loading such as object ids, etc. 
+        models: [
+        {
+            model:  'res.users',
+            fields: ['name','company_id'],
+            domain: function(self){ return [['id','=',self.session.uid]]; },
+            loaded: function(self,users){ self.user = users[0]; },
+        },{ 
+            model:  'res.company',
+            fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' ],
+            domain: function(self){ return [['id','=',self.user.company_id[0]]]; },
+            loaded: function(self,companies){ self.company = companies[0]; },
+        },{
+            model:  'product.uom',
+            fields: [],
+            domain: null,
+            loaded: function(self,units){
+                self.units = units;
+                var units_by_id = {};
+                for(var i = 0, len = units.length; i < len; i++){
+                    units_by_id[units[i].id] = units[i];
+                    units[i].groupable = ( units[i].category_id[0] === 1 );
+                    units[i].is_unit   = ( units[i].id === 1 );
+                }
+                self.units_by_id = units_by_id;
+            }
+        },{
+            model:  'res.users',
+            fields: ['name','ean13'],
+            domain: null,
+            loaded: function(self,users){ self.users = users; },
+        },{
+            model:  'res.partner',
+            fields: ['name','street','city','country_id','phone','zip','mobile','email','ean13'],
+            domain: null,
+            loaded: function(self,partners){
+                self.partners = partners;
+                self.db.add_partners(partners);
+            },
+        },{
+            model:  'account.tax',
+            fields: ['name','amount', 'price_include', 'type'],
+            domain: null,
+            loaded: function(self,taxes){ self.taxes = taxes; },
+        },{
+            model:  'pos.session',
+            fields: ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number'],
+            domain: function(self){ return [['state','=','opened'],['user_id','=',self.session.uid]]; },
+            loaded: function(self,pos_sessions){ self.pos_session = pos_sessions[0]; },
+        },{
+            model: 'pos.config',
+            fields: [],
+            domain: function(self){ return [['id','=', self.pos_session.config_id[0]]]; },
+            loaded: function(self,configs){
+                self.config = configs[0];
+                self.config.use_proxy = self.config.iface_payment_terminal || 
+                                        self.config.iface_electronic_scale ||
+                                        self.config.iface_print_via_proxy  ||
+                                        self.config.iface_scan_via_proxy   ||
+                                        self.config.iface_cashdrawer;
+                
+                self.barcode_reader.add_barcode_patterns({
+                    'product':  self.config.barcode_product,
+                    'cashier':  self.config.barcode_cashier,
+                    'client':   self.config.barcode_customer,
+                    'weight':   self.config.barcode_weight,
+                    'discount': self.config.barcode_discount,
+                    'price':    self.config.barcode_price,
+                });
+            },
+        },{
+            model: 'stock.location',
+            fields: [],
+            domain: function(self){ return [['id','=', self.config.stock_location_id[0]]]; },
+            loaded: function(self, locations){ self.shop = locations[0]; },
+        },{
+            model:  'product.pricelist',
+            fields: ['currency_id'],
+            domain: function(self){ return [['id','=',self.config.pricelist_id[0]]]; },
+            loaded: function(self, pricelists){ self.pricelist = pricelists[0]; },
+        },{
+            model: 'res.currency',
+            fields: ['symbol','position','rounding','accuracy'],
+            domain: function(self){ return [['id','=',self.pricelist.currency_id[0]]]; },
+            loaded: function(self, currencies){
+                self.currency = currencies[0];
+            },
+        },{
+            model: 'product.packaging',
+            fields: ['ean','product_tmpl_id'],
+            domain: null,
+            loaded: function(self, packagings){ 
+                self.db.add_packagings(packagings);
+            },
+        },{
+            model:  'pos.category',
+            fields: ['id','name','parent_id','child_id','image'],
+            domain: null,
+            loaded: function(self, categories){
+                self.db.add_categories(categories);
+            },
+        },{
+            model:  'product.product',
+            fields: ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 'variants',
+                     'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
+                     'product_tmpl_id'],
+            domain:  function(self){ return [['sale_ok','=',true],['available_in_pos','=',true]]; },
+            context: function(self){ return { pricelist: self.pricelist.id }; },
+            loaded: function(self, products){
+                self.db.add_products(products);
+            },
+        },{
+            model:  'account.bank.statement',
+            fields: ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
+            domain: function(self){ return [['state', '=', 'open'],['pos_session_id', '=', self.pos_session.id]]; },
+            loaded: function(self, bankstatements, tmp){
+                self.bankstatements = bankstatements;
+
+                tmp.journals = [];
+                _.each(bankstatements,function(statement){
+                    tmp.journals.push(statement.journal_id[0]);
+                });
+            },
+        },{
+            model:  'account.journal',
+            fields: [],
+            domain: function(self,tmp){ return [['id','in',tmp.journals]]; },
+            loaded: function(self, journals){
+                self.journals = journals;
+
+                // associate the bank statements with their journals. 
+                var bankstatements = self.bankstatements;
+                for(var i = 0, ilen = bankstatements.length; i < ilen; i++){
+                    for(var j = 0, jlen = journals.length; j < jlen; j++){
+                        if(bankstatements[i].journal_id[0] === journals[j].id){
+                            bankstatements[i].journal = journals[j];
+                            bankstatements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
+                        }
+                    }
+                }
+                self.cashregisters = bankstatements;
+            },
+        },{
+            loaded: function(self){
+                self.company_logo = new Image();
+                self.company_logo.crossOrigin = 'anonymous';
+                var  logo_loaded = new $.Deferred();
+                self.company_logo.onload = function(){
+                    var img = self.company_logo;
+                    var ratio = 1;
+                    var targetwidth = 300;
+                    var maxheight = 150;
+                    if( img.width !== targetwidth ){
+                        ratio = targetwidth / img.width;
+                    }
+                    if( img.height * ratio > maxheight ){
+                        ratio = maxheight / img.height;
                     }
-                    self.units_by_id = units_by_id;
+                    var width  = Math.floor(img.width * ratio);
+                    var height = Math.floor(img.height * ratio);
+                    var c = document.createElement('canvas');
+                        c.width  = width;
+                        c.height = height
+                    var ctx = c.getContext('2d');
+                        ctx.drawImage(self.company_logo,0,0, width, height);
                     
-                    return self.fetch('res.users', ['name','ean13'], [['ean13', '!=', false]]);
-                }).then(function(users){
-                    self.users = users;
+                    self.company_logo_base64 = c.toDataURL();
+                    window.logo64 = self.company_logo_base64;
+                    logo_loaded.resolve();
+                };
+                self.company_logo.onerror = function(){
+                    logo_loaded.reject();
+                };
+                self.company_logo.src = window.location.origin + '/web/binary/company_logo';
 
-                    return self.fetch('res.partner', ['name','street','city','country_id','phone','zip','mobile','email','ean13']);
-                }).then(function(partners){
-                    self.partners = partners;
-                    self.db.add_partners(partners);
-
-                    return self.fetch('account.tax', ['name','amount', 'price_include', 'type']);
-                }).then(function(taxes){
-                    self.taxes = taxes;
-
-                    return self.fetch(
-                        'pos.session', 
-                        ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number'],
-                        [['state', '=', 'opened'], ['user_id', '=', self.session.uid]]
-                    );
-                }).then(function(pos_sessions){
-                    self.pos_session = pos_sessions[0];
+                return logo_loaded;
+            },
+        },
+        ],
 
-                    return self.fetch('pos.config',[],[['id','=', self.pos_session.config_id[0]]]);
-                }).then(function(configs){
-                    self.config = configs[0];
-                    self.config.use_proxy = self.config.iface_payment_terminal || 
-                                            self.config.iface_electronic_scale ||
-                                            self.config.iface_print_via_proxy  ||
-                                            self.config.iface_scan_via_proxy   ||
-                                            self.config.iface_cashdrawer;
+        // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded. 
+        load_server_data: function(){
+            var self = this;
+            var loaded = new $.Deferred();
+            var progress = 0;
+            var progress_step = 1.0 / self.models.length;
+            var tmp = {}; // this is used to share a temporary state between models loaders
+
+            function load_model(index){
+                if(index >= self.models.length){
+                    loaded.resolve();
+                }else{
+                    var model = self.models[index];
+                    self.pos_widget.loading_message(_t('Loading')+' '+(model.model || ''), progress);
+                    var fields =  typeof model.fields === 'function'  ? model.fields(self,tmp)  : model.fields;
+                    var domain =  typeof model.domain === 'function'  ? model.domain(self,tmp)  : model.domain;
+                    var context = typeof model.context === 'function' ? model.context(self,tmp) : model.context; 
+                    progress += progress_step;
                     
-                    self.barcode_reader.add_barcode_patterns({
-                        'product':  self.config.barcode_product,
-                        'cashier':  self.config.barcode_cashier,
-                        'client':   self.config.barcode_customer,
-                        'weight':   self.config.barcode_weight,
-                        'discount': self.config.barcode_discount,
-                        'price':    self.config.barcode_price,
-                    });
-                    return self.fetch('stock.location',[],[['id','=', self.config.stock_location_id[0]]]);
-                }).then(function(shops){
-                    self.shop = shops[0];
-
-                    return self.fetch('product.pricelist',['currency_id'],[['id','=',self.config.pricelist_id[0]]]);
-                }).then(function(pricelists){
-                    self.pricelist = pricelists[0];
-
-                    return self.fetch('res.currency',['symbol','position','rounding','accuracy'],[['id','=',self.pricelist.currency_id[0]]]);
-                }).then(function(currencies){
-                    self.currency = currencies[0];
-
-                    return self.fetch('product.packaging',['ean','product_tmpl_id']);
-                }).then(function(packagings){
-                    self.db.add_packagings(packagings);
-
-                    return self.fetch('pos.category', ['id','name','parent_id','child_id','image']);
-                }).then(function(categories){
-                    self.db.add_categories(categories);
-
-                    return self.fetch(
-                        'product.product',
-                        ['name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 'variants',
-                         'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
-                         'product_tmpl_id'],
-                        [['sale_ok','=',true],['available_in_pos','=',true]],
-                        {pricelist: self.pricelist.id} // context for price
-                    );
-                }).then(function(products){
-                    self.db.add_products(products);
-
-                    return self.fetch(
-                        'account.bank.statement',
-                        ['account_id','currency','journal_id','state','name','user_id','pos_session_id'],
-                        [['state','=','open'],['pos_session_id', '=', self.pos_session.id]]
-                    );
-                }).then(function(bankstatements){
-                    var journals = [];
-                    _.each(bankstatements,function(statement) {
-                        journals.push(statement.journal_id[0]);
-                    });
-                    self.bankstatements = bankstatements;
-                    return self.fetch('account.journal', undefined, [['id','in', journals]]);
-                }).then(function(journals){
-                    self.journals = journals; 
-
-                    // associate the bank statements with their journals. 
-                    var bankstatements = self.bankstatements;
-                    for(var i = 0, ilen = bankstatements.length; i < ilen; i++){
-                        for(var j = 0, jlen = journals.length; j < jlen; j++){
-                            if(bankstatements[i].journal_id[0] === journals[j].id){
-                                bankstatements[i].journal = journals[j];
-                                bankstatements[i].self_checkout_payment_method = journals[j].self_checkout_payment_method;
-                            }
+                    if( model.model ){
+                        new instance.web.Model(model.model).query(fields).filter(domain).context(context).all()
+                            .then(function(result){
+                                try{    // catching exceptions in model.loaded(...)
+                                    $.when(model.loaded(self,result,tmp))
+                                        .then(function(){ load_model(index + 1); },
+                                              function(err){ loaded.reject(err); });
+                                }catch(err){
+                                    loaded.reject(err);
+                                }
+                            },function(err){
+                                loaded.reject(err);
+                            });
+                    }else if( model.loaded ){
+                        try{    // catching exceptions in model.loaded(...)
+                            $.when(model.loaded(self,tmp))
+                                .then(  function(){ load_model(index +1); },
+                                        function(err){ loaded.reject(err); });
+                        }catch(err){
+                            loaded.reject(err);
                         }
+                    }else{
+                        load_model(index + 1);
                     }
-                    self.cashregisters = bankstatements;
-
-                    // Load the company Logo
+                }
+            }
 
-                    self.company_logo = new Image();
-                    self.company_logo.crossOrigin = 'anonymous';
-                    var  logo_loaded = new $.Deferred();
-                    self.company_logo.onload = function(){
-                        var img = self.company_logo;
-                        var ratio = 1;
-                        var targetwidth = 300;
-                        var maxheight = 150;
-                        if( img.width !== targetwidth ){
-                            ratio = targetwidth / img.width;
-                        }
-                        if( img.height * ratio > maxheight ){
-                            ratio = maxheight / img.height;
-                        }
-                        var width  = Math.floor(img.width * ratio);
-                        var height = Math.floor(img.height * ratio);
-                        var c = document.createElement('canvas');
-                            c.width  = width;
-                            c.height = height
-                        var ctx = c.getContext('2d');
-                            ctx.drawImage(self.company_logo,0,0, width, height);
-                        
-                        self.company_logo_base64 = c.toDataURL();
-                        window.logo64 = self.company_logo_base64;
-                        logo_loaded.resolve();
-                    };
-                    self.company_logo.onerror = function(){
-                        logo_loaded.reject();
-                    };
-                    self.company_logo.src = window.location.origin + '/web/binary/company_logo';
+            try{
+                load_model(0);
+            }catch(err){
+                loaded.reject(err);
+            }
 
-                    return logo_loaded;
-                });
-        
             return loaded;
         },
 
@@ -324,20 +394,20 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
         // it returns a deferred that succeeds after having tried to send the order and all the other pending orders.
         push_order: function(order) {
             var self = this;
-            this.proxy.log('push_order',order.export_as_JSON());
-            var order_id = this.db.add_order(order.export_as_JSON());
-            var pushed = new $.Deferred();
 
-            this.set('synch',{state:'connecting', pending:self.db.get_orders().length});
+            if(order){
+                this.proxy.log('push_order',order.export_as_JSON());
+                this.db.add_order(order.export_as_JSON());
+            }
+            
+            var pushed = new $.Deferred();
 
             this.flush_mutex.exec(function(){
-                var flushed = self._flush_all_orders();
+                var flushed = self._flush_orders(self.db.get_orders());
 
-                flushed.always(function(){
+                flushed.always(function(ids){
                     pushed.resolve();
                 });
-
-                return flushed;
             });
             return pushed;
         },
@@ -361,8 +431,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
 
             var order_id = this.db.add_order(order.export_as_JSON());
 
-            this.set('synch',{state:'connecting', pending:self.db.get_orders().length});
-
             this.flush_mutex.exec(function(){
                 var done = new $.Deferred(); // holds the mutex
 
@@ -373,7 +441,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
                 // things will happen as a duplicate will be sent next time
                 // so we must make sure the server detects and ignores duplicated orders
 
-                var transfer = self._flush_order(order_id, {timeout:30000, to_invoice:true});
+                var transfer = self._flush_orders([self.db.get_order(order_id)], {timeout:30000, to_invoice:true});
                 
                 transfer.fail(function(){
                     invoiced.reject('error-transfer');
@@ -382,10 +450,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
 
                 // on success, get the order id generated by the server
                 transfer.pipe(function(order_server_id){    
+
                     // generate the pdf and download it
                     self.pos_widget.do_action('point_of_sale.pos_invoice_report',{additional_context:{ 
                         active_ids:order_server_id,
                     }});
+
                     invoiced.resolve();
                     done.resolve();
                 });
@@ -397,62 +467,33 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
             return invoiced;
         },
 
-        // attemps to send all pending orders ( stored in the pos_db ) to the server,
-        // and remove the successfully sent ones from the db once
-        // it has been confirmed that they have been sent correctly.
-        flush: function() {
+        // wrapper around the _save_to_server that updates the synch status widget
+        _flush_orders: function(orders, options) {
             var self = this;
-            var flushed = new $.Deferred();
-
-            this.flush_mutex.exec(function(){
-                var done = new $.Deferred();
 
-                self._flush_all_orders()
-                    .done(  function(){ flushed.resolve();})
-                    .fail(  function(){ flushed.reject(); })
-                    .always(function(){ done.resolve();   });
+            this.set('synch',{ state: 'connecting', pending: orders.length});
 
-                return done;
-            });
-
-            return flushed;
-        },
-
-        // attempts to send the locally stored order of id 'order_id'
-        // the sending is asynchronous and can take some time to decide if it is successful or not
-        // it is therefore important to only call this method from inside a mutex
-        // this method returns a deferred indicating wether the sending was successful or not
-        // there is a timeout parameter which is set to 2 seconds by default. 
-        _flush_order: function( order_id, options) {
-            return this._flush_all_orders([this.db.get_order(order_id)], options);
-        },
-        
-        // attempts to send all the locally stored orders. As with _flush_order, it should only be
-        // called from within a mutex. 
-        // this method returns a deferred that always succeeds when all orders have been tried to be sent,
-        // even if none of them could actually be sent. 
-        _flush_all_orders: function () {
-            var self = this;
-            self.set('synch', {
-                state: 'connecting',
-                pending: self.get('synch').pending
-            });
-            return self._save_to_server(self.db.get_orders()).done(function () {
+            return self._save_to_server(orders, options).done(function (server_ids) {
                 var pending = self.db.get_orders().length;
+
                 self.set('synch', {
                     state: pending ? 'connecting' : 'connected',
                     pending: pending
                 });
+
+                return server_ids;
             });
         },
 
         // send an array of orders to the server
         // available options:
         // - timeout: timeout for the rpc call in ms
+        // returns a deferred that resolves with the list of
+        // server generated ids for the sent orders
         _save_to_server: function (orders, options) {
             if (!orders || !orders.length) {
                 var result = $.Deferred();
-                result.resolve();
+                result.resolve([]);
                 return result;
             }
                 
@@ -474,11 +515,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal
                     shadow: !options.to_invoice,
                     timeout: timeout
                 }
-            ).then(function () {
+            ).then(function (server_ids) {
                 _.each(orders, function (order) {
                     self.db.remove_order(order.id);
                 });
-            }).fail(function (unused, event){
+                return server_ids;
+            }).fail(function (error, event){
+                if(error.code === 200 ){    // Business Logic Error, not a connection problem
+                    self.pos_widget.screen_selector.show_popup('error-traceback',{
+                        message: error.data.message,
+                        comment: error.data.debug
+                    });
+                }
                 // prevent an error popup creation by the rpc failure
                 // we want the failure to be silent as we send the orders in the background
                 event.preventDefault();
diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js
index 2f2f5e57312a87d058d2f9e358c8d8dbe08952e3..169940e50613cc0e47491adc8908e8969d1942f9 100644
--- a/addons/point_of_sale/static/src/js/screens.js
+++ b/addons/point_of_sale/static/src/js/screens.js
@@ -73,8 +73,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         load_saved_screen:  function(){
             this.close_popup();
             var selectedOrder = this.pos.get('selectedOrder');
-            // this.set_current_screen(selectedOrder.get_screen_data('screen') || this.default_screen,null,'refresh');
-            this.set_current_screen(this.default_screen,null,'refresh');
+            // FIXME : this changing screen behaviour is sometimes confusing ... 
+            this.set_current_screen(selectedOrder.get_screen_data('screen') || this.default_screen,null,'refresh');
+            //this.set_current_screen(this.default_screen,null,'refresh');
             
         },
         set_user_mode: function(user_mode){
@@ -348,6 +349,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             var self = this;
             this._super();
 
+            $('body').append('<audio src="/point_of_sale/static/src/sounds/error.wav" autoplay="true"></audio>');
+
             if( text && (text.message || text.comment) ){
                 this.$('.message').text(text.message);
                 this.$('.comment').text(text.comment);
@@ -365,6 +368,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         },
     });
 
+    module.ErrorTracebackPopupWidget = module.ErrorPopupWidget.extend({
+        template:'ErrorTracebackPopupWidget',
+    });
 
     module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({
         template:'ErrorSessionPopupWidget',
@@ -375,6 +381,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         show: function(barcode){
             this._super();
             this.$('.barcode').text(barcode);
+
         },
     });
 
@@ -412,6 +419,18 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         template: 'ErrorInvoiceTransferPopupWidget',
     });
 
+    module.UnsentOrdersPopupWidget = module.PopUpWidget.extend({
+        template: 'UnsentOrdersPopupWidget',
+        show: function(options){
+            var self = this;
+            this._super(options);
+            this.renderElement();
+            this.$('.button.confirm').click(function(){
+                self.pos_widget.screen_selector.close_popup();
+            });
+        },
+    });
+
     module.ScaleScreenWidget = module.ScreenWidget.extend({
         template:'ScaleScreenWidget',
 
@@ -557,6 +576,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
     module.ClientListScreenWidget = module.ScreenWidget.extend({
         template: 'ClientListScreenWidget',
 
+        init: function(parent, options){
+            this._super(parent, options);
+            this.partner_cache = new module.DomCache();
+        },
+
         show_leftpane: false,
 
         auto_back: true,
@@ -587,7 +611,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             }
 
             this.$('.client-list-contents').delegate('.client-line','click',function(event){
-                console.log('uh');
                 self.line_select(event,$(this),parseInt($(this).data('id')));
             });
 
@@ -609,6 +632,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
         perform_search: function(query, associate_result){
             if(query){
                 var customers = this.pos.db.search_partner(query);
+                console.log(customers);
                 this.display_client_details('hide');
                 if ( associate_result && customers.length === 1){
                     this.new_client = customers[0];
@@ -628,14 +652,24 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
             this.$('.searchbox input').focus();
         },
         render_list: function(partners){
-            var contents = this.$('.client-list-contents');
-            contents.empty();
-            for(var i = 0, len = partners.length; i < len; i++){
-                var clientline = $(QWeb.render('ClientLine',{partner:partners[i]}));
-                if( partners[i] === this.new_client ){
-                    clientline.addClass('highlight');
+            var contents = this.$el[0].querySelector('.client-list-contents');
+            contents.innerHTML = "";
+            for(var i = 0, len = Math.min(partners.length,1000); i < len; i++){
+                var partner    = partners[i];
+                var clientline = this.partner_cache.get_node(partner.id);
+                if(!clientline){
+                    var clientline_html = QWeb.render('ClientLine',{partner:partners[i]});
+                    var clientline = document.createElement('tbody');
+                    clientline.innerHTML = clientline_html;
+                    clientline = clientline.childNodes[1];
+                    this.partner_cache.cache_node(partner.id,clientline);
+                }
+                if( partners === this.new_client ){
+                    clientline.classList.add('highlight');
+                }else{
+                    clientline.classList.remove('highlight');
                 }
-                contents.append(clientline);
+                contents.appendChild(clientline);
             }
         },
         save_changes: function(){
diff --git a/addons/point_of_sale/static/src/js/tests.js b/addons/point_of_sale/static/src/js/tests.js
index 018b343de58a1f5f7efbee19bbc9f548632147e6..08aa6e872132f2b1503ca203997f38f5239e9305 100644
--- a/addons/point_of_sale/static/src/js/tests.js
+++ b/addons/point_of_sale/static/src/js/tests.js
@@ -1,93 +1,89 @@
-function openerp_pos_tests(instance, module){ //module is instance.point_of_sale
+(function() {
+    'use strict';
 
-    // Various UI Tests to measure performance and memory leaks.
-    module.UiTester = function(){
-        var running = false;
-        var queue = new module.JobQueue();
+    openerp.Tour.register({
+        id: 'pos_basic_order',
+        name: 'Complete a basic order trough the Front-End',
+        path: '/web#model=pos.session.opening&action=point_of_sale.action_pos_session_opening',
+        mode: 'test',
+        steps: [
+            {
+                title:   'Wait fot the bloody screen to be ready',
+                wait: 200,
+            },
+            {
+                title:  'Load the Session',
+                waitNot: '.oe_loading:visible',
+                element: 'span:contains("Resume Session"),span:contains("Start Session")',
+            },
+            {
+                title: 'Loading the Loading Screen',
+                waitFor: '.loader'
+            },
+            {
+                title: 'Waiting for the end of loading...',
+                waitFor: '.loader:hidden',
+            },
+            {
+                title: 'Loading The Point of Sale',
+                waitFor: '.pos',
+            },
+            {
+                title: 'On va manger des CHIPS!',
+                element: '.product-list .product-name:contains("250g Lays Pickels")',
+            },
+            {
+                title: 'The chips have been added to the Order',
+                waitFor: '.order .product-name:contains("250g Lays Pickels")',
+            },
+            {
+                title: 'The order total has been updated to the correct value',
+                wait: 2000,
+                waitFor: '.order .total .value:contains("1.48 €")',
+            },
+            {
+                title: "Let's buy more chips",
+                element: '.product-list .product-name:contains("250g Lays Pickels")',
+            },
+            {
+                title: "Let's veryify we pay the correct price for two bags of chips",
+                waitFor: '.order .total .value:contains("2.96 €")',
+            },
+            {
+                title: "Let's pay with a debit card",
+                element: ".paypad-button:contains('Bank')",
+            },
+            {
+                title: "Let's accept the payment",
+                onload: function(){ 
+                    // The test cannot validate or cancel the print() ... so we replace it by a noop !.
+                    window._print = window.print;
+                    window.print  = function(){ console.log('Print!') };
+                },
+                element: ".button .iconlabel:contains('Validate'):visible",
+            },
+            {
+                title: "Let's finish the order",
+                element: ".button:not(.disabled) .iconlabel:contains('Next'):visible",
+            },
+            {
+                onload: function(){
+                    window.print  = window._print;
+                    window._print = undefined;
+                },
+                title: "Let's wait for the order posting",
+                waitFor: ".oe_status.js_synch .js_connected:visible",
+            },
+            {
+                title: "Let's close the Point of Sale",
+                element: ".header-button:contains('Close')",
+            },
+            {
+                title: "Wait for the backend to ready itself",
+                element: 'span:contains("Resume Session"),span:contains("Start Session")',
+            },
+        ],
+    });
 
-        // stop the currently running test
-        this.stop = function(){
-            queue.clear();
-        };
+})();
 
-        // randomly switch product categories
-        this.category_switch = function(interval){
-            queue.schedule(function(){
-                var breadcrumbs = $('.breadcrumb-button');
-                var categories  = $('.category-button');
-                if(categories.length > 0){
-                    var rnd = Math.floor(Math.random()*categories.length);
-                    categories.eq(rnd).click();
-                }else{
-                    var rnd = Math.floor(Math.random()*breadcrumbs.length);
-                    breadcrumbs.eq(rnd).click();
-                }
-            },{repeat:true, duration:interval});
-        };
-
-        // randomly order products then resets the order
-        this.order_products = function(interval){
-
-            queue.schedule(function(){
-                var def = new $.Deferred();
-                var order_queue = new module.JobQueue();
-                var order_size = 1 + Math.floor(Math.random()*10);
-
-                while(order_size--){
-                    order_queue.schedule(function(){
-                        var products = $('.product');
-                        if(products.length > 0){
-                            var rnd = Math.floor(Math.random()*products.length);
-                            products.eq(rnd).click();
-                        }
-                    },{duration:20});
-                }
-                order_queue.finished().then(function(){
-                        $('.deleteorder-button').click();
-                        def.resolve();
-                });
-                return def;
-            },{repeat:true, duration: interval});
-
-        };
-
-        // makes a complete product order cycle ( print via proxy must be activated, and scale deactivated ) 
-        this.full_order_cycle = function(interval){
-            queue.schedule(function(){
-                var def = new $.Deferred();
-                var order_queue = new module.JobQueue();
-                var order_size = 1 + Math.floor(Math.random()*50);
-
-                while(order_size--){
-                    order_queue.schedule(function(){
-                        var products = $('.product');
-                        if(products.length > 0){
-                            var rnd = Math.floor(Math.random()*products.length);
-                            products.eq(rnd).click();
-                        }
-                    },{duration:50});
-                }
-                order_queue.schedule(function(){
-                    $('.paypad-button:first').click();
-                },{duration:250});
-                order_queue.schedule(function(){
-                    $('.paymentline-input:first').val(10000);
-                    $('.paymentline-input:first').keydown();
-                    $('.paymentline-input:first').keyup();
-                },{duration:250});
-                order_queue.schedule(function(){
-                    $('.pos-actionbar-button-list .button:eq(2)').click();
-                },{duration:250});
-                order_queue.schedule(function(){
-                    def.resolve();
-                });
-                return def;
-            },{repeat: true, duration: interval});
-        };
-    };
-    
-    if(jQuery.deparam(jQuery.param.querystring()).debug !== undefined){
-        window.pos_test_ui = new module.UiTester();
-    }
-
-}
diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js
index 5db121657372a3a2a676ced825874ccd8fefd7a9..0f846b9d9245e9bc2a2b0f5d5ea58a59e7254002 100644
--- a/addons/point_of_sale/static/src/js/widgets.js
+++ b/addons/point_of_sale/static/src/js/widgets.js
@@ -797,6 +797,19 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.$('.button.reference').click(function(){
                 self.pos.barcode_reader.scan(self.$('input.ean').val());
             });
+            this.$('.button.show_orders').click(function(){
+                self.pos.pos_widget.screen_selector.show_popup('unsent-orders');
+            });
+            this.$('.button.delete_orders').click(function(){
+                self.pos.pos_widget.screen_selector.show_popup('confirm',{
+                    message: _t('Delete Unsent Orders ?'),
+                    comment: _t('This operation will permanently destroy all unsent orders from the local storage. You will lose all the data. This operation cannot be undone.'),
+                    confirm: function(){
+                        self.pos.db.remove_all_orders();
+                        self.pos.set({synch: { state:'connected', pending: 0 }});
+                    },
+                });
+            });
             _.each(this.eans, function(ean, name){
                 self.$('.button.'+name).click(function(){
                     self.$('input.ean').val(ean);
@@ -840,7 +853,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
                 self.set_status(synch.state, synch.pending);
             });
             this.$el.click(function(){
-                self.pos.flush();
+                self.pos.push_order();
             });
         },
     });
@@ -1005,18 +1018,41 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             
                 self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
 
-                self.pos.flush();
-
-            }).fail(function(){   // error when loading models data from the backend
-                return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_session_opening']], ['res_id'])
-                    .pipe( _.bind(function(res){
-                        return instance.session.rpc('/web/action/load', {'action_id': res[0]['res_id']})
-                            .pipe(_.bind(function(result){
-                                var action = result.result;
-                                this.do_action(action);
-                            }, this));
-                    }, self));
+                self.pos.push_order();
+
+            }).fail(function(err){   // error when loading models data from the backend
+                self.loading_error(err);
+            });
+        },
+        loading_error: function(err){
+            var self = this;
+
+            var message = err.message;
+            var comment = err.stack;
+
+            if(err.message === 'XmlHttpRequestError '){
+                message = 'Network Failure (XmlHttpRequestError)';
+                comment = 'The Point of Sale could not be loaded due to a network problem.\n Please check your internet connection.';
+            }else if(err.message === 'OpenERP Server Error'){
+                message = err.data.message;
+                comment = err.data.debug;
+            }
+
+            if( typeof comment !== 'string' ){
+                comment = 'Traceback not available.';
+            }
+
+            var popup = $(QWeb.render('ErrorTracebackPopupWidget',{
+                widget: { message: message, comment: comment },
+            }));
+
+            popup.find('.button').click(function(){
+                self.close();
             });
+
+            popup.css({ zindex: 9001 });
+
+            popup.appendTo(this.$el);
         },
         loading_progress: function(fac){
             this.$('.loader .loader-feedback').removeClass('oe_hidden');
@@ -1082,9 +1118,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
             this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {});
             this.error_invoice_transfer_popup.appendTo(this.$el);
 
+            this.error_traceback_popup = new module.ErrorTracebackPopupWidget(this,{});
+            this.error_traceback_popup.appendTo(this.$el);
+
             this.confirm_popup = new module.ConfirmPopupWidget(this,{});
             this.confirm_popup.appendTo(this.$el);
 
+            this.unsent_orders_popup = new module.UnsentOrdersPopupWidget(this,{});
+            this.unsent_orders_popup.appendTo(this.$el);
+
             // --------  Misc ---------
 
             this.close_button = new module.HeaderButtonWidget(this,{
@@ -1139,7 +1181,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
                     'choose-receipt': this.choose_receipt_popup,
                     'error-no-client': this.error_no_client_popup,
                     'error-invoice-transfer': this.error_invoice_transfer_popup,
+                    'error-traceback': this.error_traceback_popup,
                     'confirm': this.confirm_popup,
+                    'unsent-orders': this.unsent_orders_popup,
                 },
                 default_screen: 'products',
                 default_mode: 'cashier',
diff --git a/addons/point_of_sale/static/src/sounds/error.wav b/addons/point_of_sale/static/src/sounds/error.wav
new file mode 100644
index 0000000000000000000000000000000000000000..472f39107190844ac725defe4aa9fd6fefa4d9dc
Binary files /dev/null and b/addons/point_of_sale/static/src/sounds/error.wav differ
diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml
index 929212cce8df2986e47d8a8b54712cbd5f394086..5946133086c2ef8b366010367ce7e1497a7f3ac6 100644
--- a/addons/point_of_sale/static/src/xml/pos.xml
+++ b/addons/point_of_sale/static/src/xml/pos.xml
@@ -635,6 +635,20 @@
         </div>
     </t>
 
+    <t t-name="ErrorTracebackPopupWidget">
+        <div class="modal-dialog">
+            <div class="popup popup-error">
+                <p class="message"><t t-esc=" widget.message || 'Error' " /></p>
+                <p class="comment traceback"><t t-esc=" widget.comment || '' "/></p>
+                <div class="footer">
+                    <div class="button">
+                        Ok
+                    </div>
+                </div>
+            </div>
+        </div>
+    </t>
+
     <t t-name="ErrorBarcodePopupWidget">
         <div class="modal-dialog">
             <div class="popup popup-barcode">
@@ -672,6 +686,29 @@
         </div>
     </t>
 
+    <t t-name="UnsentOrdersPopupWidget">
+        <div class="modal-dialog">
+            <div class="popup popup-unsent-orders">
+                <p class="message">Unsent Orders</p>
+                <t t-if='widget.pos.db.get_orders().length === 0'>
+                    <p class='comment'>
+                        There are no unsent orders
+                    </p>
+                </t>
+                <t t-if='widget.pos.db.get_orders().length > 0'>
+                    <p class='comment traceback'>
+                        <t t-esc='JSON.stringify(widget.pos.db.get_orders(),null,2)' />
+                    </p>
+                </t>
+                <div class="footer">
+                    <div class="button confirm">
+                        Ok
+                    </div>
+                </div>
+            </div>
+        </div>
+    </t>
+
     <t t-name="Product">
         <span class='product' t-att-data-product-id="product.id">
             <div class="product-img">
@@ -779,6 +816,12 @@
                     <li class="button reference">Reference</li>
                 </ul>
 
+                <p class="category">Unsent Orders</p>
+                <ul>
+                    <li class="button show_orders">Show All Unsent Orders</li>
+                    <li class="button delete_orders">Delete All Unsent Orders</li>
+                </ul>
+
                 <p class="category">Hardware Status</p>
                 <ul>
                     <li class="status weighting">Weighting</li>
diff --git a/addons/point_of_sale/test/test_frontend.py b/addons/point_of_sale/test/test_frontend.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e51c5ae64382765aee56c63f33f23f868cc3b45
--- /dev/null
+++ b/addons/point_of_sale/test/test_frontend.py
@@ -0,0 +1,9 @@
+
+import openerp.tests
+
+@openerp.tests.common.at_install(False)
+@openerp.tests.common.post_install(True)
+class TestUi(openerp.tests.HttpCase):
+    def test_01_pos_basic_order(self):
+        self.phantom_js("/", "openerp.Tour.run('pos_basic_order', 'test')", "openerp.Tour.tours.pos_basic_order", login="admin")
+
diff --git a/addons/pos_discount/__init__.py b/addons/pos_discount/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5487d145a9bfcf6de5d28013c6fb6a3b88f25e3a
--- /dev/null
+++ b/addons/pos_discount/__init__.py
@@ -0,0 +1,25 @@
+# -*- 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 discount
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/pos_discount/__openerp__.py b/addons/pos_discount/__openerp__.py
new file mode 100644
index 0000000000000000000000000000000000000000..d367db148cdf625247209019de5bbdc85cb7637a
--- /dev/null
+++ b/addons/pos_discount/__openerp__.py
@@ -0,0 +1,47 @@
+# -*- 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': 'Point of Sale Discounts',
+    'version': '1.0',
+    'category': 'Point of Sale',
+    'sequence': 6,
+    'summary': 'Simple Discounts in the Point of Sale ',
+    'description': """
+
+=======================
+
+This module allows the cashier to quickly give a percentage
+sale discount to a customer.
+
+""",
+    'author': 'OpenERP SA',
+    'depends': ['point_of_sale'],
+    'data': [
+        'views/views.xml',
+        'views/templates.xml'
+    ],
+    'installable': True,
+    'auto_install': False,
+}
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/pos_discount/discount.py b/addons/pos_discount/discount.py
new file mode 100644
index 0000000000000000000000000000000000000000..f8d24eb638bae28752342b5ef967e7863dc37f8f
--- /dev/null
+++ b/addons/pos_discount/discount.py
@@ -0,0 +1,39 @@
+# -*- 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 _
+
+class pos_config(osv.osv):
+    _inherit = 'pos.config' 
+    _columns = {
+        'discount_pc': fields.float('Discount Percentage', help='The discount percentage'),
+        'discount_product_id': fields.many2one('product.product','Discount Product', help='The product used to model the discount'),
+    }
+    _defaults = {
+        'discount_pc': 10,
+    }
+
diff --git a/addons/pos_discount/static/src/js/discount.js b/addons/pos_discount/static/src/js/discount.js
new file mode 100644
index 0000000000000000000000000000000000000000..5eaab362d2093e06ef8c4f2dfee2ccb56e99533e
--- /dev/null
+++ b/addons/pos_discount/static/src/js/discount.js
@@ -0,0 +1,34 @@
+openerp.pos_discount = function(instance){
+    var module   = instance.point_of_sale;
+    var round_pr = instance.web.round_precision
+    var QWeb = instance.web.qweb;
+
+    QWeb.add_template('/pos_discount/static/src/xml/discount.xml');
+
+    module.PosWidget.include({
+        build_widgets: function(){
+            var self = this;
+            this._super();
+            
+            if(!this.pos.config.discount_product_id){
+                return;
+            }
+
+            var discount = $(QWeb.render('DiscountButton'));
+
+            discount.click(function(){
+                var order    = self.pos.get('selectedOrder');
+                var product  = self.pos.db.get_product_by_id(self.pos.config.discount_product_id[0]);
+                var discount = - self.pos.config.discount_pc/ 100.0 * order.getTotalTaxIncluded();
+                if( discount < 0 ){
+                    order.addProduct(product, { price: discount });
+                }
+            });
+
+            discount.appendTo(this.$('.control-buttons'));
+            this.$('.control-buttons').removeClass('oe_hidden');
+        },
+    });
+
+};
+
diff --git a/addons/pos_discount/static/src/xml/discount.xml b/addons/pos_discount/static/src/xml/discount.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c8be941683fdeb2372ec83e4481105d1cb5b6526
--- /dev/null
+++ b/addons/pos_discount/static/src/xml/discount.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates id="template" xml:space="preserve">
+
+    <t t-name="DiscountButton">
+        <div class='control-button js_discount'>
+            Discount
+        </div>
+    </t>
+
+</templates>
diff --git a/addons/pos_discount/views/templates.xml b/addons/pos_discount/views/templates.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0dbf22eb0fee59710d6f1600cfe2f1b78694dc67
--- /dev/null
+++ b/addons/pos_discount/views/templates.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <template id="assets_frontend" inherit_id="web.assets_common">
+          <xpath expr="." position="inside">
+              <script type="text/javascript" src="/pos_discount/static/src/js/discount.js"></script>
+          </xpath>
+        </template>
+
+    </data>
+</openerp>
diff --git a/addons/pos_discount/views/views.xml b/addons/pos_discount/views/views.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3448e0b2b6ff5a3986abd38217fa8f10ca25e468
--- /dev/null
+++ b/addons/pos_discount/views/views.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<openerp>
+    <data>
+
+        <record model="ir.ui.view" id="view_pos_config_form">
+            <field name="name">pos.config.form.view</field>
+            <field name="model">pos.config</field>
+            <field name="inherit_id" ref="point_of_sale.view_pos_config_form" />
+            <field name="arch" type="xml">
+                <xpath expr="//group[@string='Receipt']" position="after">
+                    <group string="Discounts" col="4" >
+                        <field name='discount_pc' />
+                        <field name="discount_product_id" />
+                    </group>
+                </xpath>
+            </field>
+        </record>
+
+    </data>
+</openerp>
diff --git a/addons/pos_restaurant/__init__.py b/addons/pos_restaurant/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..381469e26fc1a95bff32e1beddd82911d7f124c7
--- /dev/null
+++ b/addons/pos_restaurant/__init__.py
@@ -0,0 +1,25 @@
+# -*- 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:
+
diff --git a/addons/pos_restaurant/__openerp__.py b/addons/pos_restaurant/__openerp__.py
new file mode 100644
index 0000000000000000000000000000000000000000..dbf735b800e7f0b4a41545292786cf16654f3050
--- /dev/null
+++ b/addons/pos_restaurant/__openerp__.py
@@ -0,0 +1,55 @@
+# -*- 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:
diff --git a/addons/pos_restaurant/restaurant.py b/addons/pos_restaurant/restaurant.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7d02a336c1eef013b4ee0261ff5439e9269d4f3
--- /dev/null
+++ b/addons/pos_restaurant/restaurant.py
@@ -0,0 +1,55 @@
+# -*- 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,
+    }
+            
diff --git a/addons/pos_restaurant/restaurant_view.xml b/addons/pos_restaurant/restaurant_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..84adc883b94a72b6447bd5988f7b617d8667e8a5
--- /dev/null
+++ b/addons/pos_restaurant/restaurant_view.xml
@@ -0,0 +1,74 @@
+<?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 &amp; Restaurant" >
+                        <field name="iface_splitbill" />
+                        <field name="iface_printbill" />
+                        <field name="printer_ids" />
+                    </group>
+                </sheet>
+            </field>
+        </record>
+
+    </data>
+</openerp>
diff --git a/addons/pos_restaurant/security/ir.model.access.csv b/addons/pos_restaurant/security/ir.model.access.csv
new file mode 100644
index 0000000000000000000000000000000000000000..31c7f956f86fecc6e195123951cbacd222b83d73
--- /dev/null
+++ b/addons/pos_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/pos_restaurant/static/src/css/restaurant.css b/addons/pos_restaurant/static/src/css/restaurant.css
new file mode 100644
index 0000000000000000000000000000000000000000..0ffebfc0a46600de48672e0ebaf12dca5cdd6616
--- /dev/null
+++ b/addons/pos_restaurant/static/src/css/restaurant.css
@@ -0,0 +1,2 @@
+/* --- Restaurant Specific CSS --- */
+
diff --git a/addons/pos_restaurant/static/src/js/main.js b/addons/pos_restaurant/static/src/js/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..606eef836a8616dffb6c32eeaf7f4ec4f485d50e
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/main.js
@@ -0,0 +1,11 @@
+openerp.pos_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/pos_restaurant/static/src/js/multiprint.js b/addons/pos_restaurant/static/src/js/multiprint.js
new file mode 100644
index 0000000000000000000000000000000000000000..14e412a507129945a5eeb05d79eb674950c750de
--- /dev/null
+++ b/addons/pos_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/pos_restaurant/static/src/js/printbill.js b/addons/pos_restaurant/static/src/js/printbill.js
new file mode 100644
index 0000000000000000000000000000000000000000..89bf9db7bc7a2c92f0326428151431b2b3c42c99
--- /dev/null
+++ b/addons/pos_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/pos_restaurant/static/src/js/splitbill.js b/addons/pos_restaurant/static/src/js/splitbill.js
new file mode 100644
index 0000000000000000000000000000000000000000..81591ccc58970d8dafbf6d406b61242a2f3f3aaf
--- /dev/null
+++ b/addons/pos_restaurant/static/src/js/splitbill.js
@@ -0,0 +1,193 @@
+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');
+            }
+        },
+    });
+}
diff --git a/addons/pos_restaurant/static/src/xml/multiprint.xml b/addons/pos_restaurant/static/src/xml/multiprint.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8f3027dd552e4988e3f210e185531f20111e4171
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/multiprint.xml
@@ -0,0 +1,55 @@
+<?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>
diff --git a/addons/pos_restaurant/static/src/xml/printbill.xml b/addons/pos_restaurant/static/src/xml/printbill.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8727743a2ad980bb4662a95bf37db013b149a8d8
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/printbill.xml
@@ -0,0 +1,143 @@
+<?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) &lt;= 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>
diff --git a/addons/pos_restaurant/static/src/xml/splitbill.xml b/addons/pos_restaurant/static/src/xml/splitbill.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d56f01aaa9b88da048378303df683053c8fa70c1
--- /dev/null
+++ b/addons/pos_restaurant/static/src/xml/splitbill.xml
@@ -0,0 +1,88 @@
+<?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>
diff --git a/addons/pos_restaurant/views/templates.xml b/addons/pos_restaurant/views/templates.xml
new file mode 100644
index 0000000000000000000000000000000000000000..10c269be86f08efa88098f324e39323011050bf0
--- /dev/null
+++ b/addons/pos_restaurant/views/templates.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- vim:fdn=3:
+-->
+<openerp>
+    <data>
+
+        <template id="index" inherit_id='point_of_sale.index' name="Restaurant Index">&lt;!DOCTYPE html&gt;
+            <xpath expr="//link[@id='pos-stylesheet']" position="after">
+                <link rel="stylesheet" href="/pos_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="/pos_restaurant/static/src/js/multiprint.js"></script>
+              <script type="text/javascript" src="/pos_restaurant/static/src/js/splitbill.js"></script>
+              <script type="text/javascript" src="/pos_restaurant/static/src/js/printbill.js"></script>
+              <script type="text/javascript" src="/pos_restaurant/static/src/js/main.js"></script>
+          </xpath>
+        </template>
+
+    </data>
+</openerp>