[MERGE] from master
authorFrédéric van der Essen <fvdessen@gmail.com>
Tue, 9 Dec 2014 22:38:33 +0000 (23:38 +0100)
committerFrédéric van der Essen <fvdessen@gmail.com>
Tue, 9 Dec 2014 22:38:33 +0000 (23:38 +0100)
12 files changed:
1  2 
addons/account/account_view.xml
addons/point_of_sale/point_of_sale_view.xml
addons/point_of_sale/static/src/js/models.js
addons/point_of_sale/static/src/js/screens.js
addons/point_of_sale/static/src/js/widgets.js
addons/product/product.py
addons/product/product_view.xml
addons/stock/stock.py
addons/stock/stock_view.xml
addons/web/static/src/css/base.css
addons/web/static/src/css/base.sass
openerp/addons/base/res/res_partner.py

              <field name="model">account.journal</field>
              <field name="arch" type="xml">
                  <tree string="Account Journal">
 +                    <field name='sequence' widget='handle'/>
                      <field name="code"/>
                      <field name="name"/>
                      <field name="type"/>
                          </div>
                          <group>
                              <group>
-                                 <field name="account_id" domain="[('type', 'not in', ['view', 'closed', 'consolidation'])]"/>
+                                 <field name="account_id" domain="[('company_id', '=', company_id)]"/>
                                  <field name="amount_type"/>
-                                 <field name="tax_id" domain="[('type_tax_use', 'in', ['purchase', 'all']), ('parent_id', '=', False)]"/>
+                                 <field name="tax_id" domain="[('company_id', '=', company_id)]"/>
                              </group>
                              <group>
                                  <field name="label"/>
                                      <field name="amount" class="oe_inline" />
                                      <label string="%" class="oe_inline" attrs="{'invisible':[('amount_type','not in',('percentage_of_total', 'percentage_of_balance'))]}" />
                                  </div>
-                                 <field name="analytic_account_id" groups="analytic.group_analytic_accounting"/>
+                                 <field name="analytic_account_id" groups="analytic.group_analytic_accounting" domain="[('company_id', '=', company_id)]"/>
+                                 <field name="company_id" groups="base.group_multi_company"/>
                              </group>
                          </group>
                      </sheet>
              <field name="field_parent" eval="False"/>
              <field name="arch" type="xml">
                  <tree string="Product Product Categories">
-                     <field name="sequence" invisible="1"/>
+                     <field name="sequence" widget="handle"/>
                      <field name="complete_name"/>
                  </tree>
              </field>
              <field name="model">product.template</field>
              <field name="inherit_id" ref="product.product_template_only_form_view"/>
              <field name="arch" type="xml">
 -                <field name="ean13" position="after">
 +                <field name="barcode" position="after">
                      <button colspan="2" name="%(action_edit_ean)d" type="action" string="Set a Custom EAN"
                              attrs="{'invisible': [('product_variant_count', '>', 1)]}" class="oe_link oe_edit_only"/>
                  </field>
              <field name="model">product.product</field>
              <field name="inherit_id" ref="product.product_normal_form_view"/>
              <field name="arch" type="xml">
 -                <field name="ean13" position="after">
 +                <field name="barcode" position="after">
                      <button colspan="2" name="%(action_edit_ean)d" type="action" string="Set a Custom EAN" class="oe_link oe_edit_only"/>
                  </field>
              </field>
          </record>
 +
 +        <record id="product_template_tree_view" model="ir.ui.view">
 +            <field name="name">product.template.tree.inherit</field>
 +            <field name="model">product.template</field>
 +            <field name="inherit_id" ref="product.product_template_tree_view"/>
 +            <field name="arch" type="xml">
 +                <field name="name" position="before">
 +                    <field name="sequence" widget="handle" />
 +                </field>
 +            </field>
 +        </record>
 +
          <!-- END -->
  
          <menuitem name="Configuration" parent="menu_point_root"
                              <field name="pricelist_id" groups="product.group_sale_pricelist"/>
                              <field name="journal_id" widget="selection"/>
                              <field name="group_by" groups="account.group_account_user"/>
 +                            <field name="barcode_nomenclature_id" />
                              <field name="sequence_id" readonly="1" groups="base.group_no_one"/>
                              <field name="currency_id" invisible="1"/>
                          </group>
                              <group>
                                  <field name="iface_vkeyboard" />
                                  <field name="iface_invoicing" />
 +                                <field name="iface_precompute_cash" />
                              </group>
                              <group>
                                  <field name="iface_fullscreen" />
                                  <field name="iface_big_scrollbars" />
 +                                <field name="iface_print_auto" />
 +                                <field name="iface_tax_included" />
                              </group>
                          </group>
 -                        <group string="Hardware Proxy" >
 +                        <group string="Hardware Proxy / PosBox" >
                              <field name="proxy_ip" />
 -                            <field name="iface_print_via_proxy" />
 -                            <field name="iface_scan_via_proxy" />
 +                            <field name="iface_print_via_proxy" string="Receipt Printer"/>
 +                            <field name="iface_scan_via_proxy" string="Barcode Scanner"/>
                              <field name="iface_electronic_scale" />
                              <field name="iface_cashdrawer" />
                          </group>
                          <group string="Receipt" >
 -                            <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="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>
 +                            <field name="receipt_header" string="Header" placeholder="A custom receipt header message"/>
 +                            <field name="receipt_footer" string="Footer" placeholder="A custom receipt header footage"/>
                          </group>
                      </sheet>
  
              </field>
          </record>
  
 -
 +        <!-- Barcode Nomenclatures -->
 +        <menuitem parent="menu_point_config_product" action="barcodes.action_barcode_nomenclature_form" id="menu_pos_barcode_nomenclature_all"
 +            sequence="30"/>
      </data>
  </openerp>
  
@@@ -1,9 -1,6 +1,9 @@@
 -function openerp_pos_models(instance, module){ //module is instance.point_of_sale
 +openerp.point_of_sale.load_models = function load_models(instance, module){ //module is instance.point_of_sale
 +    "use strict";
 +
      var QWeb = instance.web.qweb;
        var _t = instance.web._t;
 +    var barcode_parser_module = instance.barcodes;
  
      var round_di = instance.web.round_decimals;
      var round_pr = instance.web.round_precision
@@@ -27,8 -24,7 +27,8 @@@
              this.pos_widget = attributes.pos_widget;
  
              this.proxy = new module.ProxyDevice(this);              // used to communicate to the hardware devices via a local proxy
 -            this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy, patterns: {}});  // used to read barcodes
 +            this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy});
 +
              this.proxy_queue = new module.JobQueue();           // used to prevent parallels communications to the proxy
              this.db = new module.PosDB();                       // a local database used to search trough products and categories & store pending orders
              this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined;    //debug mode 
@@@ -45,6 -41,7 +45,6 @@@
              this.partners = [];
              this.cashier = null;
              this.cashregisters = [];
 -            this.bankstatements = [];
              this.taxes = [];
              this.pos_session = null;
              this.config = null;
@@@ -82,7 -79,8 +82,7 @@@
                      if(self.config.use_proxy){
                          return self.connect_to_proxy();
                      }
 -                });
 -            
 +                });  // used to read barcodes);
          },
  
          // releases ressources holds by the model at the end of life of the posmodel
                  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','state_id','country_id','vat','phone','zip','mobile','email','ean13','write_date'],
 -            domain: [['customer','=',true]],
 +            fields: ['name','street','city','state_id','country_id','vat','phone','zip','mobile','email','barcode','write_date'],
-             domain: null,
++            domain: [['customer','=',true]], 
              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; },
 +            loaded: function(self,taxes){ 
 +                self.taxes = taxes; 
 +                self.taxes_by_id = {};
 +                
 +                for (var i = 0; i < taxes.length; i++) {
 +                    self.taxes_by_id[taxes[i].id] = taxes[i];
 +                }
 +            },
          },{
              model:  'pos.session',
              fields: ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number','login_number'],
              domain: function(self){ return [['state','=','opened'],['user_id','=',self.session.uid]]; },
              loaded: function(self,pos_sessions){
                  self.pos_session = pos_sessions[0]; 
 -
 -                var orders = self.db.get_orders();
 -                for (var i = 0; i < orders.length; i++) {
 -                    self.pos_session.sequence_number = Math.max(self.pos_session.sequence_number, orders[i].data.sequence_number+1);
 -                }
              },
          },{
              model: 'pos.config',
                                          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,
 -                });
  
                  if (self.config.company_id[0] !== self.user.company_id[0]) {
                      throw new Error(_t("Error: The Point of Sale User must belong to the same company as the Point of Sale. You are probably trying to load the point of sale as an administrator in a multi-company setup, with the administrator account set to the wrong company."));
                  }
 +
 +                self.db.set_uuid(self.config.uuid);
 +
 +                var orders = self.db.get_orders();
 +                for (var i = 0; i < orders.length; i++) {
 +                    self.pos_session.sequence_number = Math.max(self.pos_session.sequence_number, orders[i].data.sequence_number+1);
 +                }
 +           },
 +        },{
 +            model:  'res.users',
 +            fields: ['name','pos_security_pin','groups_id','barcode'],
 +            domain: function(self){ return [['company_id','=',self.user.company_id[0]],'|', ['groups_id','=', self.config.group_pos_manager_id[0]],['groups_id','=', self.config.group_pos_user_id[0]]]; },
 +            loaded: function(self,users){ 
 +                // we attribute a role to the user, 'cashier' or 'manager', depending
 +                // on the group the user belongs. 
 +                var pos_users = [];
 +                for (var i = 0; i < users.length; i++) {
 +                    var user = users[i];
 +                    for (var j = 0; j < user.groups_id.length; j++) {
 +                        var group_id = user.groups_id[j];
 +                        if (group_id === self.config.group_pos_manager_id[0]) {
 +                            user.role = 'manager';
 +                            break;
 +                        } else if (group_id === self.config.group_pos_user_id[0]) {
 +                            user.role = 'cashier';
 +                        }
 +                    }
 +                    if (user.role) {
 +                        pos_users.push(user);
 +                    }
 +                    // replace the current user with its updated version
 +                    if (user.id === self.user.id) {
 +                        self.user = user;
 +                    }
 +                }
 +                self.users = pos_users; 
              },
          },{
              model: 'stock.location',
              },
          },{
              model: 'product.packaging',
 -            fields: ['ean','product_tmpl_id'],
 +            fields: ['barcode','product_tmpl_id'],
              domain: null,
              loaded: function(self, packagings){ 
                  self.db.add_packagings(packagings);
              },
          },{
              model:  'product.product',
 -            fields: ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'ean13', 'default_code', 
 +            fields: ['display_name', 'list_price','price','pos_categ_id', 'taxes_id', 'barcode', 'default_code', 
                       'to_weight', 'uom_id', 'uos_id', 'uos_coeff', 'mes_type', 'description_sale', 'description',
                       'product_tmpl_id'],
-             order:   ['sequence','name'],
-             domain:  function(self){ return [['sale_ok','=',true],['available_in_pos','=',true]]; },
++            order:  ['sequence','name'],
+             domain: [['sale_ok','=',true],['available_in_pos','=',true]],
              context: function(self){ return { pricelist: self.pricelist.id, display_default_code: false }; },
              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;
 +            loaded: function(self, cashregisters, tmp){
 +                self.cashregisters = cashregisters;
  
                  tmp.journals = [];
 -                _.each(bankstatements,function(statement){
 +                _.each(cashregisters,function(statement){
                      tmp.journals.push(statement.journal_id[0]);
                  });
              },
                  self.journals = journals;
  
                  // associate the bank statements with their journals. 
 -                var bankstatements = self.bankstatements;
 -                for(var i = 0, ilen = bankstatements.length; i < ilen; i++){
 +                var cashregisters = self.cashregisters;
 +                for(var i = 0, ilen = cashregisters.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];
 +                        if(cashregisters[i].journal_id[0] === journals[j].id){
 +                            cashregisters[i].journal = journals[j];
                          }
                      }
                  }
 -                self.cashregisters = bankstatements;
 +
 +                self.cashregisters_by_id = {};
 +                for (var i = 0; i < self.cashregisters.length; i++) {
 +                    self.cashregisters_by_id[self.cashregisters[i].id] = self.cashregisters[i];
 +                }
 +
 +                self.cashregisters = self.cashregisters.sort(function(a,b){ 
 +                    return a.journal.sequence - b.journal.sequence; 
 +                });
 +
              },
 -        },{
 +        },  {
              label: 'fonts',
              loaded: function(self){
                  var fonts_loaded = new $.Deferred();
  
                  return logo_loaded;
              },
 -        },
 +        }, {
 +            label: 'barcodes',
 +            loaded: function(self) {
 +                var barcode_parser = new barcode_parser_module.BarcodeParser({'nomenclature_id': self.config.barcode_nomenclature_id});
 +                self.barcode_reader.set_barcode_parser(barcode_parser);
 +                return barcode_parser.is_loaded();
 +            },
 +        }
          ],
  
          // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded. 
                  }else{
                      var model = self.models[index];
                      self.pos_widget.loading_message(_t('Loading')+' '+(model.label || model.model || ''), progress);
 +
 +                    var cond = typeof model.condition === 'function'  ? model.condition(self,tmp) : true;
 +                    if (!cond) {
 +                        load_model(index+1);
 +                        return;
 +                    }
 +
                      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; 
                      var ids     = typeof model.ids === 'function'     ? model.ids(self,tmp) : model.ids;
 +                    var order   = typeof model.order === 'function'   ? model.order(self,tmp):    model.order;
                      progress += progress_step;
                      
  
                          if (model.ids) {
                              var records = new instance.web.Model(model.model).call('read',[ids,fields],context);
                          } else {
 -                            var records = new instance.web.Model(model.model).query(fields).filter(domain).context(context).all()
 +                            var records = new instance.web.Model(model.model).query(fields).filter(domain).order_by(order).context(context).all()
                          }
                          records.then(function(result){
                                  try{    // catching exceptions in model.loaded(...)
                                          .then(function(){ load_model(index + 1); },
                                                function(err){ loaded.reject(err); });
                                  }catch(err){
 +                                    console.error(err.stack);
                                      loaded.reject(err);
                                  }
                              },function(err){
                      } else {
                          def.reject();
                      }
-                 }, function(){ def.reject(); });    
+                 }, function(err,event){ event.preventDefault(); def.reject(); });    
              return def;
          },
  
          // this is called when an order is removed from the order collection. It ensures that there is always an existing
          // order and a valid selected order
          on_removed_order: function(removed_order,index,reason){
 -            if( (reason === 'abandon' || removed_order.temporary) && this.get('orders').size() > 0){
 +            var order_list = this.get_order_list();
 +            if( (reason === 'abandon' || removed_order.temporary) && order_list.length > 0){
                  // when we intentionally remove an unfinished order, and there is another existing one
 -                this.set({'selectedOrder' : this.get('orders').at(index) || this.get('orders').last()});
 +                this.set_order(order_list[index] || order_list[order_list.length -1]);
              }else{
                  // when the order was automatically removed after completion, 
                  // or when we intentionally delete the only concurrent order
              }
          },
  
 +        // returns the user who is currently the cashier for this point of sale
 +        get_cashier: function(){
 +            return this.cashier || this.user;
 +        },
 +        // changes the current cashier
 +        set_cashier: function(user){
 +            this.cashier = user;
 +        },
          //creates a new empty order and sets it as the current order
          add_new_order: function(){
 -            var order = new module.Order({pos:this});
 +            var order = new module.Order({},{pos:this});
              this.get('orders').add(order);
              this.set('selectedOrder', order);
 +            return order;
 +        },
 +        // load the locally saved unpaid orders for this session.
 +        load_orders: function(){
 +            var jsons = this.db.get_unpaid_orders();
 +            var orders = [];
 +            var not_loaded_count = 0; 
 +
 +            for (var i = 0; i < jsons.length; i++) {
 +                var json = jsons[i];
 +                if (json.pos_session_id === this.pos_session.id) {
 +                    orders.push(new module.Order({},{
 +                        pos:  this,
 +                        json: json,
 +                    }));
 +                } else {
 +                    not_loaded_count += 1;
 +                }
 +            }
 +
 +            if (not_loaded_count) {
 +                console.info('There are '+not_loaded_count+' locally saved unpaid orders belonging to another session');
 +            }
 +            
 +            orders = orders.sort(function(a,b){
 +                return a.sequence_number - b.sequence_number;
 +            });
 +
 +            if (orders.length) {
 +                this.get('orders').add(orders);
 +            }
          },
  
 +        set_start_order: function(){
 +            var orders = this.get('orders').models;
 +            
 +            if (orders.length && !this.get('selectedOrder')) {
 +                this.set('selectedOrder',orders[0]);
 +            } else {
 +                this.add_new_order();
 +            }
 +        },
 +
 +        // return the current order
          get_order: function(){
              return this.get('selectedOrder');
          },
  
 +        // change the current order
 +        set_order: function(order){
 +            this.set({ selectedOrder: order });
 +        },
 +        
 +        // return the list of unpaid orders
 +        get_order_list: function(){
 +            return this.get('orders').models;
 +        },
 +
          //removes the current order
          delete_current_order: function(){
 -            this.get('selectedOrder').destroy({'reason':'abandon'});
 +            var order = this.get_order();
 +            if (order) {
 +                order.destroy({'reason':'abandon'});
 +            }
          },
  
          // saves the order locally and try to send it to the backend. 
                  return server_ids;
              }).fail(function (error, event){
                  if(error.code === 200 ){    // Business Logic Error, not a connection problem
 -                    //if warning do not need to dispaly traceback!!
 -                    if(error.data.exception_type == 'warning'){
 +                    //if warning do not need to display traceback!!
 +                    if (error.data.exception_type == 'warning') {
                          delete error.data.debug;
                      }
                      self.pos_widget.screen_selector.show_popup('error-traceback',{
  
          scan_product: function(parsed_code){
              var self = this;
 -            var selectedOrder = this.get('selectedOrder');
 -            if(parsed_code.encoding === 'ean13'){
 -                var product = this.db.get_product_by_ean13(parsed_code.base_code);
 -            }else if(parsed_code.encoding === 'reference'){
 -                var product = this.db.get_product_by_reference(parsed_code.code);
 -            }
 +            var selectedOrder = this.get_order();       
 +            var product = this.db.get_product_by_barcode(parsed_code.base_code);
  
              if(!product){
                  return false;
              }
  
              if(parsed_code.type === 'price'){
 -                selectedOrder.addProduct(product, {price:parsed_code.value});
 +                selectedOrder.add_product(product, {price:parsed_code.value});
              }else if(parsed_code.type === 'weight'){
 -                selectedOrder.addProduct(product, {quantity:parsed_code.value, merge:false});
 +                selectedOrder.add_product(product, {quantity:parsed_code.value, merge:false});
              }else if(parsed_code.type === 'discount'){
 -                selectedOrder.addProduct(product, {discount:parsed_code.value, merge:false});
 +                selectedOrder.add_product(product, {discount:parsed_code.value, merge:false});
              }else{
 -                selectedOrder.addProduct(product);
 +                selectedOrder.add_product(product);
              }
              return true;
          },
      // An Order contains zero or more Orderlines.
      module.Orderline = Backbone.Model.extend({
          initialize: function(attr,options){
 -            this.pos = options.pos;
 +            this.pos   = options.pos;
              this.order = options.order;
 +            if (options.json) {
 +                this.init_from_JSON(options.json);
 +                return;
 +            }
              this.product = options.product;
              this.price   = options.product.price;
              this.quantity = 1;
              this.selected = false;
              this.id       = orderline_id++; 
          },
 +        init_from_JSON: function(json) {
 +            this.product = this.pos.db.get_product_by_id(json.product_id);
 +            if (!this.product) {
 +                console.error('ERROR: attempting to recover product not available in the point of sale');
 +            }
 +            this.price = json.price_unit;
 +            this.set_discount(json.discount);
 +            this.set_quantity(json.qty);
 +        },
          clone: function(){
              var orderline = new module.Orderline({},{
                  pos: this.pos,
          // rounded to zero
          set_quantity: function(quantity){
              if(quantity === 'remove'){
 -                this.order.removeOrderline(this);
 +                this.order.remove_orderline(this);
                  return;
              }else{
                  var quant = parseFloat(quantity) || 0;
              return {
                  quantity:           this.get_quantity(),
                  unit_name:          this.get_unit().name,
 -                price:              this.get_unit_price(),
 +                price:              this.get_unit_display_price(),
                  discount:           this.get_discount(),
                  product_name:       this.get_product().display_name,
                  price_display :     this.get_display_price(),
          get_unit_price: function(){
              return this.price;
          },
 +        get_unit_display_price: function(){
 +            if (this.pos.config.iface_tax_included) {
 +                var quantity = this.quantity;
 +                this.quantity = 1.0;
 +                var price = this.get_all_prices().priceWithTax;
 +                this.quantity = quantity;
 +                return price;
 +            } else {
 +                return this.get_unit_price();
 +            }
 +        },
          get_base_price:    function(){
              var rounding = this.pos.currency.rounding;
              return  round_pr(round_pr(this.get_unit_price() * this.get_quantity(),rounding) * (1- this.get_discount()/100.0),rounding);
          },
          get_display_price: function(){
              return this.get_base_price();
 +            if (this.pos.config.iface_tax_included) {
 +                return this.get_all_prices().priceWithTax;
 +            } else {
 +                return this.get_base_price();
 +            }
          },
          get_price_without_tax: function(){
              return this.get_all_prices().priceWithoutTax;
          get_tax_details: function(){
              return this.get_all_prices().taxDetails;
          },
 +        get_taxes: function(){
 +            var taxes_ids = this.get_product().taxes_id;
 +            var taxes = [];
 +            for (var i = 0; i < taxes_ids.length; i++) {
 +                taxes.push(this.pos.taxes_by_id[taxes_ids[i]]);
 +            }
 +            return taxes;
 +        },
          get_all_prices: function(){
              var self = this;
              var currency_rounding = this.pos.currency.rounding;
      // Every Paymentline contains a cashregister and an amount of money.
      module.Paymentline = Backbone.Model.extend({
          initialize: function(attributes, options) {
 +            this.pos = options.pos;
              this.amount = 0;
 +            this.selected = false;
 +            if (options.json) {
 +                this.init_from_JSON(options.json);
 +                return;
 +            }
              this.cashregister = options.cashregister;
              this.name = this.cashregister.journal_id[1];
 -            this.selected = false;
 -            this.pos = options.pos;
 +        },
 +        init_from_JSON: function(json){
 +            this.amount = json.amount;
 +            this.cashregister = this.pos.cashregisters_by_id[json.statement_id];
 +            this.name = this.cashregister.journal_id[1];
          },
          //sets the amount of money on this payment line
          set_amount: function(value){
              this.amount = round_di(parseFloat(value) || 0, this.pos.currency.decimals);
 -            this.trigger('change:amount',this);
 +            this.trigger('change',this);
          },
          // returns the amount of money on this paymentline
          get_amount: function(){
          set_selected: function(selected){
              if(this.selected !== selected){
                  this.selected = selected;
 -                this.trigger('change:selected',this);
 +                this.trigger('change',this);
              }
          },
          // returns the associated cashregister
      module.PaymentlineCollection = Backbone.Collection.extend({
          model: module.Paymentline,
      });
 -    
  
      // An order more or less represents the content of a client's shopping cart (the OrderLines) 
      // plus the associated payment information (the Paymentlines) 
      // there is always an active ('selected') order in the Pos, a new one is created
      // automaticaly once an order is completed and sent to the server.
      module.Order = Backbone.Model.extend({
 -        initialize: function(attributes){
 +        initialize: function(attributes,options){
              Backbone.Model.prototype.initialize.apply(this, arguments);
 -            this.pos = attributes.pos; 
 -            this.sequence_number = this.pos.pos_session.sequence_number++;
 -            this.uid =     this.generateUniqueId();
 -            this.set({
 -                creationDate:   new Date(),
 -                orderLines:     new module.OrderlineCollection(),
 -                paymentLines:   new module.PaymentlineCollection(),
 -                name:           _t("Order ") + this.uid,
 -                client:         null,
 -            });
 +            options  = options || {};
 +
 +            this.init_locked    = true;
 +            this.pos            = options.pos; 
              this.selected_orderline   = undefined;
              this.selected_paymentline = undefined;
 -            this.screen_data = {};  // see ScreenSelector
 -            this.receipt_type = 'receipt';  // 'receipt' || 'invoice'
 -            this.temporary = attributes.temporary || false;
 -            this.to_invoice = false;
 +            this.screen_data    = {};  // see ScreenSelector
 +            this.temporary      = options.temporary || false;
 +            this.creation_date  = new Date();
 +            this.to_invoice     = false;
 +            this.orderlines     = new module.OrderlineCollection();
 +            this.paymentlines   = new module.PaymentlineCollection(); 
 +            this.pos_session_id = this.pos.pos_session.id;
 +
 +            this.set({ client: null });
 +
 +            if (options.json) {
 +                this.init_from_JSON(options.json);
 +            } else {
 +                this.sequence_number = this.pos.pos_session.sequence_number++;
 +                this.uid  = this.generate_unique_id();
 +                this.name = _t("Order ") + this.uid; 
 +            }
 +
 +            this.on('change',              function(){ this.save_to_db("order:change"); }, this);
 +            this.orderlines.on('change',   function(){ this.save_to_db("orderline:change"); }, this);
 +            this.orderlines.on('add',      function(){ this.save_to_db("orderline:add"); }, this);
 +            this.orderlines.on('remove',   function(){ this.save_to_db("orderline:remove"); }, this);
 +            this.paymentlines.on('change', function(){ this.save_to_db("paymentline:change"); }, this);
 +            this.paymentlines.on('add',    function(){ this.save_to_db("paymentline:add"); }, this);
 +            this.paymentlines.on('remove', function(){ this.save_to_db("paymentline:rem"); }, this);
 +
 +            this.init_locked = false;
 +            this.save_to_db();
 +
              return this;
          },
 +        save_to_db: function(){
 +            if (!this.init_locked) {
 +                this.pos.db.save_unpaid_order(this);
 +            } 
 +        },
 +        init_from_JSON: function(json) {
 +            this.sequence_number = json.sequence_number;
 +            this.pos.pos_session.sequence_number = Math.max(this.sequence_number+1,this.pos.pos_session.sequence_number);
 +            this.session_id    = json.pos_session_id;
 +            this.uid = json.uid;
 +            this.name = _t("Order ") + this.uid;
 +            if (json.partner_id) {
 +                var client = this.pos.db.get_partner_by_id(json.partner_id);
 +                if (!client) {
 +                    console.error('ERROR: trying to load a parner not available in the pos');
 +                }
 +            } else {
 +                var client = null;
 +            }
 +            this.set_client(client);
 +
 +            this.temporary = false;     // FIXME
 +            this.to_invoice = false;    // FIXME
 +
 +            var orderlines = json.lines;
 +            for (var i = 0; i < orderlines.length; i++) {
 +                var orderline = orderlines[i][2];
 +                this.add_orderline(new module.Orderline({}, {pos: this.pos, order: this, json: orderline}));
 +            }
 +
 +            var paymentlines = json.statement_ids;
 +            for (var i = 0; i < paymentlines.length; i++) {
 +                var paymentline = paymentlines[i][2];
 +                var newpaymentline = new module.Paymentline({},{pos: this.pos, json: paymentline});
 +                this.paymentlines.add(newpaymentline);
 +
 +                if (i === paymentlines.length - 1) {
 +                    this.select_paymentline(newpaymentline);
 +                }
 +            }
 +        },
 +        export_as_JSON: function() {
 +            var orderLines, paymentLines;
 +            orderLines = [];
 +            this.orderlines.each(_.bind( function(item) {
 +                return orderLines.push([0, 0, item.export_as_JSON()]);
 +            }, this));
 +            paymentLines = [];
 +            this.paymentlines.each(_.bind( function(item) {
 +                return paymentLines.push([0, 0, item.export_as_JSON()]);
 +            }, this));
 +            return {
 +                name: this.get_name(),
 +                amount_paid: this.get_total_paid(),
 +                amount_total: this.get_total_with_tax(),
 +                amount_tax: this.get_total_tax(),
 +                amount_return: this.get_change(),
 +                lines: orderLines,
 +                statement_ids: paymentLines,
 +                pos_session_id: this.pos_session_id,
 +                partner_id: this.get_client() ? this.get_client().id : false,
 +                user_id: this.pos.cashier ? this.pos.cashier.id : this.pos.user.id,
 +                uid: this.uid,
 +                sequence_number: this.sequence_number,
 +            };
 +        },
 +        export_for_printing: function(){
 +            var orderlines = [];
 +            var self = this;
 +
 +            this.orderlines.each(function(orderline){
 +                orderlines.push(orderline.export_for_printing());
 +            });
 +
 +            var paymentlines = [];
 +            this.paymentlines.each(function(paymentline){
 +                paymentlines.push(paymentline.export_for_printing());
 +            });
 +            var client  = this.get('client');
 +            var cashier = this.pos.cashier || this.pos.user;
 +            var company = this.pos.company;
 +            var shop    = this.pos.shop;
 +            var date    = new Date();
 +
 +            function is_xml(subreceipt){
 +                return subreceipt ? (subreceipt.split('\n')[0].indexOf('<!DOCTYPE QWEB') >= 0) : false;
 +            }
 +
 +            function render_xml(subreceipt){
 +                if (!is_xml(subreceipt)) {
 +                    return subreceipt;
 +                } else {
 +                    subreceipt = subreceipt.split('\n').slice(1).join('\n');
 +                    var qweb = new QWeb2.Engine();
 +                        qweb.debug = instance.session.debug;
 +                        qweb.default_dict = _.clone(QWeb.default_dict);
 +                        qweb.add_template('<templates><t t-name="subreceipt">'+subreceipt+'</t></templates>');
 +                    
 +                    return qweb.render('subreceipt',{'pos':self.pos,'widget':self.pos.pos_widget,'order':self, 'receipt': receipt}) ;
 +                }
 +            }
 +
 +            var receipt = {
 +                orderlines: orderlines,
 +                paymentlines: paymentlines,
 +                subtotal: this.get_subtotal(),
 +                total_with_tax: this.get_total_with_tax(),
 +                total_without_tax: this.get_total_without_tax(),
 +                total_tax: this.get_total_tax(),
 +                total_paid: this.get_total_paid(),
 +                total_discount: this.get_total_discount(),
 +                tax_details: this.get_tax_details(),
 +                change: this.get_change(),
 +                name : this.get_name(),
 +                client: client ? client.name : null ,
 +                invoice_id: null,   //TODO
 +                cashier: cashier ? cashier.name : null,
 +                header: this.pos.config.receipt_header || '',
 +                footer: this.pos.config.receipt_footer || '',
 +                precision: {
 +                    price: 2,
 +                    money: 2,
 +                    quantity: 3,
 +                },
 +                date: { 
 +                    year: date.getFullYear(), 
 +                    month: date.getMonth(), 
 +                    date: date.getDate(),       // day of the month 
 +                    day: date.getDay(),         // day of the week 
 +                    hour: date.getHours(), 
 +                    minute: date.getMinutes() ,
 +                    isostring: date.toISOString(),
 +                    localestring: date.toLocaleString(),
 +                }, 
 +                company:{
 +                    email: company.email,
 +                    website: company.website,
 +                    company_registry: company.company_registry,
 +                    contact_address: company.partner_id[1], 
 +                    vat: company.vat,
 +                    name: company.name,
 +                    phone: company.phone,
 +                    logo:  this.pos.company_logo_base64,
 +                },
 +                shop:{
 +                    name: shop.name,
 +                },
 +                currency: this.pos.currency,
 +            };
 +            
 +            if (is_xml(this.pos.config.receipt_header)){
 +                receipt.header_xml = render_xml(this.pos.config.receipt_header);
 +            }
 +
 +            if (is_xml(this.pos.config.receipt_footer)){
 +                receipt.footer_xml = render_xml(this.pos.config.receipt_footer);
 +            }
 +
 +            return receipt;
 +        },
          is_empty: function(){
 -            return (this.get('orderLines').models.length === 0);
 +            return this.orderlines.models.length === 0;
          },
 -        // Generates a public identification number for the order.
 -        // The generated number must be unique and sequential. They are made 12 digit long
 -        // to fit into EAN-13 barcodes, should it be needed 
 -        generateUniqueId: function() {
 +        generate_unique_id: function() {
 +            // Generates a public identification number for the order.
 +            // The generated number must be unique and sequential. They are made 12 digit long
 +            // to fit into EAN-13 barcodes, should it be needed 
 +
              function zero_pad(num,size){
                  var s = ""+num;
                  while (s.length < size) {
                     zero_pad(this.pos.pos_session.login_number,3) +'-'+
                     zero_pad(this.sequence_number,4);
          },
 -        addOrderline: function(line){
 +        get_name: function() {
 +            return this.name;
 +        },
 +        /* ---- Order Lines --- */
 +        add_orderline: function(line){
              if(line.order){
 -                order.removeOrderline(line);
 +                line.order.remove_orderline(line);
              }
              line.order = this;
 -            this.get('orderLines').add(line);
 -            this.selectLine(this.getLastOrderline());
 +            this.orderlines.add(line);
 +            this.select_orderline(this.get_last_orderline());
 +        },
 +        get_orderline: function(id){
 +            var orderlines = this.orderlines.models;
 +            for(var i = 0; i < orderlines.length; i++){
 +                if(orderlines[i].id === id){
 +                    return orderlines[i];
 +                }
 +            }
 +            return null;
          },
 -        addProduct: function(product, options){
 +        get_orderlines: function(){
 +            return this.orderlines.models;
 +        },
 +        get_last_orderline: function(){
 +            return this.orderlines.at(this.orderlines.length -1);
 +        },
 +        remove_orderline: function( line ){
 +            this.orderlines.remove(line);
 +            this.select_orderline(this.get_last_orderline());
 +        },
 +        add_product: function(product, options){
              options = options || {};
              var attr = JSON.parse(JSON.stringify(product));
              attr.pos = this.pos;
                  line.set_discount(options.discount);
              }
  
 -            var last_orderline = this.getLastOrderline();
 +            if(options.extras !== undefined){
 +                for (var prop in options.extras) { 
 +                    line[prop] = options.extras[prop];
 +                }
 +            }
 +
 +            var last_orderline = this.get_last_orderline();
              if( last_orderline && last_orderline.can_be_merged_with(line) && options.merge !== false){
                  last_orderline.merge(line);
              }else{
 -                this.get('orderLines').add(line);
 +                this.orderlines.add(line);
              }
 -            this.selectLine(this.getLastOrderline());
 +            this.select_orderline(this.get_last_orderline());
          },
 -        removeOrderline: function( line ){
 -            this.get('orderLines').remove(line);
 -            this.selectLine(this.getLastOrderline());
 +        get_selected_orderline: function(){
 +            return this.selected_orderline;
          },
 -        getOrderline: function(id){
 -            var orderlines = this.get('orderLines').models;
 -            for(var i = 0; i < orderlines.length; i++){
 -                if(orderlines[i].id === id){
 -                    return orderlines[i];
 +        select_orderline: function(line){
 +            if(line){
 +                if(line !== this.selected_orderline){
 +                    if(this.selected_orderline){
 +                        this.selected_orderline.set_selected(false);
 +                    }
 +                    this.selected_orderline = line;
 +                    this.selected_orderline.set_selected(true);
                  }
 +            }else{
 +                this.selected_orderline = undefined;
              }
 -            return null;
          },
 -        getLastOrderline: function(){
 -            return this.get('orderLines').at(this.get('orderLines').length -1);
 +        deselect_orderline: function(){
 +            if(this.selected_orderline){
 +                this.selected_orderline.set_selected(false);
 +                this.selected_orderline = undefined;
 +            }
          },
 -        addPaymentline: function(cashregister) {
 -            var paymentLines = this.get('paymentLines');
 -            var newPaymentline = new module.Paymentline({},{cashregister:cashregister, pos:this.pos});
 -            if(cashregister.journal.type !== 'cash'){
 -                newPaymentline.set_amount( Math.max(this.getDueLeft(),0) );
 +        /* ---- Payment Lines --- */
 +        add_paymentline: function(cashregister) {
 +            var newPaymentline = new module.Paymentline({},{cashregister:cashregister, pos: this.pos});
 +            if(cashregister.journal.type !== 'cash' || this.pos.config.iface_precompute_cash){
 +                newPaymentline.set_amount( Math.max(this.get_due(),0) );
              }
 -            paymentLines.add(newPaymentline);
 -            this.selectPaymentline(newPaymentline);
 +            this.paymentlines.add(newPaymentline);
 +            this.select_paymentline(newPaymentline);
  
          },
 -        removePaymentline: function(line){
 +        get_paymentlines: function(){
 +            return this.paymentlines.models;
 +        },
 +        remove_paymentline: function(line){
              if(this.selected_paymentline === line){
 -                this.selectPaymentline(undefined);
 +                this.select_paymentline(undefined);
              }
 -            this.get('paymentLines').remove(line);
 +            this.paymentlines.remove(line);
          },
 -        getName: function() {
 -            return this.get('name');
 +        clean_empty_paymentlines: function() {
 +            var lines = this.paymentlines.models;
 +            var empty = [];
 +            for ( var i = 0; i < lines.length; i++) {
 +                if (!lines[i].get_amount()) {
 +                    empty.push(lines[i]);
 +                }
 +            }
 +            for ( var i = 0; i < empty.length; i++) {
 +                this.remove_paymentline(empty[i]);
 +            }
 +        },
 +        select_paymentline: function(line){
 +            if(line !== this.selected_paymentline){
 +                if(this.selected_paymentline){
 +                    this.selected_paymentline.set_selected(false);
 +                }
 +                this.selected_paymentline = line;
 +                if(this.selected_paymentline){
 +                    this.selected_paymentline.set_selected(true);
 +                }
 +                this.trigger('change:selected_paymentline',this.selected_paymentline);
 +            }
          },
 -        getSubtotal : function(){
 -            return (this.get('orderLines')).reduce((function(sum, orderLine){
 +        /* ---- Payment Status --- */
 +        get_subtotal : function(){
 +            return this.orderlines.reduce((function(sum, orderLine){
                  return sum + orderLine.get_display_price();
              }), 0);
          },
 -        getTotalTaxIncluded: function() {
 -            return (this.get('orderLines')).reduce((function(sum, orderLine) {
 +        get_total_with_tax: function() {
 +            return this.orderlines.reduce((function(sum, orderLine) {
                  return sum + orderLine.get_price_with_tax();
              }), 0);
          },
 -        getDiscountTotal: function() {
 -            return (this.get('orderLines')).reduce((function(sum, orderLine) {
 -                return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity());
 +        get_total_without_tax: function() {
 +            return this.orderlines.reduce((function(sum, orderLine) {
 +                return sum + orderLine.get_price_without_tax();
              }), 0);
          },
 -        getTotalTaxExcluded: function() {
 -            return (this.get('orderLines')).reduce((function(sum, orderLine) {
 -                return sum + orderLine.get_price_without_tax();
 +        get_total_discount: function() {
 +            return this.orderlines.reduce((function(sum, orderLine) {
 +                return sum + (orderLine.get_unit_price() * (orderLine.get_discount()/100) * orderLine.get_quantity());
              }), 0);
          },
 -        getTax: function() {
 -            return (this.get('orderLines')).reduce((function(sum, orderLine) {
 +        get_total_tax: function() {
 +            return this.orderlines.reduce((function(sum, orderLine) {
                  return sum + orderLine.get_tax();
              }), 0);
          },
 -        getTaxDetails: function(){
 +        get_total_paid: function() {
 +            return this.paymentlines.reduce((function(sum, paymentLine) {
 +                return sum + paymentLine.get_amount();
 +            }), 0);
 +        },
 +        get_tax_details: function(){
              var details = {};
              var fulldetails = [];
              var taxes_by_id = {};
                  taxes_by_id[this.pos.taxes[i].id] = this.pos.taxes[i];
              }
  
 -            this.get('orderLines').each(function(line){
 +            this.orderlines.each(function(line){
                  var ldetails = line.get_tax_details();
                  for(var id in ldetails){
                      if(ldetails.hasOwnProperty(id)){
  
              return fulldetails;
          },
 -        getPaidTotal: function() {
 -            return (this.get('paymentLines')).reduce((function(sum, paymentLine) {
 -                return sum + paymentLine.get_amount();
 -            }), 0);
 +        // Returns a total only for the orderlines with products belonging to the category 
 +        get_total_for_category_with_tax: function(categ_id){
 +            var total = 0;
 +            var self = this;
 +
 +            if (categ_id instanceof Array) {
 +                for (var i = 0; i < categ_id.length; i++) {
 +                    total += this.get_total_for_category_with_tax(categ_id[i]);
 +                }
 +                return total;
 +            }
 +            
 +            this.orderlines.each(function(line){
 +                if ( self.pos.db.category_contains(categ_id,line.product.id) ) {
 +                    total += line.get_price_with_tax();
 +                }
 +            });
 +
 +            return total;
          },
 -        getChange: function(paymentline) {
 +        get_total_for_taxes: function(tax_id){
 +            var total = 0;
 +            var self = this;
 +
 +            if (!(tax_id instanceof Array)) {
 +                tax_id = [tax_id];
 +            }
 +
 +            var tax_set = {};
 +
 +            for (var i = 0; i < tax_id.length; i++) {
 +                tax_set[tax_id[i]] = true;
 +            }
 +
 +            this.orderlines.each(function(line){
 +                var taxes_ids = line.get_product().taxes_id;
 +                for (var i = 0; i < taxes_ids.length; i++) {
 +                    if (tax_set[taxes_ids[i]]) {
 +                        total += line.get_price_with_tax();
 +                        return;
 +                    }
 +                }
 +            });
 +
 +            return total;
 +        },
 +        get_change: function(paymentline) {
              if (!paymentline) {
 -                var change = this.getPaidTotal() - this.getTotalTaxIncluded();
 +                var change = this.get_total_paid() - this.get_total_with_tax();
              } else {
 -                var change = -this.getTotalTaxIncluded(); 
 -                var lines  = this.get('paymentLines').models;
 +                var change = -this.get_total_with_tax(); 
 +                var lines  = this.paymentlines.models;
                  for (var i = 0; i < lines.length; i++) {
                      change += lines[i].get_amount();
                      if (lines[i] === paymentline) {
              }
              return round_pr(Math.max(0,change), this.pos.currency.rounding);
          },
 -        getDueLeft: function(paymentline) {
 +        get_due: function(paymentline) {
              if (!paymentline) {
 -                var due = this.getTotalTaxIncluded() - this.getPaidTotal();
 +                var due = this.get_total_with_tax() - this.get_total_paid();
              } else {
 -                var due = this.getTotalTaxIncluded();
 -                var lines = this.get('paymentLines').models;
 +                var due = this.get_total_with_tax();
 +                var lines = this.paymentlines.models;
                  for (var i = 0; i < lines.length; i++) {
                      if (lines[i] === paymentline) {
                          break;
              }
              return round_pr(Math.max(0,due), this.pos.currency.rounding);
          },
 -        isPaid: function(){
 -            return this.getDueLeft() === 0;
 +        is_paid: function(){
 +            return this.get_due() === 0;
          },
 -        isPaidWithCash: function(){
 -            return !!this.get('paymentLines').find( function(pl){
 +        is_paid_with_cash: function(){
 +            return !!this.paymentlines.find( function(pl){
                  return pl.cashregister.journal.type === 'cash';
              });
          },
          finalize: function(){
              this.destroy();
          },
 -        // sets the type of receipt 'receipt'(default) or 'invoice'
 -        set_receipt_type: function(type){
 -            this.receipt_type = type;
 +        destroy: function(args){
 +            Backbone.Model.prototype.destroy.apply(this,arguments);
 +            this.pos.db.remove_unpaid_order(this);
 +        },
 +        /* ---- Invoice --- */
 +        set_to_invoice: function(to_invoice) {
 +            this.to_invoice = to_invoice;
          },
 -        get_receipt_type: function(){
 -            return this.receipt_type;
 +        is_to_invoice: function(){
 +            return this.to_invoice;
          },
 +        /* ---- Client / Customer --- */
          // the client related to the current order.
          set_client: function(client){
              this.set('client',client);
              var client = this.get('client');
              return client ? client.name : "";
          },
 +        /* ---- Screen Status --- */
          // the order also stores the screen status, as the PoS supports
          // different active screens per order. This method is used to
          // store the screen status.
              if(arguments.length === 2){
                  this.screen_data[key] = value;
              }else if(arguments.length === 1){
 -                for(key in arguments[0]){
 +                for(var key in arguments[0]){
                      this.screen_data[key] = arguments[0][key];
                  }
              }
          },
 -        set_to_invoice: function(to_invoice) {
 -            this.to_invoice = to_invoice;
 -        },
 -        is_to_invoice: function(){
 -            return this.to_invoice;
 -        },
 -        // remove all the paymentlines with zero money in it
 -        clean_empty_paymentlines: function() {
 -            var lines = this.get('paymentLines').models;
 -            var empty = [];
 -            for ( var i = 0; i < lines.length; i++) {
 -                if (!lines[i].get_amount()) {
 -                    empty.push(lines[i]);
 -                }
 -            }
 -            for ( var i = 0; i < empty.length; i++) {
 -                this.removePaymentline(empty[i]);
 -            }
 -        },
          //see set_screen_data
          get_screen_data: function(key){
              return this.screen_data[key];
          },
 -        // exports a JSON for receipt printing
 -        export_for_printing: function(){
 -            var orderlines = [];
 -            this.get('orderLines').each(function(orderline){
 -                orderlines.push(orderline.export_for_printing());
 -            });
 -
 -            var paymentlines = [];
 -            this.get('paymentLines').each(function(paymentline){
 -                paymentlines.push(paymentline.export_for_printing());
 -            });
 -            var client  = this.get('client');
 -            var cashier = this.pos.cashier || this.pos.user;
 -            var company = this.pos.company;
 -            var shop    = this.pos.shop;
 -            var date = new Date();
 -
 -            return {
 -                orderlines: orderlines,
 -                paymentlines: paymentlines,
 -                subtotal: this.getSubtotal(),
 -                total_with_tax: this.getTotalTaxIncluded(),
 -                total_without_tax: this.getTotalTaxExcluded(),
 -                total_tax: this.getTax(),
 -                total_paid: this.getPaidTotal(),
 -                total_discount: this.getDiscountTotal(),
 -                tax_details: this.getTaxDetails(),
 -                change: this.getChange(),
 -                name : this.getName(),
 -                client: client ? client.name : null ,
 -                invoice_id: null,   //TODO
 -                cashier: cashier ? cashier.name : null,
 -                header: this.pos.config.receipt_header || '',
 -                footer: this.pos.config.receipt_footer || '',
 -                precision: {
 -                    price: 2,
 -                    money: 2,
 -                    quantity: 3,
 -                },
 -                date: { 
 -                    year: date.getFullYear(), 
 -                    month: date.getMonth(), 
 -                    date: date.getDate(),       // day of the month 
 -                    day: date.getDay(),         // day of the week 
 -                    hour: date.getHours(), 
 -                    minute: date.getMinutes() ,
 -                    isostring: date.toISOString(),
 -                    localestring: date.toLocaleString(),
 -                }, 
 -                company:{
 -                    email: company.email,
 -                    website: company.website,
 -                    company_registry: company.company_registry,
 -                    contact_address: company.partner_id[1], 
 -                    vat: company.vat,
 -                    name: company.name,
 -                    phone: company.phone,
 -                    logo:  this.pos.company_logo_base64,
 -                },
 -                shop:{
 -                    name: shop.name,
 -                },
 -                currency: this.pos.currency,
 -            };
 -        },
 -        export_as_JSON: function() {
 -            var orderLines, paymentLines;
 -            orderLines = [];
 -            (this.get('orderLines')).each(_.bind( function(item) {
 -                return orderLines.push([0, 0, item.export_as_JSON()]);
 -            }, this));
 -            paymentLines = [];
 -            (this.get('paymentLines')).each(_.bind( function(item) {
 -                return paymentLines.push([0, 0, item.export_as_JSON()]);
 -            }, this));
 -            return {
 -                name: this.getName(),
 -                amount_paid: this.getPaidTotal(),
 -                amount_total: this.getTotalTaxIncluded(),
 -                amount_tax: this.getTax(),
 -                amount_return: this.getChange(),
 -                lines: orderLines,
 -                statement_ids: paymentLines,
 -                pos_session_id: this.pos.pos_session.id,
 -                partner_id: this.get_client() ? this.get_client().id : false,
 -                user_id: this.pos.cashier ? this.pos.cashier.id : this.pos.user.id,
 -                uid: this.uid,
 -                sequence_number: this.sequence_number,
 -            };
 -        },
 -        getSelectedLine: function(){
 -            return this.selected_orderline;
 -        },
 -        selectLine: function(line){
 -            if(line){
 -                if(line !== this.selected_orderline){
 -                    if(this.selected_orderline){
 -                        this.selected_orderline.set_selected(false);
 -                    }
 -                    this.selected_orderline = line;
 -                    this.selected_orderline.set_selected(true);
 -                }
 -            }else{
 -                this.selected_orderline = undefined;
 -            }
 -        },
 -        deselectLine: function(){
 -            if(this.selected_orderline){
 -                this.selected_orderline.set_selected(false);
 -                this.selected_orderline = undefined;
 -            }
 -        },
 -        selectPaymentline: function(line){
 -            if(line !== this.selected_paymentline){
 -                if(this.selected_paymentline){
 -                    this.selected_paymentline.set_selected(false);
 -                }
 -                this.selected_paymentline = line;
 -                if(this.selected_paymentline){
 -                    this.selected_paymentline.set_selected(true);
 -                }
 -                this.trigger('change:selected_paymentline',this.selected_paymentline);
 -            }
 -        },
      });
  
      module.OrderCollection = Backbone.Collection.extend({
@@@ -15,9 -15,7 +15,9 @@@
  // that only one screen is shown at the same time and that show() is called after all
  // hide()s
  
 -function openerp_pos_screens(instance, module){ //module is instance.point_of_sale
 +openerp.point_of_sale.load_screens = function load_screens(instance, module){ //module is instance.point_of_sale
 +    "use strict";
 +
      var QWeb = instance.web.qweb,
      _t = instance.web._t;
  
      module.ScreenSelector = instance.web.Class.extend({
          init: function(options){
              this.pos = options.pos;
 -
 -            this.screen_set = options.screen_set || {};
 -
 -            this.popup_set = options.popup_set || {};
 -
 +            this.screen_set     = options.screen_set || {};
 +            this.popup_set      = options.popup_set || {};
              this.default_screen = options.default_screen;
 -
 -            this.current_popup = null;
 -
 -            this.current_mode = options.default_mode || 'cashier';
 -
 +            this.startup_screen = options.startup_screen;
 +            this.current_popup  = null;
 +            this.current_mode   = options.default_mode || 'cashier';
              this.current_screen = null; 
  
 -            for(screen_name in this.screen_set){
 +            for (var screen_name in this.screen_set) {
                  this.screen_set[screen_name].hide();
              }
              
 -            for(popup_name in this.popup_set){
 +            for (var popup_name in this.popup_set) {
                  this.popup_set[popup_name].hide();
              }
  
 -            this.pos.get('selectedOrder').set_screen_data({
 -                'screen': this.default_screen,
 -            });
 +            if (this.pos.get_order()) {
 +                this.pos.get_order().set_screen_data({
 +                    'screen': this.default_screen,
 +                });
 +            }
  
              this.pos.bind('change:selectedOrder', this.load_saved_screen, this);
          },
                  this.current_popup = null;
              }
          },
 -        load_saved_screen:  function(){
 +        load_saved_screen:  function(options){
 +            options = options || {};
              this.close_popup();
 -            var selectedOrder = this.pos.get('selectedOrder');
 +            var selectedOrder = this.pos.get_order();
              // 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(selectedOrder.get_screen_data('screen') || options.default_screen || this.default_screen,null,'refresh');
              //this.set_current_screen(this.default_screen,null,'refresh');
              
          },
  
              this.close_popup();
  
 -            var order = this.pos.get('selectedOrder');
 -            var old_screen_name = order.get_screen_data('screen');
 +            var order = this.pos.get_order();
 +            if (order) {
 +                var old_screen_name = order.get_screen_data('screen');
  
 -            order.set_screen_data('screen',screen_name);
 +                order.set_screen_data('screen',screen_name);
  
 -            if(params){
 -                order.set_screen_data('params',params);
 -            }
 +                if(params){
 +                    order.set_screen_data('params',params);
 +                }
  
 -            if( screen_name !== old_screen_name ){
 -                order.set_screen_data('previous-screen',old_screen_name);
 +                if( screen_name !== old_screen_name ){
 +                    order.set_screen_data('previous-screen',old_screen_name);
 +                }
              }
  
              if ( refresh || screen !== this.current_screen){
              }
          },
          get_current_screen: function(){
 -            return this.pos.get('selectedOrder').get_screen_data('screen') || this.default_screen;
 +            return this.pos.get_order().get_screen_data('screen') || this.default_screen;
          },
          back: function(){
 -            var previous = this.pos.get('selectedOrder').get_screen_data('previous-screen');
 +            var previous = this.pos.get_order().get_screen_data('previous-screen');
              if(previous){
                  this.set_current_screen(previous);
              }
          },
          get_current_screen_param: function(param){
 -            var params = this.pos.get('selectedOrder').get_screen_data('params');
 +            var params = this.pos.get_order().get_screen_data('params');
              return params ? params[param] : undefined;
          },
          set_default_screen: function(){
              this.set_current_screen(this.default_screen);
          },
 +        change_default_screen: function(screen){ 
 +            this.default_screen = screen;
 +        },
      });
  
      module.ScreenWidget = module.PosBaseWidget.extend({
  
          // what happens when a cashier id barcode is scanned.
          // the default behavior is the following : 
 -        // - if there's a user with a matching ean, put it as the active 'cashier', go to cashier mode, and return true
 +        // - if there's a user with a matching barcode, put it as the active 'cashier', go to cashier mode, and return true
          // - else : do nothing and return false. You probably want to extend this to show and appropriate error popup... 
          barcode_cashier_action: function(code){
              var users = this.pos.users;
              for(var i = 0, len = users.length; i < len; i++){
 -                if(users[i].ean13 === code.code){
 -                    this.pos.cashier = users[i];
 -                    this.pos_widget.username.refresh();
 +                if(users[i].barcode === code.code){
 +                    this.pos.set_cashier(users[i]);
 +                    this.pos_widget.username.renderElement();
                      return true;
                  }
              }
          
          // what happens when a client id barcode is scanned.
          // the default behavior is the following : 
 -        // - if there's a user with a matching ean, put it as the active 'client' and return true
 +        // - if there's a user with a matching barcode, put it as the active 'client' and return true
          // - else : return false. 
          barcode_client_action: function(code){
 -            var partner = this.pos.db.get_partner_by_ean13(code.code);
 +            var partner = this.pos.db.get_partner_by_barcode(code.code);
              if(partner){
 -                this.pos.get('selectedOrder').set_client(partner);
 -                this.pos_widget.username.refresh();
 +                this.pos.get_order().set_client(partner);
                  return true;
              }
              this.pos_widget.screen_selector.show_popup('error-barcode',code.code);
          // what happens when a discount barcode is scanned : the default behavior
          // is to set the discount on the last order.
          barcode_discount_action: function(code){
 -            var last_orderline = this.pos.get('selectedOrder').getLastOrderline();
 +            var last_orderline = this.pos.get_order().get_last_orderline();
              if(last_orderline){
                  last_orderline.set_discount(code.value)
              }
              this.pos_widget.set_leftpane_visible(this.show_leftpane);
  
              this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode());
 -
              this.pos.barcode_reader.set_action_callback({
                  'cashier': self.barcode_cashier_action ? function(code){ self.barcode_cashier_action(code); } : undefined ,
                  'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined ,
      module.ConfirmPopupWidget = module.PopUpWidget.extend({
          template: 'ConfirmPopupWidget',
          show: function(options){
 +            options = options || {};
              var self = this;
              this._super();
  
          },
      });
  
 -    module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
 -        template: 'ErrorInvoiceTransferPopupWidget',
 +    /**
 +     * A popup that allows the user to select one item from a list. 
 +     *
 +     * show_popup('selection',{
 +     *  message: 'Pick an Option',
 +     *      message: "Popup Title",
 +     *      list: [
 +     *          { label: 'foobar',  item: 45 },
 +     *          { label: 'bar foo', item: 'stuff' },
 +     *      ],
 +     *      confirm: function(item) {
 +     *          // get the item selected by the user.
 +     *      },
 +     *      cancel: function(){
 +     *          // user chose nothing
 +     *      }
 +     *  });
 +     */
 +
 +    module.SelectionPopupWidget = module.PopUpWidget.extend({
 +        template: 'SelectionPopupWidget',
 +        show: function(options){
 +            options = options || {};
 +            var self = this;
 +            this._super();
 +
 +            this.message = options.message || '';
 +            this.list    = options.list    || [];
 +            this.renderElement();
 +
 +            this.$('.button.cancel').click(function(){
 +                self.pos_widget.screen_selector.close_popup();
 +                if (options.cancel){
 +                    options.cancel.call(self);
 +                }
 +            });
 +
 +            this.$('.selection-item').click(function(){
 +                self.pos_widget.screen_selector.close_popup();
 +                if (options.confirm) {
 +                    var item = self.list[parseInt($(this).data('item-index'))];
 +                    item = item ? item.item : item;
 +                    options.confirm.call(self,item);
 +                }
 +            });
 +        },
      });
  
 -    module.UnsentOrdersPopupWidget = module.PopUpWidget.extend({
 -        template: 'UnsentOrdersPopupWidget',
 +    module.TextInputPopupWidget = module.PopUpWidget.extend({
 +        template: 'TextInputPopupWidget',
          show: function(options){
 +            options = options || {};
              var self = this;
 -            this._super(options);
 +            this._super();
 +
 +            this.message = options.message || '';
 +            this.comment = options.comment || '';
 +            this.value   = options.value   || '';
              this.renderElement();
 +            this.$('input,textarea').focus();
 +            
 +            this.$('.button.cancel').click(function(){
 +                self.pos_widget.screen_selector.close_popup();
 +                if( options.cancel ){
 +                    options.cancel.call(self);
 +                }
 +            });
 +
              this.$('.button.confirm').click(function(){
                  self.pos_widget.screen_selector.close_popup();
 +                var value = self.$('input,textarea').val();
 +                if( options.confirm ){
 +                    options.confirm.call(self,value);
 +                }
              });
          },
      });
  
 +    module.TextAreaPopupWidget = module.TextInputPopupWidget.extend({
 +        template: 'TextAreaPopupWidget',
 +    });
 +
 +    module.NumberPopupWidget = module.PopUpWidget.extend({
 +        template: 'NumberPopupWidget',
 +        click_numpad_button: function($el,event){
 +            this.numpad_input($el.data('action'));
 +        },
 +        numpad_input: function(input) { //FIXME -> Deduplicate code
 +            var oldbuf = this.inputbuffer.slice(0);
 +
 +            if (input === '.') {
 +                if (this.firstinput) {
 +                    this.inputbuffer = "0.";
 +                }else if (!this.inputbuffer.length || this.inputbuffer === '-') {
 +                    this.inputbuffer += "0.";
 +                } else if (this.inputbuffer.indexOf('.') < 0){
 +                    this.inputbuffer = this.inputbuffer + '.';
 +                }
 +            } else if (input === 'CLEAR') {
 +                this.inputbuffer = ""; 
 +            } else if (input === 'BACKSPACE') { 
 +                this.inputbuffer = this.inputbuffer.substring(0,this.inputbuffer.length - 1);
 +            } else if (input === '+') {
 +                if ( this.inputbuffer[0] === '-' ) {
 +                    this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
 +                }
 +            } else if (input === '-') {
 +                if ( this.inputbuffer[0] === '-' ) {
 +                    this.inputbuffer = this.inputbuffer.substring(1,this.inputbuffer.length);
 +                } else {
 +                    this.inputbuffer = '-' + this.inputbuffer;
 +                }
 +            } else if (input[0] === '+' && !isNaN(parseFloat(input))) {
 +                this.inputbuffer = '' + ((parseFloat(this.inputbuffer) || 0) + parseFloat(input));
 +            } else if (!isNaN(parseInt(input))) {
 +                if (this.firstinput) {
 +                    this.inputbuffer = '' + input;
 +                } else {
 +                    this.inputbuffer += input;
 +                }
 +            }
 +
 +            this.firstinput = this.inputbuffer.length === 0;
 +
 +            if (this.inputbuffer !== oldbuf) {
 +                this.$('.value').text(this.inputbuffer);
 +            }
 +        },
 +        show: function(options){
 +            options = options || {};
 +            var self = this;
 +            this._super();
 +
 +            this.message = options.message || '';
 +            this.comment = options.comment || '';
 +            this.inputbuffer = options.value   || '';
 +            this.renderElement();
 +            this.firstinput = true;
 +            
 +            this.$('.input-button,.mode-button').click(function(event){
 +                self.click_numpad_button($(this),event);
 +            });
 +            this.$('.button.cancel').click(function(){
 +                self.pos_widget.screen_selector.close_popup();
 +                if( options.cancel ){
 +                    options.cancel.call(self);
 +                }
 +            });
 +
 +            this.$('.button.confirm').click(function(){
 +                self.pos_widget.screen_selector.close_popup();
 +                if( options.confirm ){
 +                    options.confirm.call(self,self.inputbuffer);
 +                }
 +            });
 +        },
 +    });
 +
 +    module.PasswordPopupWidget = module.NumberPopupWidget.extend({
 +        renderElement: function(){
 +            this._super();
 +            this.$('.popup').addClass('popup-password');    // HELLO HACK !
 +        },
 +    });
 +
 +    module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({
 +        template: 'ErrorNoClientPopupWidget',
 +    });
 +
 +    module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({
 +        template: 'ErrorInvoiceTransferPopupWidget',
 +    });
 +
 +    module.UnsentOrdersPopupWidget = module.ConfirmPopupWidget.extend({
 +        template: 'UnsentOrdersPopupWidget',
 +    });
 +
 +    module.UnpaidOrdersPopupWidget = module.ConfirmPopupWidget.extend({
 +        template: 'UnpaidOrdersPopupWidget',
 +    });
 +
      module.ScaleScreenWidget = module.ScreenWidget.extend({
          template:'ScaleScreenWidget',
  
              }
          },
          order_product: function(){
 -            this.pos.get('selectedOrder').addProduct(this.get_product(),{ quantity: this.weight });
 +            this.pos.get_order().add_product(this.get_product(),{ quantity: this.weight });
          },
          get_product_name: function(){
              var product = this.get_product();
                      if(product.to_weight && self.pos.config.iface_electronic_scale){
                          self.pos_widget.screen_selector.set_current_screen('scale',{product: product});
                      }else{
 -                        self.pos.get('selectedOrder').addProduct(product);
 +                        self.pos.get_order().add_product(product);
                      }
                  },
                  product_list: this.pos.db.get_product_by_category(0)
  
              this.renderElement();
              this.details_visible = false;
 -            this.old_client = this.pos.get('selectedOrder').get('client');
 +            this.old_client = this.pos.get_order().get_client()
              this.new_client = this.old_client;
  
              this.$('.back').click(function(){
          barcode_client_action: function(code){
              if (this.editing_client) {
                  this.$('.detail.barcode').val(code.code);
 -            } else if (this.pos.db.get_partner_by_ean13(code.code)) {
 -                this.display_client_details('show',this.pos.db.get_partner_by_ean13(code.code));
 +            } else if (this.pos.db.get_partner_by_barcode(code.code)) {
 +                this.display_client_details('show',this.pos.db.get_partner_by_barcode(code.code));
              }
          },
          perform_search: function(query, associate_result){
          },
          save_changes: function(){
              if( this.has_client_changed() ){
 -                this.pos.get('selectedOrder').set_client(this.new_client);
 +                this.pos.get_order().set_client(this.new_client);
              }
          },
          has_client_changed: function(){
  
              fields.id           = partner.id || false;
              fields.country_id   = fields.country_id || false;
 -            fields.ean13        = fields.ean13 ? this.pos.barcode_reader.sanitize_ean(fields.ean13) : false; 
 +            fields.barcode        = fields.barcode ? this.pos.barcode_reader.sanitize_ean(fields.barcode) : false; 
  
              new instance.web.Model('res.partner').call('create_from_ui',[fields]).then(function(partner_id){
                  self.saved_client_details(partner_id);
+             },function(err,event){
+                 event.preventDefault();
+                 self.pos_widget.screen_selector.show_popup('error',{
+                     'message':_t('Error: Could not Save Changes'),
+                     'comment':_t('Your Internet connection is probably down.'),
+                 });
              });
          },
          
  
              this.refresh();
  
 -            if (!this.pos.get('selectedOrder')._printed) {
 +            if (!this.pos.get_order()._printed && this.pos.config.iface_print_auto) {
                  this.print();
              }
  
              // 2 seconds is the same as the default timeout for sending orders and so the dialog
              // should have appeared before the timeout... so yeah that's not ultra reliable. 
  
 -            this.lock_screen(true);  
 +            this.lock_screen(true);
              setTimeout(function(){
 -                self.lock_screen(false);  
 +                self.lock_screen(false);
              }, 2000);
          },
          lock_screen: function(locked) {
              }
          },
          print: function() {
 -            this.pos.get('selectedOrder')._printed = true;
 +            this.pos.get_order()._printed = true;
              window.print();
          },
          finish_order: function() {
              this.$('.pos-receipt-container').html(QWeb.render('PosTicket',{
                      widget:this,
                      order: order,
 -                    orderlines: order.get('orderLines').models,
 -                    paymentlines: order.get('paymentLines').models,
 +                    receipt: order.export_for_printing(),
 +                    orderlines: order.get_orderlines(),
 +                    paymentlines: order.get_paymentlines(),
                  }));
          },
      });
                  }
              }
  
 -            this.firstinput = false;
 +            this.firstinput = this.inputbuffer.length === 0;
  
              if (this.inputbuffer !== oldbuf) {
                  var order = this.pos.get_order();
              return numpad;
          },
          click_delete_paymentline: function(cid){
 -            var lines = this.pos.get_order().get('paymentLines').models;
 +            var lines = this.pos.get_order().get_paymentlines();
              for ( var i = 0; i < lines.length; i++ ) {
                  if (lines[i].cid === cid) {
 -                    this.pos.get_order().removePaymentline(lines[i]);
 +                    this.pos.get_order().remove_paymentline(lines[i]);
                      this.reset_input();
                      this.render_paymentlines();
                      return;
              }
          },
          click_paymentline: function(cid){
 -            var lines = this.pos.get_order().get('paymentLines').models;
 +            var lines = this.pos.get_order().get_paymentlines();
              for ( var i = 0; i < lines.length; i++ ) {
                  if (lines[i].cid === cid) {
 -                    this.pos.get_order().selectPaymentline(lines[i]);
 +                    this.pos.get_order().select_paymentline(lines[i]);
                      this.reset_input();
                      this.render_paymentlines();
                      return;
          render_paymentlines: function() {
              var self  = this;
              var order = this.pos.get_order();
 -            var lines = order.get('paymentLines').models;
 +            if (!order) {
 +                return;
 +            }
 +
 +            var lines = order.get_paymentlines();
  
              this.$('.paymentlines-container').empty();
              var lines = $(QWeb.render('PaymentScreen-Paymentlines', { 
                      break;
                  }
              }
 -            this.pos.get_order().addPaymentline( cashregister );
 +            this.pos.get_order().add_paymentline( cashregister );
              this.reset_input();
              this.render_paymentlines();
          },
                  this.$('.js_invoice').removeClass('highlight');
              }
          },
 +        click_set_customer: function(){
 +            this.pos_widget.screen_selector.set_current_screen('clientlist');
 +        },
 +        click_back: function(){
 +            this.pos_widget.screen_selector.set_current_screen('products');
 +        },
          renderElement: function() {
              var self = this;
              this._super();
              this.render_paymentlines();
  
              this.$('.back').click(function(){
 -                self.pos_widget.screen_selector.back();
 +                self.click_back();
              });
  
              this.$('.next').click(function(){
                  self.validate_order();
              });
  
 +            this.$('.js_set_customer').click(function(){
 +                self.click_set_customer();
 +            });
              this.$('.js_invoice').click(function(){
                  self.click_invoice();
              });
          watch_order_changes: function() {
              var self = this;
              var order = this.pos.get_order();
 +            if (!order) {
 +                return;
 +            }
              if(this.old_order){
                  this.old_order.unbind(null,null,this);
              }
          order_changes: function(){
              var self = this;
              var order = this.pos.get_order();
 -            if (order.isPaid()) {
 +            if (!order) {
 +                return;
 +            } else if (order.is_paid()) {
                  self.$('.next').addClass('highlight');
              }else{
                  self.$('.next').removeClass('highlight');
              }
          },
 +        print_escpos_receipt: function(){
 +            var env = {
 +                widget:  this,
 +                pos:     this.pos,
 +                order:   this.pos.get_order(),
 +                receipt: this.pos.get_order().export_for_printing(),
 +            };
 +
 +            this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',env));
 +        },
 +
          // Check if the order is paid, then sends it to the backend,
          // and complete the sale process
          validate_order: function() {
  
              var order = this.pos.get_order();
  
 -            if(order.get('orderLines').models.length === 0){
 +            // FIXME: this check is there because the backend is unable to
 +            // process empty orders. This is not the right place to fix it.
 +            if (order.get_orderlines().length === 0) {
                  this.pos_widget.screen_selector.show_popup('error',{
                      'message': _t('Empty Order'),
                      'comment': _t('There must be at least one product in your order before it can be validated'),
                  return;
              }
  
 -            if (!order.isPaid() || this.invoicing) {
 +            if (!order.is_paid() || this.invoicing) {
                  return;
              }
  
              // The exact amount must be paid if there is no cash payment method defined.
 -            if (Math.abs(order.getTotalTaxIncluded() - order.getPaidTotal()) > 0.00001) {
 +            if (Math.abs(order.get_total_with_tax() - order.get_total_paid()) > 0.00001) {
                  var cash = false;
                  for (var i = 0; i < this.pos.cashregisters.length; i++) {
                      cash = cash || (this.pos.cashregisters[i].journal.type === 'cash');
                  }
              }
  
 -            if (order.isPaidWithCash() && this.pos.config.iface_cashdrawer) { 
 -            
 +            if (order.is_paid_with_cash() && this.pos.config.iface_cashdrawer) { 
 +
                      this.pos.proxy.open_cashbox();
              }
  
              } else {
                  this.pos.push_order(order) 
                  if (this.pos.config.iface_print_via_proxy) {
 -                    var receipt = currentOrder.export_for_printing();
 -                    this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',{
 -                        receipt: receipt, widget: self,
 -                    }));
 +                    this.print_escpos_receipt();
                      order.finalize();    //finish order and go back to scan screen
                  } else {
                      this.pos_widget.screen_selector.set_current_screen(this.next_screen);
      });
  
  }
 +
@@@ -1,6 -1,4 +1,6 @@@
 -function openerp_pos_widgets(instance, module){ //module is instance.point_of_sale
 +openerp.point_of_sale.load_widgets = function load_widgets(instance, module){ //module is instance.point_of_sale
 +    "use strict";
 +
      var QWeb = instance.web.qweb;
        var _t = instance.web._t;
  
@@@ -85,7 -83,7 +85,7 @@@
          },
      });
  
 -    // The action pads contains the payment button and the customer selection button.
 +    // The action pad contains the payment button and the customer selection button
      module.ActionpadWidget = module.PosBaseWidget.extend({
          template: 'ActionpadWidget',
          renderElement: function() {
                  if(!self.editable){
                      return;
                  }
 -                self.pos.get('selectedOrder').selectLine(this.orderline);
 +                self.pos.get_order().select_orderline(this.orderline);
                  self.pos_widget.numpad.state.reset();
              };
              this.client_change_handler = function(event){
                  self.update_summary();
              }
 -            this.bind_order_events();
 +            if (this.pos.get_order()) {
 +                this.bind_order_events();
 +            }
          },
          enable_numpad: function(){
              this.disable_numpad();  //ensure we don't register the callbacks twice
              }
          },
          set_editable: function(editable){
 -            this.editable = editable;
 -            if(editable){
 -                this.enable_numpad();
 -            }else{
 -                this.disable_numpad();
 -                this.pos.get('selectedOrder').deselectLine();
 +            var order = this.pos.get_order();
 +            if (order) {
 +                this.editable = editable;
 +                if (editable) {
 +                    this.enable_numpad();
 +                }else{
 +                    this.disable_numpad();
 +                    order.deselect_orderline();
 +                }
              }
          },
          set_value: function(val) {
 -              var order = this.pos.get('selectedOrder');
 -              if (this.editable && order.getSelectedLine()) {
 +              var order = this.pos.get_order();
 +              if (this.editable && order.get_selected_orderline()) {
                  var mode = this.numpad_state.get('mode');
                  if( mode === 'quantity'){
 -                    order.getSelectedLine().set_quantity(val);
 +                    order.get_selected_orderline().set_quantity(val);
                  }else if( mode === 'discount'){
 -                    order.getSelectedLine().set_discount(val);
 +                    order.get_selected_orderline().set_discount(val);
                  }else if( mode === 'price'){
 -                    order.getSelectedLine().set_unit_price(val);
 +                    order.get_selected_orderline().set_unit_price(val);
                  }
                }
          },
          change_selected_order: function() {
 -            this.bind_order_events();
 -            this.renderElement();
 +            if (this.pos.get_order()) {
 +                this.bind_order_events();
 +                this.renderElement();
 +            }
 +        },
 +        orderline_add: function(){
 +            this.numpad_state.reset();
 +            this.renderElement('and_scroll_to_bottom');
 +        },
 +        orderline_remove: function(line){
 +            this.remove_orderline(line);
 +            this.numpad_state.reset();
 +            this.update_summary();
 +        },
 +        orderline_change: function(line){
 +            this.rerender_orderline(line);
 +            this.update_summary();
          },
          bind_order_events: function() {
 -
 -            var order = this.pos.get('selectedOrder');
 +            var order = this.pos.get_order();
                  order.unbind('change:client', this.client_change_handler);
                  order.bind('change:client', this.client_change_handler);
  
 -            var lines = order.get('orderLines');
 -                lines.unbind();
 -                lines.bind('add', function(){ 
 -                        this.numpad_state.reset();
 -                        this.renderElement(true);
 -                    },this);
 -                lines.bind('remove', function(line){
 -                        this.remove_orderline(line);
 -                        this.numpad_state.reset();
 -                        this.update_summary();
 -                    },this);
 -                lines.bind('change', function(line){
 -                        this.rerender_orderline(line);
 -                        this.update_summary();
 -                    },this);
 +            var lines = order.orderlines;
 +                lines.unbind('add',     this.orderline_add,    this);
 +                lines.bind('add',       this.orderline_add,    this);
 +                lines.unbind('remove',  this.orderline_remove, this);
 +                lines.bind('remove',    this.orderline_remove, this); 
 +                lines.unbind('change',  this.orderline_change, this);
 +                lines.bind('change',    this.orderline_change, this);
 +
          },
          render_orderline: function(orderline){
              var el_str  = openerp.qweb.render('Orderline',{widget:this, line:orderline}); 
              return el_node;
          },
          remove_orderline: function(order_line){
 -            if(this.pos.get('selectedOrder').get('orderLines').length === 0){
 +            if(this.pos.get_order().get_orderlines().length === 0){
                  this.renderElement();
              }else{
                  order_line.node.parentNode.removeChild(order_line.node);
          renderElement: function(scrollbottom){
              this.pos_widget.numpad.state.reset();
  
 -            var order  = this.pos.get('selectedOrder');
 -            var orderlines = order.get('orderLines').models;
 +            var order  = this.pos.get_order();
 +            if (!order) {
 +                return;
 +            }
 +            var orderlines = order.get_orderlines();
  
              var el_str  = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines});
  
              }
          },
          update_summary: function(){
 -            var order = this.pos.get('selectedOrder');
 -            var total     = order ? order.getTotalTaxIncluded() : 0;
 -            var taxes     = order ? total - order.getTotalTaxExcluded() : 0;
 +            var order = this.pos.get_order();
 +            var total     = order ? order.get_total_with_tax() : 0;
 +            var taxes     = order ? total - order.get_total_without_tax() : 0;
  
              this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
              this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
          },
      });
  
 -    module.OrderButtonWidget = module.PosBaseWidget.extend({
 -        template:'OrderButtonWidget',
 +    module.OrderSelectorWidget = module.PosBaseWidget.extend({
 +        template: 'OrderSelectorWidget',
          init: function(parent, options) {
 -            this._super(parent,options);
 -            var self = this;
 -
 -            this.order = options.order;
 -            this.order.bind('destroy',this.destroy, this );
 -            this.order.bind('change', this.renderElement, this );
 -            this.pos.bind('change:selectedOrder', this.renderElement,this );
 -        },
 -        renderElement:function(){
 -            this.selected = ( this.pos.get('selectedOrder') === this.order )
 -            this._super();
 -            var self = this;
 -            this.$el.click(function(){ 
 -                if( self.pos.get('selectedOrder') === self.order ){
 -                    var ss = self.pos.pos_widget.screen_selector;
 -                    if(ss.get_current_screen() === 'clientlist'){
 -                        ss.back();
 -                    }else if (ss.get_current_screen() !== 'receipt'){
 -                        ss.set_current_screen('clientlist');
 -                    }
 -                }else{
 -                    self.selectOrder();
 +            this._super(parent, options);
 +            this.pos.get('orders').bind('add remove change',this.renderElement,this);
 +            this.pos.bind('change:selectedOrder',this.renderElement,this);
 +        },
 +        get_order_by_uid: function(uid) {
 +            var orders = this.pos.get_order_list();
 +            for (var i = 0; i < orders.length; i++) {
 +                if (orders[i].uid === uid) {
 +                    return orders[i];
                  }
 -            });
 -            if( this.selected){
 -                this.$el.addClass('selected');
              }
 +            return undefined;
          },
 -        selectOrder: function(event) {
 -            this.pos.set({
 -                selectedOrder: this.order
 -            });
 +        order_click_handler: function(event,$el) {
 +            var order = this.get_order_by_uid($el.data('uid'));
 +            if (order) {
 +                this.pos.set_order(order);
 +            }
          },
 -        destroy: function(){
 -            this.order.unbind('destroy', this.destroy, this);
 -            this.order.unbind('change',  this.renderElement, this);
 -            this.pos.unbind('change:selectedOrder', this.renderElement, this);
 +        neworder_click_handler: function(event, $el) {
 +            this.pos.add_new_order();
 +        },
 +        deleteorder_click_handler: function(event, $el) {
 +            var self  = this;
 +            var order = this.pos.get_order(); 
 +            if (!order) {
 +                return;
 +            } else if ( !order.is_empty() ){
 +                this.pos_widget.screen_selector.show_popup('confirm',{
 +                    message: _t('Destroy Current Order ?'),
 +                    comment: _t('You will lose any data associated with the current order'),
 +                    confirm: function(){
 +                        self.pos.delete_current_order();
 +                    },
 +                });
 +            } else {
 +                this.pos.delete_current_order();
 +            }
 +        },
 +        renderElement: function(){
 +            var self = this;
              this._super();
 +            this.$('.order-button.select-order').click(function(event){
 +                self.order_click_handler(event,$(this));
 +            });
 +            this.$('.neworder-button').click(function(event){
 +                self.neworder_click_handler(event,$(this));
 +            });
 +            this.$('.deleteorder-button').click(function(event){
 +                self.deleteorder_click_handler(event,$(this));
 +            });
          },
      });
  
              if(query){
                  var products = this.pos.db.search_product_in_category(category.id,query)
                  if(buy_result && products.length === 1){
 -                        this.pos.get('selectedOrder').addProduct(products[0]);
 +                        this.pos.get_order().add_product(products[0]);
                          this.clear_search();
                  }else{
                      this.product_list_widget.set_product_list(products);
          init: function(parent, options){
              var options = options || {};
              this._super(parent,options);
          },
          set_user_mode: function(mode){
              this.mode = mode;
 -            this.refresh();
 -        },
 -        refresh: function(){
              this.renderElement();
          },
 +        renderElement: function(){
 +            var self = this;
 +            this._super();
 +
 +            this.$el.click(function(){
 +                self.click_username();
 +            });
 +        },
 +        click_username: function(){
 +            var self = this;
 +            this.pos_widget.select_user({
 +                'security':     true,
 +                'current_user': this.pos.get_cashier(),
 +                'message':      _t('Change Cashier'),
 +            }).then(function(user){
 +                self.pos.set_cashier(user);
 +                self.renderElement();
 +            });
 +        },
          get_name: function(){
 -            var user;
 -            if(this.mode === 'cashier'){
 -                user = this.pos.cashier || this.pos.user;
 -            }else{
 -                user = this.pos.get('selectedOrder').get_client()  || this.pos.user;
 -            }
 +            var user = this.pos.cashier || this.pos.user;
              if(user){
                  return user.name;
              }else{
                      },
                  });
              });
 +            this.$('.button.show_unpaid_orders').click(function(){
 +                self.pos.pos_widget.screen_selector.show_popup('unpaid-orders');
 +            });
 +            this.$('.button.delete_unpaid_orders').click(function(){
 +                self.pos.pos_widget.screen_selector.show_popup('confirm',{
 +                    message: _t('Delete Unpaid Orders ?'),
 +                    comment: _t('This operation will permanently destroy all unpaid orders from all sessions that have been put in the local storage. You will lose all the data and exit the point of sale. This operation cannot be undone.'),
 +                    confirm: function(){
 +                        self.pos.db.remove_all_unpaid_orders();
 +                        window.location = '/';
 +                    },
 +                });
 +            });
              _.each(this.eans, function(ean, name){
                  self.$('.button.'+name).click(function(){
                      self.$('input.ean').val(ean);
  
                  self.renderElement();
                  
 -                self.$('.neworder-button').click(function(){
 -                    self.pos.add_new_order();
 -                });
 -
 -                self.$('.deleteorder-button').click(function(){
 -                    if( !self.pos.get('selectedOrder').is_empty() ){
 -                        self.screen_selector.show_popup('confirm',{
 -                            message: _t('Destroy Current Order ?'),
 -                            comment: _t('You will lose any data associated with the current order'),
 -                            confirm: function(){
 -                                self.pos.delete_current_order();
 -                            },
 -                        });
 -                    }else{
 -                        self.pos.delete_current_order();
 -                    }
 -                });
 -                
 -                //when a new order is created, add an order button widget
 -                self.pos.get('orders').bind('add', function(new_order){
 -                    var new_order_button = new module.OrderButtonWidget(null, {
 -                        order: new_order,
 -                        pos: self.pos
 -                    });
 -                    new_order_button.appendTo(this.$('.orders'));
 -                    new_order_button.selectOrder();
 -                }, self);
 -
 -                self.pos.add_new_order();
 +                self.pos.load_orders();
 +                self.pos.set_start_order();
  
                  self.build_widgets();
  
                      window.screen.availHeight > window.innerHeight    )){
                      self.screen_selector.show_popup('fullscreen');
                  }
 -                self.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
 +                self.loading_hide();
  
                  self.pos.push_order();
  
          },
          loading_progress: function(fac){
              this.$('.loader .loader-feedback').removeClass('oe_hidden');
 -            this.$('.loader .progress').css({'width': ''+Math.floor(fac*100)+'%'});
 +            this.$('.loader .progress').removeClass('oe_hidden').css({'width': ''+Math.floor(fac*100)+'%'});
          },
          loading_message: function(msg,progress){
              this.$('.loader .loader-feedback').removeClass('oe_hidden');
              this.$('.loader .message').text(msg);
 -            if(typeof progress !== 'undefined'){
 +            if (typeof progress !== 'undefined') {
                  this.loading_progress(progress);
 +            } else {
 +                this.$('.loader .progress').addClass('oe_hidden');
              }
          },
          loading_skip: function(callback){
                  this.$('.loader .button.skip').addClass('oe_hidden');
              }
          },
 +        loading_hide: function(){
 +            this.$('.loader').animate({opacity:0},1500,'swing',function(){self.$('.loader').addClass('oe_hidden');});
 +        },
 +        loading_show: function(){
 +            this.$('.loader').removeClass('oe_hidden').animate({opacity:1},150,'swing');
 +        },
 +
 +        // A Generic UI that allow to select a user from a list.
 +        // It returns a deferred that resolves with the selected user 
 +        // upon success. Several options are available :
 +        // - security: passwords will be asked
 +        // - only_managers: restricts the list to managers
 +        // - current_user: password will not be asked if this 
 +        //                 user is selected.
 +        // - message: The title of the user selection list. 
 +        select_user: function(options){
 +            options = options || {};
 +            var self = this;
 +            var def  = new $.Deferred();
 +
 +            var list = [];
 +            for (var i = 0; i < this.pos.users.length; i++) {
 +                var user = this.pos.users[i];
 +                if (!options.only_managers || user.role === 'manager') {
 +                    list.push({
 +                        'label': user.name,
 +                        'item':  user,
 +                    });
 +                }
 +            }
 +
 +            this.pos_widget.screen_selector.show_popup('selection',{
 +                'message': options.message || _t('Select User'),
 +                list: list,
 +                confirm: function(user){ def.resolve(user); },
 +                cancel:  function(){ def.reject(); },
 +            });
 +
 +            return def.then(function(user){
 +                if (options.security && user !== options.current_user && user.pos_security_pin) {
 +                    var ret = new $.Deferred();
 +
 +                    self.pos_widget.screen_selector.show_popup('password',{
 +                        'message': _t('Password'),
 +                        confirm: function(password) {
 +                            if (password !== user.pos_security_pin) {
 +                                this.pos_widget.screen_selector.show_popup('error',{
 +                                    'message':_t('Password Incorrect'),
 +                                });
 +                                ret.reject();
 +                            } else {
 +                                ret.resolve(user);
 +                            }
 +                        },
 +                        cancel: function(){ ret.reject(); },
 +                    });
 +
 +                    return ret;
 +                } else {
 +                    return user;
 +                }
 +            });
 +        },
 +
 +        // checks if the current user (or the user provided) has manager
 +        // access rights. If not, a popup is shown allowing the user to
 +        // temporarily login as an administrator. 
 +        // This method returns a defferred, that succeeds with the 
 +        // manager user when the login is successfull.
 +        sudo: function(user){
 +            var def = new $.Deferred();
 +            user = user || this.pos.get_cashier();
 +
 +            if (user.role === 'manager') {
 +                return new $.Deferred().resolve(user);
 +            } else {
 +                return this.select_user({
 +                    security:       true, 
 +                    only_managers:  true,
 +                    message:       _t('Login as a Manager'),
 +                })
 +            }
 +        },
 +
          // This method instantiates all the screens, widgets, etc. If you want to add new screens change the
          // startup screen, etc, override this method.
          build_widgets: function() {
              this.fullscreen_popup = new module.FullscreenPopup(this,{});
              this.fullscreen_popup.appendTo(this.$el);
  
 +            this.selection_popup = new module.SelectionPopupWidget(this,{});
 +            this.selection_popup.appendTo(this.$el);
 +
 +            this.textinput_popup = new module.TextInputPopupWidget(this,{});
 +            this.textinput_popup.appendTo(this.$el);
 +
 +            this.textarea_popup = new module.TextAreaPopupWidget(this,{});
 +            this.textarea_popup.appendTo(this.$el);
 +
 +            this.number_popup = new module.NumberPopupWidget(this,{});
 +            this.number_popup.appendTo(this.$el);
 +
 +            this.password_popup = new module.PasswordPopupWidget(this,{});
 +            this.password_popup.appendTo(this.$el);
 +
              this.unsent_orders_popup = new module.UnsentOrdersPopupWidget(this,{});
              this.unsent_orders_popup.appendTo(this.$el);
  
 +            this.unpaid_orders_popup = new module.UnpaidOrdersPopupWidget(this,{});
 +            this.unpaid_orders_popup.appendTo(this.$el);
 +
              // --------  Misc ---------
  
 +            this.order_selector = new module.OrderSelectorWidget(this,{});
 +            this.order_selector.replace(this.$('.placeholder-OrderSelectorWidget'));
 +
 +            if(this.pos.config.use_proxy){
 +                this.proxy_status = new module.ProxyStatusWidget(this,{});
 +                this.proxy_status.appendTo(this.$('.pos-rightheader'));
 +            }
 +
 +            this.notification = new module.SynchNotificationWidget(this,{});
 +            this.notification.appendTo(this.$('.pos-rightheader'));
 +
              this.close_button = new module.HeaderButtonWidget(this,{
                  label: _t('Close'),
                  action: function(){ 
              });
              this.close_button.appendTo(this.$('.pos-rightheader'));
  
 -            this.notification = new module.SynchNotificationWidget(this,{});
 -            this.notification.appendTo(this.$('.pos-rightheader'));
  
 -            if(this.pos.config.use_proxy){
 -                this.proxy_status = new module.ProxyStatusWidget(this,{});
 -                this.proxy_status.appendTo(this.$('.pos-rightheader'));
 -            }
  
              this.username   = new module.UsernameWidget(this,{});
              this.username.replace(this.$('.placeholder-UsernameWidget'));
                      'clientlist': this.clientlist_screen,
                  },
                  popup_set:{
 -                    'error': this.error_popup,
 -                    'error-barcode': this.error_barcode_popup,
 -                    'error-traceback': this.error_traceback_popup,
 -                    'confirm': this.confirm_popup,
 -                    'fullscreen': this.fullscreen_popup,
 -                    'unsent-orders': this.unsent_orders_popup,
 +                    'error':            this.error_popup,
 +                    'error-barcode':    this.error_barcode_popup,
 +                    'error-traceback':  this.error_traceback_popup,
 +                    'textinput':        this.textinput_popup,
 +                    'textarea':         this.textarea_popup,
 +                    'number':           this.number_popup,
 +                    'password':         this.password_popup,
 +                    'confirm':          this.confirm_popup,
 +                    'fullscreen':       this.fullscreen_popup,
 +                    'selection':        this.selection_popup,
 +                    'unsent-orders':    this.unsent_orders_popup,
 +                    'unpaid-orders':    this.unpaid_orders_popup,
                  },
                  default_screen: 'products',
                  default_mode: 'cashier',
          },
          close: function() {
              var self = this;
 -
 -            function close(){
 -                self.pos.push_order().then(function(){
 -                    return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(function(res) {
 -                        window.location = '/web#action=' + res[0]['res_id'];
 -                    },function(err,event) {
 -                        event.preventDefault();
 -                        self.screen_selector.show_popup('error',{
 -                            'message': _t('Could not close the point of sale.'),
 -                            'comment': _t('Your internet connection is probably down.'),
 -                        });
 -                        self.close_button.renderElement();
 +            self.loading_show();
 +            self.loading_message(_t('Closing ...'));
 +
 +            self.pos.push_order().then(function(){
-                 return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id']).pipe(function(res) {
++                return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_client_pos_menu']], ['res_id'])
++                .pipe(function(res) {
 +                    window.location = '/web#action=' + res[0]['res_id'];
++                },function(err,event) {
++                    event.preventDefault();
++                    self.screen_selector.show_popup('error',{
++                        'message': _t('Could not close the point of sale.'),
++                        'comment': _t('Your internet connection is probably down.'),
+                     });
++                    self.close_button.renderElement();
                  });
 -            }
 -
 -            var draft_order = _.find( self.pos.get('orders').models, function(order){
 -                return order.get('orderLines').length !== 0 && order.get('paymentLines').length === 0;
              });
 -            if(draft_order){
 -                if (confirm(_t("Pending orders will be lost.\nAre you sure you want to leave this session?"))) {
 -                    close();
 -                }
 -            }else{
 -                close();
 -            }
 +
          },
          destroy: function() {
              this.pos.destroy();
@@@ -32,7 -32,7 +32,7 @@@ from openerp.tools import DEFAULT_SERVE
  import psycopg2
  
  import openerp.addons.decimal_precision as dp
- from openerp.tools.float_utils import float_round
+ from openerp.tools.float_utils import float_round, float_compare
  
  def ean_checksum(eancode):
      """returns the checksum of an ean string of length 13, returns -1 if the string has the wrong length"""
      check = int(10 - math.ceil(total % 10.0)) %10
      return check
  
 -def check_ean(eancode):
 -    """returns True if eancode is a valid ean13 string, or null"""
 -    if not eancode:
 -        return True
 -    if len(eancode) != 13:
 -        return False
 -    try:
 -        int(eancode)
 -    except:
 -        return False
 -    return ean_checksum(eancode) == int(eancode[-1])
 -
  def sanitize_ean13(ean13):
      """Creates and returns a valid ean13 from an invalid one"""
      if not ean13:
@@@ -130,7 -142,7 +130,7 @@@ class product_uom(osv.osv)
              string='Bigger Ratio',
              help='How many times this Unit of Measure is bigger than the reference Unit of Measure in this category:\n'\
                      '1 * (this unit) = ratio * (reference unit)', required=True),
-         'rounding': fields.float('Rounding Precision', digits_compute=dp.get_precision('Product Unit of Measure'), required=True,
+         'rounding': fields.float('Rounding Precision', digits=0, required=True,
              help="The computed quantity will be a multiple of this value. "\
                   "Use 1.0 for a Unit of Measure that cannot be further split, such as a piece."),
          'active': fields.boolean('Active', help="By unchecking the active field you can disable a unit of measure without deleting it."),
      _defaults = {
          'active': 1,
          'rounding': 0.01,
+         'factor': 1,
          'uom_type': 'reference',
+         'factor': 1.0,
      }
  
      _sql_constraints = [
          ('factor_gt_zero', 'CHECK (factor!=0)', 'The conversion ratio for a unit of measure cannot be 0!')
      ]
  
-     def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False, round=True):
+     def _compute_qty(self, cr, uid, from_uom_id, qty, to_uom_id=False, round=True, rounding_method='UP'):
          if not from_uom_id or not qty or not to_uom_id:
              return qty
          uoms = self.browse(cr, uid, [from_uom_id, to_uom_id])
              from_unit, to_unit = uoms[0], uoms[-1]
          else:
              from_unit, to_unit = uoms[-1], uoms[0]
-         return self._compute_qty_obj(cr, uid, from_unit, qty, to_unit, round=round)
+         return self._compute_qty_obj(cr, uid, from_unit, qty, to_unit, round=round, rounding_method=rounding_method)
  
-     def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, round=True, context=None):
+     def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, round=True, rounding_method='UP', context=None):
          if context is None:
              context = {}
          if from_unit.category_id.id != to_unit.category_id.id:
                  raise osv.except_osv(_('Error!'), _('Conversion from Product UoM %s to Default UoM %s is not possible as they both belong to different Category!.') % (from_unit.name,to_unit.name,))
              else:
                  return qty
-         # First round to the precision of the original unit, so that
-         # float representation errors do not bias the following ceil()
-         # e.g. with 1 / (1/12) we could get 12.0000048, ceiling to 13! 
-         amount = float_round(qty/from_unit.factor, precision_rounding=from_unit.rounding)
+         amount = qty/from_unit.factor
          if to_unit:
              amount = amount * to_unit.factor
              if round:
-                 amount = ceiling(amount, to_unit.rounding)
+                 amount = float_round(amount, precision_rounding=to_unit.rounding, rounding_method=rounding_method)
          return amount
  
      def _compute_price(self, cr, uid, from_uom_id, price, to_uom_id=False):
@@@ -483,7 -494,6 +482,7 @@@ class product_template(osv.osv)
  
      _columns = {
          'name': fields.char('Name', required=True, translate=True, select=True),
 +        'sequence': fields.integer('Sequence', help='Gives the sequence order when displaying a product list'),
          'product_manager': fields.many2one('res.users','Product Manager'),
          'description': fields.text('Description',translate=True,
              help="A precise description of the Product, used only for internal information purposes."),
          'product_variant_count': fields.function( _get_product_variant_count, type='integer', string='# of Product Variants'),
  
          # related to display product product information if is_product_variant
 -        'ean13': fields.related('product_variant_ids', 'ean13', type='char', string='EAN13 Barcode'),
 +        'barcode': fields.related('product_variant_ids', 'barcode', type='char', string='Barcode', oldname='ean13'),
          'default_code': fields.related('product_variant_ids', 'default_code', type='char', string='Internal Reference'),
      }
  
          for tmpl_id in tmpl_ids:
  
              # list of values combination
+             variant_alone = []
              all_variants = [[]]
              for variant_id in tmpl_id.attribute_line_ids:
-                 if len(variant_id.value_ids) > 1:
-                     temp_variants = []
+                 if len(variant_id.value_ids) == 1:
+                     variant_alone.append(variant_id.value_ids[0])
+                 temp_variants = []
+                 for variant in all_variants:
                      for value_id in variant_id.value_ids:
-                         for variant in all_variants:
-                             temp_variants.append(variant + [int(value_id)])
-                     all_variants = temp_variants
+                         temp_variants.append(variant + [int(value_id)])
+                 all_variants = temp_variants
+             # adding an attribute with only one value should not recreate product
+             # write this attribute on every product to make sure we don't lose them
+             for variant_id in variant_alone:
+                 product_ids = []
+                 for product_id in tmpl_id.product_variant_ids:
+                     if variant_id.id not in map(int, product_id.attribute_value_ids):
+                         product_ids.append(product_id.id)
+                 product_obj.write(cr, uid, product_ids, {'attribute_value_ids': [(4, variant_id.id)]}, context=ctx)
  
              # check product
              variant_ids_to_active = []
          # TODO: this is needed to set given values to first variant after creation
          # these fields should be moved to product as lead to confusion
          related_vals = {}
 -        if vals.get('ean13'):
 -            related_vals['ean13'] = vals['ean13']
 +        if vals.get('barcode'):
 +            related_vals['barcode'] = vals['barcode']
          if vals.get('default_code'):
              related_vals['default_code'] = vals['default_code']
          if related_vals:
          'categ_id' : _default_category,
          'type' : 'consu',
          'active': True,
 +        'sequence': 1,
      }
  
      def _check_uom(self, cursor, user, ids, context=None):
@@@ -912,7 -932,7 +922,7 @@@ class product_product(osv.osv)
          'default_code' : fields.char('Internal Reference', select=True),
          'active': fields.boolean('Active', help="If unchecked, it will allow you to hide the product without removing it."),
          'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade", select=True, auto_join=True),
 -        'ean13': fields.char('EAN13 Barcode', size=13, help="International Article Number used for product identification."),
 +        'barcode': fields.char('Barcode', help="International Article Number used for product identification.", oldname='ean13'),
          'name_template': fields.related('product_tmpl_id', 'name', string="Template Name", type='char', store={
              'product.template': (_get_name_template_ids, ['name'], 10),
              'product.product': (lambda self, cr, uid, ids, c=None: ids, [], 10),
                  return {'value': {'uom_po_id': uom_id}}
          return False
  
 -    def _check_ean_key(self, cr, uid, ids, context=None):
 -        for product in self.read(cr, uid, ids, ['ean13'], context=context):
 -            if not check_ean(product['ean13']):
 -                return False
 -        return True
 -
 -    _constraints = [(_check_ean_key, 'You provided an invalid "EAN13 Barcode" reference. You may use the "Internal Reference" field instead.', ['ean13'])]
 -
      def on_order(self, cr, uid, ids, orderline, quantity):
          pass
  
              if operator in positive_operators:
                  ids = self.search(cr, user, [('default_code','=',name)]+ args, limit=limit, context=context)
                  if not ids:
 -                    ids = self.search(cr, user, [('ean13','=',name)]+ args, limit=limit, context=context)
 +                    ids = self.search(cr, user, [('barcode','=',name)]+ args, limit=limit, context=context)
              if not ids and operator not in expression.NEGATIVE_TERM_OPERATORS:
                  # Do not merge the 2 next lines into one single search, SQL search performance would be abysmal
                  # on a database with thousands of matching products, due to the huge merge+unique needed for the
  class product_packaging(osv.osv):
      _name = "product.packaging"
      _description = "Packaging"
 -    _rec_name = 'ean'
 +    _rec_name = 'barcode'
      _order = 'sequence'
      _columns = {
          'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of packaging."),
          'rows' : fields.integer('Number of Layers', required=True,
              help='The number of layers on a pallet or box'),
          'product_tmpl_id' : fields.many2one('product.template', 'Product', select=1, ondelete='cascade', required=True),
 -        'ean' : fields.char('EAN', size=14, help="The EAN code of the package unit."),
 +        'barcode' : fields.char('Barcode', help="The Barcode of the package unit.", oldname="ean"),
          'code' : fields.char('Code', help="The code of the transport unit."),
          'weight': fields.float('Total Package Weight',
              help='The weight of a full package, pallet or box.'),
      }
  
 -    def _check_ean_key(self, cr, uid, ids, context=None):
 -        for pack in self.browse(cr, uid, ids, context=context):
 -            if not check_ean(pack.ean):
 -                return False
 -        return True
 -
 -    _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean'])]
 -
      def name_get(self, cr, uid, ids, context=None):
          if not len(ids):
              return []
          res = []
          for pckg in self.browse(cr, uid, ids, context=context):
 -            p_name = pckg.ean and '[' + pckg.ean + '] ' or ''
 +            p_name = pckg.barcode and '[' + pckg.barcode + '] ' or ''
              p_name += pckg.ul.name
              res.append((pckg.id,p_name))
          return res
@@@ -1254,7 -1290,7 +1264,7 @@@ class res_currency(osv.osv)
              main_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
              for currency_id in ids:
                  if currency_id == main_currency.id:
-                     if main_currency.rounding < 10 ** -digits:
+                     if float_compare(main_currency.rounding, 10 ** -digits, precision_digits=6) == -1:
                          return False
          return True
  
@@@ -1273,7 -1309,7 +1283,7 @@@ class decimal_precision(osv.osv)
              main_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
              for decimal_precision in ids:
                  if decimal_precision == account_precision_id:
-                     if main_currency.rounding < 10 ** -digits:
+                     if float_compare(main_currency.rounding, 10 ** -digits, precision_digits=6) == -1:
                          return False
          return True
  
                                      <group groups="product.group_uos" string="Unit of Measure">
                                          <field name="uos_id"/>
                                          <field name="uos_coeff"/>
+                                         <field name="mes_type"/>
                                      </group>
                                  </group>
                                  <group name="website_and_pos" col="2">
              <field name="view_type">form</field>
              <field name="context">{'search_default_product_tmpl_id': [active_id], 'default_product_tmpl_id': active_id}</field>
              <field name="search_view_id" ref="product_search_form_view"/>
+             <field name="view_id" eval="False"/> <!-- Force empty -->
              <field name="help" type="html">
                <p class="oe_view_nocontent_create">
                  Click to define a new product.
                      <attribute name="name">Product Template</attribute>
                  </xpath>
                  <field name="active" position="after">
 -                    <field name="ean13" attrs="{'invisible': [('product_variant_count', '>', 1)]}"/>
 +                    <field name="barcode" attrs="{'invisible': [('product_variant_count', '>', 1)]}"/>
                      <field name="default_code" attrs="{'invisible': [('product_variant_count', '>', 1)]}"/>
                  </field>
                  <xpath expr="//page[@string='Sales']" position="after">
                      <field name="lst_price"/>
                      <field name="price" invisible="not context.get('pricelist',False)"/>
                      <field name="uom_id"/>
 -                    <field name="ean13"/>
 +                    <field name="barcode"/>
                      <field name="state" invisible="1"/>
                      <field name="product_tmpl_id" invisible="1"/>
                  </tree>
                      <attribute name="string">Product Variant</attribute>
                  </form>
                  <field name="active" position="after">
 -                    <field name="ean13"/>
 +                    <field name="barcode"/>
                      <field name="default_code"/>
                  </field>
                  <field name="list_price" position="attributes">
                                  <field name="factor"
                                      digits="[42,5]"
                                      attrs="{'invisible':[('uom_type','!=','smaller')],
-                                             'readonly':[('uom_type','!=','smaller')]}"/>
+                                             'readonly':[('uom_type','=','bigger')]}"/>
                                  <field name="factor_inv"
                                      digits="[42,5]"
                                      attrs="{'invisible':[('uom_type','!=','bigger')],
                          </group>
                          <group>
                              <field name="active"/>
-                             <field name="rounding"/>
+                             <field name="rounding" digits="[42, 5]"/>
                          </group>
                      </group>
                  </form>
                  <tree string="Packaging">
                      <field name="sequence" widget="handle"/>
                      <field name="product_tmpl_id"/>
 -                    <field name="ean"/>
 +                    <field name="barcode"/>
                      <field name="qty"/>
                      <field name="ul"/>
                      <field name="ul_container"/>
                      <group col="4">
                          <field name="product_tmpl_id"/>
                          <newline/>
 -                        <field name="ean"/>
 +                        <field name="barcode"/>
                          <field name="sequence" invisible="1"/>
                          <newline/>
                          <field name="qty"/>
              <field name="arch" type="xml">
                  <form string="Packaging">
                      <group col="4">
 -                        <field name="ean"/>
 +                        <field name="barcode"/>
                          <field name="sequence" invisible="1"/>
                          <newline/>
                          <field name="qty"/>
diff --combined addons/stock/stock.py
@@@ -23,13 -23,12 +23,14 @@@ from datetime import date, datetim
  from dateutil import relativedelta
  import json
  import time
 +import sets
  
 +import openerp
  from openerp.osv import fields, osv
+ from openerp.tools.float_utils import float_compare, float_round
  from openerp.tools.translate import _
  from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
 -from openerp import SUPERUSER_ID, api
 +from openerp import SUPERUSER_ID, api, models
  import openerp.addons.decimal_precision as dp
  from openerp.addons.procurement import procurement
  import logging
@@@ -124,7 -123,7 +125,7 @@@ class stock_location(osv.osv)
                         \n* Production: Virtual counterpart location for production operations: this location consumes the raw material and produces finished products
                         \n* Transit Location: Counterpart location that should be used in inter-companies or inter-warehouses operations
                        """, select=True),
-         'complete_name': fields.function(_complete_name, type='char', string="Location Name",
+         'complete_name': fields.function(_complete_name, type='char', string="Full Location Name",
                              store={'stock.location': (_get_sublocations, ['name', 'location_id', 'active'], 10)}),
          'location_id': fields.many2one('stock.location', 'Parent Location', select=True, ondelete='cascade'),
          'child_ids': fields.one2many('stock.location', 'location_id', 'Contains'),
          'scrap_location': fields.boolean('Is a Scrap Location?', help='Check this box to allow using this location to put scrapped/damaged goods.'),
          'removal_strategy_id': fields.many2one('product.removal', 'Removal Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to take the products from, which lot etc. for this location. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here."),
          'putaway_strategy_id': fields.many2one('product.putaway', 'Put Away Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to store the products. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here."),
 -        'loc_barcode': fields.char('Location Barcode'),
 +        'barcode': fields.char('Barcode', oldname='loc_barcode'),
      }
      _defaults = {
          'active': True,
          'posz': 0,
          'scrap_location': False,
      }
 -    _sql_constraints = [('loc_barcode_company_uniq', 'unique (loc_barcode,company_id)', 'The barcode for a location must be unique per company !')]
 +    _sql_constraints = [('barcode_company_uniq', 'unique (barcode,company_id)', 'The barcode for a location must be unique per company !')]
  
      def create(self, cr, uid, default, context=None):
 -        if not default.get('loc_barcode', False):
 -            default.update({'loc_barcode': default.get('complete_name', False)})
 +        if not default.get('barcode', False):
 +            default.update({'barcode': default.get('complete_name', False)})
          return super(stock_location, self).create(cr, uid, default, context=context)
  
      def get_putaway_strategy(self, cr, uid, location, product, context=None):
@@@ -379,9 -378,10 +380,10 @@@ class stock_quant(osv.osv)
              if move.picking_id:
                  self.pool.get('stock.picking').write(cr, uid, [move.picking_id.id], {'recompute_pack_op': True}, context=context)
          #check if move'state needs to be set as 'assigned'
-         if reserved_availability == move.product_qty and move.state in ('confirmed', 'waiting'):
+         rounding = move.product_id.uom_id.rounding
+         if float_compare(reserved_availability, move.product_qty, precision_rounding=rounding) == 0 and move.state in ('confirmed', 'waiting')  :
              self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context)
-         elif reserved_availability > 0 and not move.partially_available:
+         elif float_compare(reserved_availability, 0, precision_rounding=rounding) > 0 and not move.partially_available:
              self.pool.get('stock.move').write(cr, uid, [move.id], {'partially_available': True}, context=context)
  
      def quants_move(self, cr, uid, quants, move, location_to, location_from=False, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
                  quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, force_location_from=location_from, force_location_to=location_to, context=context)
              else:
                  self._quant_split(cr, uid, quant, qty, context=context)
-                 quant.refresh()
                  to_move_quants.append(quant)
              quants_reconcile.append(quant)
          if to_move_quants:
          if location_to.usage == 'internal':
              if self.search(cr, uid, [('product_id', '=', move.product_id.id), ('qty','<', 0)], limit=1, context=context):
                  for quant in quants_reconcile:
-                     quant.refresh()
                      self._quant_reconcile_negative(cr, uid, quant, move, context=context)
  
      def move_quants_write(self, cr, uid, quants, move, location_dest_id, dest_package_id, context=None):
          if not prefered_domain_list:
              return self.quants_get(cr, uid, location, product, qty, domain=domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context)
          for prefered_domain in prefered_domain_list:
-             if res_qty > 0:
+             res_qty_cmp = float_compare(res_qty, 0, precision_rounding=product.uom_id.rounding)
+             if res_qty_cmp > 0:
                  #try to replace the last tuple (None, res_qty) with something that wasn't chosen at first because of the prefered order
                  quants.pop()
                  tmp_quants = self.quants_get(cr, uid, location, product, res_qty, domain=domain + prefered_domain, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=context)
              context = {}
          price_unit = self.pool.get('stock.move').get_price_unit(cr, uid, move, context=context)
          location = force_location_to or move.location_dest_id
+         rounding = move.product_id.uom_id.rounding
          vals = {
              'product_id': move.product_id.id,
              'location_id': location.id,
-             'qty': qty,
+             'qty': float_round(qty, precision_rounding=rounding),
              'cost': price_unit,
              'history_ids': [(4, move.id)],
              'in_date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
              #it means that a negative quant has to be created as well.
              negative_vals = vals.copy()
              negative_vals['location_id'] = force_location_from and force_location_from.id or move.location_id.id
-             negative_vals['qty'] = -qty
+             negative_vals['qty'] = float_round(-qty, precision_rounding=rounding)
              negative_vals['cost'] = price_unit
              negative_vals['negative_move_id'] = move.id
              negative_vals['package_id'] = src_package_id
  
      def _quant_split(self, cr, uid, quant, qty, context=None):
          context = context or {}
-         if (quant.qty > 0 and quant.qty <= qty) or (quant.qty <= 0 and quant.qty >= qty):
+         rounding = quant.product_id.uom_id.rounding
+         if float_compare(abs(quant.qty), abs(qty), precision_rounding=rounding) <= 0: # if quant <= qty in abs, take it entirely
              return False
-         new_quant = self.copy(cr, SUPERUSER_ID, quant.id, default={'qty': quant.qty - qty}, context=context)
-         self.write(cr, SUPERUSER_ID, quant.id, {'qty': qty}, context=context)
-         quant.refresh()
+         qty_round = float_round(qty, precision_rounding=rounding)
+         new_qty_round = float_round(quant.qty - qty, precision_rounding=rounding)
+         new_quant = self.copy(cr, SUPERUSER_ID, quant.id, default={'qty': new_qty_round, 'history_ids': [(4, x.id) for x in quant.history_ids]}, context=context)
+         self.write(cr, SUPERUSER_ID, quant.id, {'qty': qty_round}, context=context)
          return self.browse(cr, uid, new_quant, context=context)
  
      def _get_latest_move(self, cr, uid, quant, context=None):
              dom += [('lot_id', '=', quant.lot_id.id)]
          dom += [('owner_id', '=', quant.owner_id.id)]
          dom += [('package_id', '=', quant.package_id.id)]
+         dom += [('id', '!=', quant.propagated_from_id.id)]
          quants = self.quants_get(cr, uid, quant.location_id, quant.product_id, quant.qty, dom, context=context)
+         product_uom_rounding = quant.product_id.uom_id.rounding
          for quant_neg, qty in quants:
-             if not quant_neg:
+             if not quant_neg or not solving_quant:
                  continue
              to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context)
              if not to_solve_quant_ids:
              solving_qty = qty
              solved_quant_ids = []
              for to_solve_quant in self.browse(cr, uid, to_solve_quant_ids, context=context):
-                 if solving_qty <= 0:
+                 if float_compare(solving_qty, 0, precision_rounding=product_uom_rounding) <= 0:
                      continue
                  solved_quant_ids.append(to_solve_quant.id)
                  self._quant_split(cr, uid, to_solve_quant, min(solving_qty, to_solve_quant.qty), context=context)
                  remaining_to_solve_quant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id), ('id', 'not in', solved_quant_ids)], context=context)
                  if remaining_to_solve_quant_ids:
                      self.write(cr, SUPERUSER_ID, remaining_to_solve_quant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context)
+             if solving_quant.propagated_from_id and solved_quant_ids:
+                 self.write(cr, uid, solved_quant_ids, {'propagated_from_id': solving_quant.propagated_from_id.id}, context=context)
              #delete the reconciled quants, as it is replaced by the solved quants
              self.unlink(cr, SUPERUSER_ID, [quant_neg.id], context=context)
-             #price update + accounting entries adjustments
-             self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context)
-             #merge history (and cost?)
-             self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context)
+             if solved_quant_ids:
+                 #price update + accounting entries adjustments
+                 self._price_update(cr, uid, solved_quant_ids, solving_quant.cost, context=context)
+                 #merge history (and cost?)
+                 self._quants_merge(cr, uid, solved_quant_ids, solving_quant, context=context)
              self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context)
              solving_quant = remaining_solving_quant
  
              domain += [('company_id', '=', self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id)]
          res = []
          offset = 0
-         while quantity > 0:
+         while float_compare(quantity, 0, precision_rounding=product.uom_id.rounding) > 0:
              quants = self.search(cr, uid, domain, order=orderby, limit=10, offset=offset, context=context)
              if not quants:
                  res.append((None, quantity))
                  break
              for quant in self.browse(cr, uid, quants, context=context):
-                 if quantity >= abs(quant.qty):
+                 rounding = product.uom_id.rounding
+                 if float_compare(quantity, abs(quant.qty), precision_rounding=rounding) >= 0:
                      res += [(quant, abs(quant.qty))]
                      quantity -= abs(quant.qty)
-                 elif quantity != 0:
+                 elif float_compare(quantity, 0.0, precision_rounding=rounding) != 0:
                      res += [(quant, quantity)]
                      quantity = 0
                      break
@@@ -866,7 -874,6 +876,6 @@@ class stock_picking(osv.osv)
          for pick in self.browse(cr, uid, ids, context=context):
              if pick.state == 'draft':
                  self.action_confirm(cr, uid, [pick.id], context=context)
-             pick.refresh()
              #skip the moves that don't need to be checked
              move_ids = [x.id for x in pick.move_lines if x.state not in ('draft', 'cancel', 'done')]
              if not move_ids:
                  product_putaway_strats[product.id] = location
              return location or picking.location_dest_id.id
  
+         # If we encounter an UoM that is smaller than the default UoM or the one already chosen, use the new one instead.
+         product_uom = {} # Determines UoM used in pack operations
+         for move in picking.move_lines:
+             if not product_uom.get(move.product_id.id):
+                 product_uom[move.product_id.id] = move.product_id.uom_id.id
+             if move.product_uom.id != move.product_id.uom_id.id and move.product_uom.factor > product_uom[move.product_id.id]:
+                 product_uom[move.product_id.id] = move.product_uom.id
          pack_obj = self.pool.get("stock.quant.package")
          quant_obj = self.pool.get("stock.quant")
          vals = []
                  qtys_grouped[key] = qty
  
          # Create the necessary operations for the grouped quants and remaining qtys
+         uom_obj = self.pool.get('product.uom')
          for key, qty in qtys_grouped.items():
+             product = self.pool.get("product.product").browse(cr, uid, key[0], context=context)
+             uom_id = product.uom_id.id
+             qty_uom = qty
+             if product_uom.get(key[0]):
+                 uom_id = product_uom[key[0]]
+                 qty_uom = uom_obj._compute_qty(cr, uid, product.uom_id.id, qty, uom_id)
              vals.append({
                  'picking_id': picking.id,
-                 'product_qty': qty,
+                 'product_qty': qty_uom,
                  'product_id': key[0],
                  'package_id': key[1],
                  'lot_id': key[2],
                  'owner_id': key[3],
                  'location_id': key[4],
                  'location_dest_id': key[5],
-                 'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id,
+                 'product_uom_id': uom_id,
              })
          return vals
  
                  move_quants = move.reserved_quant_ids
                  picking_quants += move_quants
                  forced_qty = (move.state == 'assigned') and move.product_qty - sum([x.qty for x in move_quants]) or 0
-                 #if we used force_assign() on the move, or if the move is incomming, forced_qty > 0
-                 if forced_qty:
+                 #if we used force_assign() on the move, or if the move is incoming, forced_qty > 0
+                 if float_compare(forced_qty, 0, precision_rounding=move.product_id.uom_id.rounding) > 0:
                      if forced_qties.get(move.product_id):
                          forced_qties[move.product_id] += forced_qty
                      else:
              '''method that creates the link between a given operation and move(s) of given product, for the given quantity.
              Returns True if it was possible to create links for the requested quantity (False if there was not enough quantity on stock moves)'''
              qty_to_assign = qty
+             prod_obj = self.pool.get("product.product")
+             product = prod_obj.browse(cr, uid, product_id)
+             rounding = product.uom_id.rounding
+             qtyassign_cmp = float_compare(qty_to_assign, 0.0, precision_rounding=rounding)
              if prod2move_ids.get(product_id):
-                 while prod2move_ids[product_id] and qty_to_assign > 0:
+                 while prod2move_ids[product_id] and qtyassign_cmp > 0:
                      qty_on_link = _create_link_for_index(operation_id, 0, product_id, qty_to_assign, quant_id=False)
                      qty_to_assign -= qty_on_link
-             return qty_to_assign == 0
+                     qtyassign_cmp = float_compare(qty_to_assign, 0.0, precision_rounding=rounding)
+             return qtyassign_cmp == 0
  
          uom_obj = self.pool.get('product.uom')
          package_obj = self.pool.get('stock.quant.package')
          quant_obj = self.pool.get('stock.quant')
+         link_obj = self.pool.get('stock.move.operation.link')
          quants_in_package_done = set()
          prod2move_ids = {}
          still_to_do = []
          operations = picking.pack_operation_ids
          operations = sorted(operations, key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
          #delete existing operations to start again from scratch
-         cr.execute("DELETE FROM stock_move_operation_link WHERE operation_id in %s", (tuple([x.id for x in operations]),))
+         links = link_obj.search(cr, uid, [('operation_id', 'in', [x.id for x in operations])], context=context)
+         if links:
+             link_obj.unlink(cr, uid, links, context=context)
          #1) first, try to create links when quants can be identified without any doubt
          for ops in operations:
              #for each operation, create the links with the stock move by seeking on the matching reserved quants,
                              max_qty_on_link = min(quant.qty, qty_to_assign)
                              qty_on_link = _create_link_for_quant(ops.id, quant, max_qty_on_link)
                              qty_to_assign -= qty_on_link
-                 if qty_to_assign > 0:
+                 qty_assign_cmp = float_compare(qty_to_assign, 0, precision_rounding=ops.product_id.uom_id.rounding)
+                 if qty_assign_cmp > 0:
                      #qty reserved is less than qty put in operations. We need to create a link but it's deferred after we processed
                      #all the quants (because they leave no choice on their related move and needs to be processed with higher priority)
                      still_to_do += [(ops, ops.product_id.id, qty_to_assign)]
          """
          Creates an extra move when there is no corresponding original move to be copied
          """
+         uom_obj = self.pool.get("product.uom")
+         uom_id = product.uom_id.id
+         qty = remaining_qty
+         if op.product_id and op.product_uom_id and op.product_uom_id.id != product.uom_id.id:
+             if op.product_uom_id.factor > product.uom_id.factor: #If the pack operation's is a smaller unit
+                 uom_id = op.product_uom_id.id
+                 #HALF-UP rounding as only rounding errors will be because of propagation of error from default UoM
+                 qty = uom_obj._compute_qty_obj(cr, uid, product.uom_id, remaining_qty, op.product_uom_id, rounding_method='HALF-UP')
          picking = op.picking_id
          res = {
              'picking_id': picking.id,
              'location_id': picking.location_id.id,
              'location_dest_id': picking.location_dest_id.id,
              'product_id': product.id,
-             'product_uom': product.uom_id.id,
-             'product_uom_qty': remaining_qty,
+             'product_uom': uom_id,
+             'product_uom_qty': qty,
              'name': _('Extra Move: ') + product.name,
              'state': 'draft',
              }
          moves = []
          for op in picking.pack_operation_ids:
              for product_id, remaining_qty in operation_obj._get_remaining_prod_quantities(cr, uid, op, context=context).items():
-                 if remaining_qty > 0:
-                     product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+                 product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
+                 if float_compare(remaining_qty, 0, precision_rounding=product.uom_id.rounding) > 0:
                      vals = self._prepare_values_extra_move(cr, uid, op, product, remaining_qty, context=context)
                      moves.append(move_obj.create(cr, uid, vals, context=context))
          if moves:
                  if not all_op_processed:
                      todo_move_ids += self._create_extra_moves(cr, uid, picking, context=context)
  
-                 picking.refresh()
-                 #split move lines eventually
+                 #split move lines if needed
                  toassign_move_ids = []
                  for move in picking.move_lines:
                      remaining_qty = move.remaining_qty
                          continue
                      elif move.state == 'draft':
                          toassign_move_ids.append(move.id)
-                     if remaining_qty == 0:
+                     if float_compare(remaining_qty, 0,  precision_rounding = move.product_id.uom_id.rounding) == 0:
                          if move.state in ('draft', 'assigned', 'confirmed'):
                              todo_move_ids.append(move.id)
-                     elif remaining_qty > 0 and remaining_qty < move.product_qty:
+                     elif float_compare(remaining_qty,0, precision_rounding = move.product_id.uom_id.rounding) > 0 and \
+                                 float_compare(remaining_qty, move.product_qty, precision_rounding = move.product_id.uom_id.rounding) < 0:
                          new_move = stock_move_obj.split(cr, uid, move, remaining_qty, context=context)
                          todo_move_ids.append(move.id)
                          #Assign move as it was assigned before
                      self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context)
                  elif context.get('do_only_split'):
                      context = dict(context, split=todo_move_ids)
-             picking.refresh()
              self._create_backorder(cr, uid, picking, context=context)
              if toassign_move_ids:
                  stock_move_obj.action_assign(cr, uid, toassign_move_ids, context=context)
                  stock_operation_obj.write(cr, uid, pack_operation_ids, {'result_package_id': package_id}, context=context)
          return True
  
 -    def process_product_id_from_ui(self, cr, uid, picking_id, product_id, op_id, increment=True, context=None):
 +    def process_product_id_from_ui(self, cr, uid, picking_id, product_id, op_id, increment=1, context=None):
          return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id),('id', '=', op_id)], increment=increment, context=context)
  
      def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, visible_op_ids, context=None):
          '''This function is called each time there barcode scanner reads an input'''
 -        lot_obj = self.pool.get('stock.production.lot')
 -        package_obj = self.pool.get('stock.quant.package')
 -        product_obj = self.pool.get('product.product')
          stock_operation_obj = self.pool.get('stock.pack.operation')
 -        stock_location_obj = self.pool.get('stock.location')
          answer = {'filter_loc': False, 'operation_id': False}
 -        #check if the barcode correspond to a location
 -        matching_location_ids = stock_location_obj.search(cr, uid, [('loc_barcode', '=', barcode_str)], context=context)
 -        if matching_location_ids:
 -            #if we have a location, return immediatly with the location name
 -            location = stock_location_obj.browse(cr, uid, matching_location_ids[0], context=None)
 -            answer['filter_loc'] = stock_location_obj._name_get(cr, uid, location, context=None)
 -            answer['filter_loc_id'] = matching_location_ids[0]
 -            return answer
 -        #check if the barcode correspond to a product
 -        matching_product_ids = product_obj.search(cr, uid, ['|', ('ean13', '=', barcode_str), ('default_code', '=', barcode_str)], context=context)
 -        if matching_product_ids:
 -            op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
 -            answer['operation_id'] = op_id
 -            return answer
 +
 +        # Barcode Nomenclatures
 +        picking_type_id = self.browse(cr, uid, [picking_id], context=context).picking_type_id.id
 +        barcode_nom = self.pool.get('stock.picking.type').browse(cr, uid, [picking_type_id], context=context).barcode_nomenclature_id
 +        parsed_result = barcode_nom.parse_barcode(barcode_str)
 +        
 +        #check if the barcode is a weighted barcode or simply a product
 +        if parsed_result['type'] in ['weight', 'product', 'package']:
 +            weight=1
 +            if parsed_result['type'] == 'weight':
 +                domain = ['|', ('barcode', '=', parsed_result['base_code']), ('default_code', '=', parsed_result['base_code'])]
 +                weight=parsed_result['value']
 +                obj = self.pool.get('product.product')
 +                id_in_operation = 'product_id'
 +            elif parsed_result['type'] == 'product':
 +                domain = ['|', ('barcode', '=', parsed_result['code']), ('default_code', '=', parsed_result['code'])]
 +                obj = self.pool.get('product.product')
 +                id_in_operation = 'product_id'
 +            else:
 +                domain = [('name', '=', parsed_result['code'])]
 +                obj = self.pool.get('stock.quant.package')
 +                id_in_operation = 'package_id'
 +
 +            matching_product_ids = obj.search(cr, uid, domain, context=context)
 +            if matching_product_ids:
 +                op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [(id_in_operation, '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=weight, context=context)
 +                answer['operation_id'] = op_id
 +                return answer
 +                  
          #check if the barcode correspond to a lot
 -        matching_lot_ids = lot_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
 -        if matching_lot_ids:
 -            lot = lot_obj.browse(cr, uid, matching_lot_ids[0], context=context)
 -            op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
 -            answer['operation_id'] = op_id
 -            return answer
 -        #check if the barcode correspond to a package
 -        matching_package_ids = package_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
 -        if matching_package_ids:
 -            op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('package_id', '=', matching_package_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
 -            answer['operation_id'] = op_id
 -            return answer
 +        elif parsed_result['type'] == 'lot':
 +            lot_obj = self.pool.get('stock.production.lot')
 +            matching_lot_ids = lot_obj.search(cr, uid, [('name', '=', parsed_result['code'])], context=context)
 +            if matching_lot_ids:
 +                lot = lot_obj.browse(cr, uid, matching_lot_ids[0], context=context)
 +                op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], filter_visible=True, visible_op_ids=visible_op_ids, increment=1, context=context)
 +                answer['operation_id'] = op_id
 +                return answer
 +            
 +        #check if the barcode correspond to a location
 +        elif parsed_result['type'] == 'location':
 +            stock_location_obj = self.pool.get('stock.location')
 +            matching_location_ids = stock_location_obj.search(cr, uid, [('barcode', '=', parsed_result['code'])], context=context)
 +            if matching_location_ids:
 +                #if we have a location, return immediatly with the location name
 +                location = stock_location_obj.browse(cr, uid, matching_location_ids[0], context=None)
 +                answer['filter_loc'] = stock_location_obj._name_get(cr, uid, location, context=None)
 +                answer['filter_loc_id'] = matching_location_ids[0]
 +                return answer
 +            
          return answer
  
  
@@@ -1570,7 -1587,7 +1608,7 @@@ class stock_move(osv.osv)
          uom_obj = self.pool.get('product.uom')
          res = {}
          for m in self.browse(cr, uid, ids, context=context):
-             res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, round=False, context=context)
+             res[m.id] = uom_obj._compute_qty_obj(cr, uid, m.product_uom, m.product_uom_qty, m.product_id.uom_id, context=context)
          return res
  
      def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None):
              qty = move.product_qty
              for record in move.linked_move_operation_ids:
                  qty -= record.qty
-             #converting the remaining quantity in the move UoM
-             res[move.id] = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom, round=False, context=context)
+             # Keeping in product default UoM
+             res[move.id] = float_round(qty, precision_rounding=move.product_id.uom_id.rounding)
          return res
  
      def _get_lot_ids(self, cr, uid, ids, field_name, args, context=None):
          'date': fields.datetime('Date', required=True, select=True, help="Move date: scheduled date until move is done, then date of actual move processing", states={'done': [('readonly', True)]}),
          'date_expected': fields.datetime('Expected Date', states={'done': [('readonly', True)]}, required=True, select=True, help="Scheduled date for the processing of this move"),
          'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type', '<>', 'service')], states={'done': [('readonly', True)]}),
-         'product_qty': fields.function(_quantity_normalize, fnct_inv=_set_product_qty, _type='float', store={
+         'product_qty': fields.function(_quantity_normalize, fnct_inv=_set_product_qty, type='float', digits=0, store={
                  'stock.move': (lambda self, cr, uid, ids, ctx: ids, ['product_id', 'product_uom_qty', 'product_uom'], 20),
                  'product.product': (_get_moves_from_prod, ['uom_id'], 20),
              }, string='Quantity',
-             digits_compute=dp.get_precision('Product Unit of Measure'),
              help='Quantity in the default UoM of the product'),
          'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
              required=True, states={'done': [('readonly', True)]},
          'move_dest_id': fields.many2one('stock.move', 'Destination Move', help="Optional: next stock move when chaining them", select=True, copy=False),
          'move_orig_ids': fields.one2many('stock.move', 'move_dest_id', 'Original Move', help="Optional: previous stock move when chaining them", select=True),
  
-         'picking_id': fields.many2one('stock.picking', 'Reference Stock Move', select=True, states={'done': [('readonly', True)]}),
+         'picking_id': fields.many2one('stock.picking', 'Transfer Reference', select=True, states={'done': [('readonly', True)]}),
          'note': fields.text('Notes'),
          'state': fields.selection([('draft', 'New'),
                                     ('cancel', 'Cancelled'),
          'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants'),
          'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
          'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'move_id', string='Linked Operations', readonly=True, help='Operations that impact this move for the computation of the remaining quantities'),
-         'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
-                                          digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},),
+         'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity', digits=0,
+                                          states={'done': [('readonly', True)]}, help="Remaining Quantity in default UoM according to operations matched with this move"),
          'procurement_id': fields.many2one('procurement.order', 'Procurement'),
          'group_id': fields.many2one('procurement.group', 'Procurement Group'),
          'rule_id': fields.many2one('procurement.rule', 'Procurement Rule', help='The pull rule that created this stock move'),
              'company_id': move.company_id and move.company_id.id or False,
              'date_planned': move.date,
              'product_id': move.product_id.id,
-             'product_qty': move.product_qty,
+             'product_qty': move.product_uom_qty,
              'product_uom': move.product_uom.id,
-             'product_uos_qty': (move.product_uos and move.product_uos_qty) or move.product_qty,
+             'product_uos_qty': (move.product_uos and move.product_uos_qty) or move.product_uom_qty,
              'product_uos': (move.product_uos and move.product_uos.id) or move.product_uom.id,
              'location_id': move.location_id.id,
              'move_dest_id': move.id,
          for move in todo_moves:
              if move.linked_move_operation_ids:
                  continue
-             move.refresh()
              #then if the move isn't totally assigned, try to find quants without any specific domain
              if move.state != 'assigned':
                  qty_already_assigned = move.reserved_availability
                  # Handle pack in pack
                  if not ops.product_id and ops.package_id and ops.result_package_id.id != ops.package_id.parent_id.id:
                      self.pool.get('stock.quant.package').write(cr, SUPERUSER_ID, [ops.package_id.id], {'parent_id': ops.result_package_id.id}, context=context)
+                 if not move_qty.get(move.id):
+                     raise osv.except_osv(_("Error"), _("The roundings of your Unit of Measures %s on the move vs. %s on the product don't allow to do these operations or you are not transferring the picking at once. ") % (move.product_uom.name, move.product_id.uom_id.name))
                  move_qty[move.id] -= record.qty
          #Check for remaining qtys and unreserve/check move_dest_id in
          move_dest_ids = set()
          for move in self.browse(cr, uid, ids, context=context):
-             if move_qty[move.id] > 0:  # (=In case no pack operations in picking)
+             move_qty_cmp = float_compare(move_qty[move.id], 0, precision_rounding=move.product_id.uom_id.rounding)
+             if move_qty_cmp > 0:  # (=In case no pack operations in picking)
                  main_domain = [('qty', '>', 0)]
                  prefered_domain = [('reservation_id', '=', move.id)]
                  fallback_domain = [('reservation_id', '=', False)]
          uom_obj = self.pool.get('product.uom')
          context = context or {}
  
-         uom_qty = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom)
+         #HALF-UP rounding as only rounding errors will be because of propagation of error from default UoM
+         uom_qty = uom_obj._compute_qty_obj(cr, uid, move.product_id.uom_id, qty, move.product_uom, rounding_method='HALF-UP', context=context)
          uos_qty = uom_qty * move.product_uos_qty / move.product_uom_qty
  
          defaults = {
@@@ -2592,7 -2611,6 +2632,6 @@@ class stock_inventory(osv.osv)
                  if inventory_line.product_qty < 0 and inventory_line.product_qty != inventory_line.theoretical_qty:
                      raise osv.except_osv(_('Warning'), _('You cannot set a negative product quantity in an inventory line:\n\t%s - qty: %s' % (inventory_line.product_id.name, inventory_line.product_qty)))
              self.action_check(cr, uid, [inv.id], context=context)
-             inv.refresh()
              self.write(cr, uid, [inv.id], {'state': 'done'}, context=context)
              self.post_inventory(cr, uid, inv, context=context)
          return True
@@@ -3346,7 -3364,6 +3385,6 @@@ class stock_warehouse(osv.osv)
          new_id = super(stock_warehouse, self).create(cr, uid, vals=vals, context=context)
          warehouse = self.browse(cr, uid, new_id, context=context)
          self.create_sequences_and_picking_types(cr, uid, warehouse, context=context)
-         warehouse.refresh()
  
          #create routes and push/pull rules
          new_objects_dict = self.create_routes(cr, uid, new_id, warehouse, context=context)
                  self.change_route(cr, uid, ids, warehouse, vals.get('reception_steps', False), vals.get('delivery_steps', False), context=context_with_inactive)
                  # Check if we need to change something to resupply warehouses and associated MTO rules
                  self._check_resupply(cr, uid, warehouse, vals.get('reception_steps'), vals.get('delivery_steps'), context=context)
-                 warehouse.refresh()
              if vals.get('code') or vals.get('name'):
                  name = warehouse.name
                  #rename sequence
@@@ -3608,7 -3624,6 +3645,6 @@@ class stock_location_path(osv.osv)
                  'date_expected': newdate,
                  'location_dest_id': rule.location_dest_id.id
              })
-             move.refresh()
              #avoid looping if a push rule is not well configured
              if rule.location_dest_id.id != old_dest_location:
                  #call again push_apply to see if a next step is defined
@@@ -3824,10 -3839,7 +3860,7 @@@ class stock_pack_operation(osv.osv)
                      qty = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context)
                  for record in ops.linked_move_operation_ids:
                      qty -= record.qty
-                 #converting the remaining quantity in the pack operation UoM
-                 if ops.product_uom_id:
-                     qty = uom_obj._compute_qty_obj(cr, uid, ops.product_id.uom_id, qty, ops.product_uom_id, context=context)
-                 res[ops.id] = qty
+                 res[ops.id] = float_round(qty, precision_rounding=ops.product_id.uom_id.rounding)
          return res
  
      def product_id_change(self, cr, uid, ids, product_id, product_uom_id, product_qty, context=None):
          'cost': fields.float("Cost", help="Unit Cost for this product line"),
          'currency': fields.many2one('res.currency', string="Currency", help="Currency in which Unit cost is expressed", ondelete='CASCADE'),
          'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'operation_id', string='Linked Moves', readonly=True, help='Moves impacted by this operation for the computation of the remaining quantities'),
-         'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Qty'),
+         'remaining_qty': fields.function(_get_remaining_qty, type='float', digits = 0, string="Remaining Qty", help="Remaining quantity in default UoM according to moves matched with this operation. "),
          'location_id': fields.many2one('stock.location', 'Source Location', required=True),
          'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True),
          'processed': fields.selection([('true','Yes'), ('false','No')],'Has been processed?', required=True),
              new_lot_id = self.pool.get('stock.production.lot').create(cr, uid, val, context=context)
          self.write(cr, uid, id, {'lot_id': new_lot_id}, context=context)
  
 -    def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, increment=True, context=None):
 -        '''Search for an operation with given 'domain' in a picking, if it exists increment the qty (+1) otherwise create it
 +    def _search_and_increment(self, cr, uid, picking_id, domain, filter_visible=False, visible_op_ids=False, increment=1, context=None):
 +        '''Search for an operation with given 'domain' in a picking, if it exists increment the qty by the value of increment otherwise create it
  
          :param domain: list of tuple directly reusable as a domain
          context can receive a key 'current_package_id' with the package to consider for this operation
              operation_id = todo_operation_ids[0]
              op_obj = self.browse(cr, uid, operation_id, context=context)
              qty = op_obj.qty_done
 -            if increment:
 -                qty += 1
 -            else:
 -                qty -= 1 if qty >= 1 else 0
 +            if increment > 0:
 +                qty += increment 
 +            elif increment < 0:
                  if qty == 0 and op_obj.product_qty == 0:
                      #we have a line with 0 qty set, so delete it
                      self.unlink(cr, uid, [operation_id], context=context)
                      return False
 +                else:
 +                    qty = max(0, qty-1)
              self.write(cr, uid, [operation_id], {'qty_done': qty}, context=context)
          else:
              #no existing operation found for the given domain and picking => create a new one
                  'product_qty': 0,
                  'location_id': picking.location_id.id, 
                  'location_dest_id': picking.location_dest_id.id,
 -                'qty_done': 1,
 +                'qty_done': increment,
                  }
              for key in domain:
                  var_name, dummy, value = key
@@@ -4289,32 -4300,10 +4322,32 @@@ class stock_picking_type(osv.osv)
          'rate_picking_backorders': fields.function(_get_picking_count,
              type='integer', multi='_get_picking_count'),
  
 +        # Barcode nomenclature
 +        'barcode_nomenclature_id':  fields.many2one('barcode.nomenclature','Barcode Nomenclature', help='A barcode nomenclature', required=True),
      }
 +
 +    def _get_default_nomenclature(self, cr, uid, context=None):
 +        nom_obj = self.pool.get('barcode.nomenclature')
 +        res = nom_obj.search(cr, uid, [], limit=1, context=context)
 +        return res and res[0] or False
 +
      _defaults = {
          'warehouse_id': _default_warehouse,
          'active': True,
 +        'barcode_nomenclature_id': _get_default_nomenclature,
      }
  
 +class barcode_rule(models.Model):
 +    _inherit = 'barcode.rule'
 +
 +    def _get_type_selection(self):
 +        types = sets.Set(super(barcode_rule,self)._get_type_selection()) 
 +        types.update([
 +            ('weight','Weighted Product'),
 +            ('location','Location'),
 +            ('lot','Lot'),
 +            ('package','Package')
 +        ])
 +        return list(types)
 +
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
@@@ -40,8 -40,8 +40,8 @@@
              parent="menu_product_in_config_stock" sequence="5" groups="product.group_uom"/>
          <menuitem
              action="product.product_uom_form_action" id="menu_stock_uom_form_action"
 -            parent="menu_product_in_config_stock" sequence="4" groups="product.group_uom"/>
 -
 +            parent="menu_stock_configuration" sequence="35" groups="product.group_uom"/>
 +        
          <record id="stock_inventory_line_tree" model="ir.ui.view">
              <field name="name">stock.inventory.line.tree</field>
              <field name="model">stock.inventory.line</field>
                      <h2><field name="location_id"/></h2>
  
                      <group>
-                         <div>
-                             <group string="Additional Information">
-                                 <field name="usage"/>
-                                 <field name="partner_id"/>
-                                 <field name="company_id" groups="base.group_multi_company" widget="selection"/>
-                                 <field name="scrap_location"/>
-                                 <field name="active"/>
-                             </group>
-                         </div>
-                         <div>
-                             <group string="Localization" name="localization">
-                                 <field name="posx"/>
-                                 <field name="posy"/>
-                                 <field name="posz"/>
-                                 <field name="barcode"/>
-                             </group>
-                         </div>
-                         <div>
-                             <group string="Logistics" groups="stock.group_adv_location">
-                                 <field name="removal_strategy_id" options="{'no_create': True}"/>
-                                 <field name="putaway_strategy_id"/>
-                             </group>
-                         </div>
+                         <group string="Additional Information">
+                             <field name="usage"/>
+                             <field name="partner_id"/>
+                             <field name="company_id" groups="base.group_multi_company" widget="selection"/>
+                             <field name="scrap_location"/>
+                             <field name="active"/>
+                         </group>
+                         <group string="Localization" name="localization">
+                             <field name="posx"/>
+                             <field name="posy"/>
+                             <field name="posz"/>
 -                            <field name="loc_barcode"/>
++                            <field name="barcode"/>
+                         </group>
+                         <group string="Logistics" groups="stock.group_adv_location">
+                             <field name="removal_strategy_id" options="{'no_create': True}"/>
+                             <field name="putaway_strategy_id"/>
+                         </group>
                      </group>
                      <field name="comment" placeholder="External note..."/>
                  </form>
                  <tree string="Stock Location" colors="blue:usage=='view';darkred:usage=='internal'">
                      <field name="complete_name"/>
                      <field name="usage"/>
+                     <field name="company_id" groups="base.group_multi_company"/>
                  </tree>
              </field>
          </record>
                              <field name="move_dest_id" groups="base.group_no_one" readonly="1"/>
                          </group>
                          <group name="quants_grp" string="Reserved Quants" colspan="4" groups="base.group_no_one">
-                             <div>
-                                 <field name="reserved_quant_ids"/>
-                             </div>
+                             <field name="reserved_quant_ids" nolabel="1"/>
                          </group>
                      </group>
                  </sheet>
                          <t t-name="kanban-box">
                              <div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_stock_picking_type">
                                  <div class="oe_dropdown_toggle oe_dropdown_kanban" groups="stock.group_stock_manager">
-                                     <span class="oe_e">í</span>
+                                     <span class="oe_e">i</span>
                                      <ul class="oe_dropdown_menu">
-                                         <t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit...</a></li></t>
+                                         <t t-if="widget.view.is_action_enabled('edit')"><li><a type="edit">Edit Warehouse</a></li></t>
                                          <t t-if="widget.view.is_action_enabled('delete')"><li><a type="delete">Delete</a></li></t>
                                          <t t-if="widget.view.is_action_enabled('edit')"><li><ul class="oe_kanban_colorpicker" data-field="color"/></li></t>
                                      </ul>
                  </xpath>
              </field>
          </record>
 -        
 +
 +        <!-- Barcode Nomenclatures -->
 +        <menuitem parent="menu_stock_configuration" action="barcodes.action_barcode_nomenclature_form" id="menu_wms_barcode_nomenclature_all"
 +            sequence="50"/>
 +       
      </data>
  </openerp>
@@@ -8,7 -8,6 +8,7 @@@
    font-weight: normal;
    font-style: normal;
  }
 +
  @font-face {
    font-family: "EntypoRegular";
    src: url("/web/static/src/font/entypo-webfont.eot") format("eot");
    -moz-user-select: none;
    user-select: none;
  }
+ .openerp .oe-view-manager-header .oe-button-column {
+   height: 30px;
+ }
  .openerp .oe-view-manager-header .dropdown-menu li {
    position: relative;
  }
    border-right: none;
  }
  .openerp .oe_form .oe_subtotal_footer .oe_subtotal_footer_separator {
-   width: 108px;
+   min-width: 108px;
    border-top: 1px solid #cacaca;
    margin-top: 4px;
    padding-top: 4px;
  .openerp .oe_form .oe_form_field_image:hover .oe_form_field_image_controls {
    display: block;
  }
 +.openerp .oe_form .oe_form_field_image img {
 +  max-width: 256px;
 +  max-height: 256px;
 +}
  .openerp .oe_fileupload {
    display: inline-block;
    clear: both;
      top: 0px;
    }
  }
 +
  .kitten-mode-activated {
    background-size: cover;
    background-attachment: fixed;
@@@ -908,6 -908,8 +908,8 @@@ $sheet-padding: 16p
          -webkit-user-select: none
          -moz-user-select: none
          user-select: none
+         .oe-button-column
+             height: 30px
          .dropdown-menu
              li
                  position: relative
              td.oe_form_group_cell_label
                  border-right: none
              .oe_subtotal_footer_separator
-                 width: 108px
+                 min-width: 108px
                  border-top: 1px solid #cacaca
                  margin-top: 4px
                  padding-top: 4px
                  @include box-sizing(border)
              &:hover .oe_form_field_image_controls
                  display: block
 +            img
 +                max-width: 256px
 +                max-height: 256px
      .oe_fileupload
          display: inline-block
          clear: both
@@@ -250,7 -250,7 +250,7 @@@ class res_partner(osv.Model, format_add
          'comment': fields.text('Notes'),
          'category_id': fields.many2many('res.partner.category', id1='partner_id', id2='category_id', string='Tags'),
          'credit_limit': fields.float(string='Credit Limit'),
 -        'ean13': fields.char('EAN13', size=13),
 +        'barcode': fields.char('Barcode', oldname='ean13'),
          'active': fields.boolean('Active'),
          'customer': fields.boolean('Is a Customer', help="Check this box if this contact is a customer."),
          'supplier': fields.boolean('Is a Supplier', help="Check this box if this contact is a supplier. If it's not checked, purchase people will not see it when encoding a purchase order."),
              return {'value': {'country_id': state.country_id.id}}
          return {}
  
 -    def _check_ean_key(self, cr, uid, ids, context=None):
 -        for partner_o in self.pool['res.partner'].read(cr, uid, ids, ['ean13',]):
 -            thisean=partner_o['ean13']
 -            if thisean and thisean!='':
 -                if len(thisean)!=13:
 -                    return False
 -                sum=0
 -                for i in range(12):
 -                    if not (i % 2):
 -                        sum+=int(thisean[i])
 -                    else:
 -                        sum+=3*int(thisean[i])
 -                if math.ceil(sum/10.0)*10-sum!=int(thisean[12]):
 -                    return False
 -        return True
 -
 -#   _constraints = [(_check_ean_key, 'Error: Invalid ean code', ['ean13'])]
 -
      def _update_fields_values(self, cr, uid, partner, fields, context=None):
          """ Returns dict of write() values for synchronizing ``fields`` """
          values = {}
              if not parent.is_company:
                  parent.write({'is_company': True})
  
+     def unlink(self, cr, uid, ids, context=None):
+         orphan_contact_ids = self.search(cr, uid,
+             [('parent_id', 'in', ids), ('id', 'not in', ids), ('use_parent_address', '=', True)], context=context)
+         if orphan_contact_ids:
+             # no longer have a parent address
+             self.write(cr, uid, orphan_contact_ids, {'use_parent_address': False}, context=context)
+         return super(res_partner, self).unlink(cr, uid, ids, context=context)
      def _clean_website(self, website):
          (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(website)
          if not scheme: