[MERGE] forward port of branch 8.0 up to 2e092ac
authorChristophe Simonis <chs@odoo.com>
Wed, 3 Dec 2014 14:18:36 +0000 (15:18 +0100)
committerChristophe Simonis <chs@odoo.com>
Wed, 3 Dec 2014 14:18:36 +0000 (15:18 +0100)
1  2 
addons/account_voucher/account_voucher.py
addons/procurement/procurement.py
addons/product/product.py
addons/product/product_view.xml
addons/stock/product.py
addons/stock/stock.py
openerp/addons/base/ir/ir_actions.py
openerp/models.py
openerp/service/server.py

@@@ -344,8 -344,8 +344,8 @@@ class account_voucher(osv.osv)
          'pre_line':fields.boolean('Previous Payments ?', required=False),
          'date_due': fields.date('Due Date', readonly=True, select=True, states={'draft':[('readonly',False)]}),
          'payment_option':fields.selection([
 -                                           ('without_writeoff', 'Keep Open'),
 -                                           ('with_writeoff', 'Reconcile Payment Balance'),
 +                                           ('without_writeoff', 'Keep it open'),
 +                                           ('with_writeoff', 'Reconcile payment balance'),
                                             ], 'Payment Difference', required=True, readonly=True, states={'draft': [('readonly', False)]}, help="This field helps you to choose what you want to do with the eventual difference between the paid amount and the sum of allocated amounts. You can either choose to keep open this difference on the partner's account, or reconcile it with the payment(s)"),
          'writeoff_acc_id': fields.many2one('account.account', 'Counterpart Account', readonly=True, states={'draft': [('readonly', False)]}),
          'comment': fields.char('Counterpart Comment', required=True, readonly=True, states={'draft': [('readonly', False)]}),
          else:
              currency_id = journal.company_id.currency_id.id
  
-         period_id = self.pool['account.period'].find(cr, uid, context=dict(context, company_id=company_id))
+         period_ids = self.pool['account.period'].find(cr, uid, context=dict(context, company_id=company_id))
          vals['value'].update({
              'currency_id': currency_id,
              'payment_rate_currency_id': currency_id,
-             'period_id' : period_id
+             'period_id': period_ids and period_ids[0] or False
          })
          #in case we want to register the payment directly from an invoice, it's confusing to allow to switch the journal 
          #without seeing that the amount is expressed in the journal currency, and not in the invoice currency. So to avoid
@@@ -64,7 -64,7 +64,7 @@@ class procurement_group(osv.osv)
          'procurement_ids': fields.one2many('procurement.order', 'group_id', 'Procurements'),
      }
      _defaults = {
 -        'name': lambda self, cr, uid, c: self.pool.get('ir.sequence').get(cr, uid, 'procurement.group') or '',
 +        'name': lambda self, cr, uid, c: self.pool.get('ir.sequence').next_by_code(cr, uid, 'procurement.group') or '',
          'move_type': lambda self, cr, uid, c: 'direct'
      }
  
@@@ -203,7 -203,6 +203,6 @@@ class procurement_order(osv.osv)
              if procurement.state not in ("running", "done"):
                  try:
                      if self._assign(cr, uid, procurement, context=context):
-                         procurement.refresh()
                          res = self._run(cr, uid, procurement, context=context or {})
                          if res:
                              self.write(cr, uid, [procurement.id], {'state': 'running'}, context=context)
@@@ -142,7 -142,7 +142,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."),
          '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:
          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):
@@@ -517,7 -518,8 +518,7 @@@ class product_template(osv.osv)
          'warranty': fields.float('Warranty'),
          'sale_ok': fields.boolean('Can be Sold', help="Specify if the product can be selected in a sales order line."),
          'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
 -        'state': fields.selection([('',''),
 -            ('draft', 'In Development'),
 +        'state': fields.selection([('draft', 'In Development'),
              ('sellable','Normal'),
              ('end','End of Lifecycle'),
              ('obsolete','Obsolete')], 'Status'),
          ''' Store the standard price change in order to be able to retrieve the cost of a product template for a given date'''
          if isinstance(ids, (int, long)):
              ids = [ids]
 -        if 'uom_po_id' in vals:
 -            new_uom = self.pool.get('product.uom').browse(cr, uid, vals['uom_po_id'], context=context)
 -            for product in self.browse(cr, uid, ids, context=context):
 -                old_uom = product.uom_po_id
 -                if old_uom.category_id.id != new_uom.category_id.id:
 -                    raise osv.except_osv(_('Unit of Measure categories Mismatch!'), _("New Unit of Measure '%s' must belong to same Unit of Measure category '%s' as of old Unit of Measure '%s'. If you need to change the unit of measure, you may deactivate this product from the 'Procurements' tab and create a new one.") % (new_uom.name, old_uom.category_id.name, old_uom.name,))
          if 'standard_price' in vals:
              for prod_template_id in ids:
                  self._set_standard_price(cr, uid, prod_template_id, vals['standard_price'], context=context)
@@@ -1051,8 -1059,6 +1052,8 @@@ class product_product(osv.osv)
          return result
  
      def name_search(self, cr, user, name='', args=None, operator='ilike', context=None, limit=100):
 +        if context is None:
 +            context = {}
          if not args:
              args = []
          if name:
                  res = ptrn.search(name)
                  if res:
                      ids = self.search(cr, user, [('default_code','=', res.group(2))] + args, limit=limit, context=context)
 +            # still no results, partner in context: search on supplier info as last hope to find something
 +            if not ids and context.get('partner_id'):
 +                supplier_ids = self.pool['product.supplierinfo'].search(
 +                    cr, user, [
 +                        ('name', '=', context.get('partner_id')),
 +                        '|',
 +                        ('product_code', operator, name),
 +                        ('product_name', operator, name)
 +                    ], context=context)
 +                if supplier_ids:
 +                    ids = self.search(cr, user, [('product_tmpl_id.seller_ids', 'in', supplier_ids)], limit=limit, context=context)
          else:
              ids = self.search(cr, user, args, limit=limit, context=context)
          result = self.name_get(cr, user, ids, context=context)
@@@ -1256,7 -1251,7 +1257,7 @@@ class product_supplierinfo(osv.osv)
          'product_tmpl_id' : fields.many2one('product.template', 'Product Template', required=True, ondelete='cascade', select=True, oldname='product_id'),
          'delay' : fields.integer('Delivery Lead Time', required=True, help="Lead time in days between the confirmation of the purchase order and the receipt of the products in your warehouse. Used by the scheduler for automatic computation of the purchase order planning."),
          'pricelist_ids': fields.one2many('pricelist.partnerinfo', 'suppinfo_id', 'Supplier Pricelist', copy=True),
 -        'company_id':fields.many2one('res.company','Company',select=1),
 +        'company_id':fields.many2one('res.company', string='Company',select=1),
      }
      _defaults = {
          'min_qty': 0.0,
@@@ -10,8 -10,8 +10,8 @@@
              <field name="model">product.template</field>
              <field name="arch" type="xml">
                  <search string="Product">
 -                    <field name="name" string="Product"/>
 -                    <filter string="Services" icon="terp-accessories-archiver" domain="[('type','=','service')]"/>
 +                    <field name="name" string="Product" filter_domain="['|',('default_code','ilike',self),('name','ilike',self)]"/>
 +                    <filter string="Services" name="services" domain="[('type','=','service')]"/>
                      <filter string="Consumable" name="consumable" icon="terp-accessories-archiver" domain="[('type','=','consu')]" help="Consumable products"/>
                      <separator/>
                      <filter string="Can be Sold" name="filter_to_sell" icon="terp-accessories-archiver-minus" domain="[('sale_ok','=',1)]"/>
                              <div class="oe_title" style="width: 390px;">
                                  <label class="oe_edit_only" for="name" string="Product Name"/>
                                  <h1><field name="name" class="oe_inline"/></h1>
 -                            </div>
 -                            <div class="oe_left" name="options" groups="base.group_user">
 -                                <div>
 -                                    <field name="sale_ok"/>
 -                                    <label for="sale_ok"/>
 +                                <div name="options" groups="base.group_user">
 +                                    <div>
 +                                        <field name="sale_ok"/>
 +                                        <label for="sale_ok"/>
 +                                    </div>
                                  </div>
                              </div>
                          </div>
              <field name="mode">primary</field>
              <field name="inherit_id" ref="product.product_template_search_view"/>
              <field name="arch" type="xml">
 -                <field name="name" position="replace">
 -                   <field name="name" string="Product" filter_domain="['|',('default_code','ilike',self),('name','ilike',self)]"/>
 -                </field>
                  <field name="product_variant_ids" position="replace">
                      <field name="attribute_value_ids"/>
                  </field>
                <p class="oe_view_nocontent_create">
                  Click to define a new product.
                </p><p>
 -                You must define a product for everything you buy or sell,
 -                whether it's a physical product, a consumable or service.
 +                You must define a product for everything you sell, whether it's
 +                a physical product, a consumable or a service you offer to
 +                customers.
 +              </p><p>
 +                The product form contains information to simplify the sale
 +                process: price, notes in the quotation, accounting data,
 +                procurement methods, etc.
                </p>
              </field>
          </record>
              <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.
                </p><p>
 -                You must define a product for everything you buy or sell,
 -                whether it's a physical product, a consumable or service.
 +                You must define a product for everything you sell, whether it's
 +                a physical product, a consumable or a service you offer to
 +                customers.
 +              </p><p>
 +                The product form contains information to simplify the sale
 +                process: price, notes in the quotation, accounting data,
 +                procurement methods, etc.
                </p>
              </field>
          </record>
              <field name="view_type">form</field>
              <field name="view_id" ref="product_template_kanban_view"/>
              <field name="context">{"search_default_filter_to_sell":1}</field>
 +            <field name="help" type="html">
 +                <p class="oe_view_nocontent_create">
 +                    Click to define a new product.
 +                </p><p>
 +                    You must define a product for everything you sell, whether it's a physical product, a consumable or a service you offer to  customers.               
 +                </p><p>
 +                    The product form contains information to simplify the sale process: price, notes in the quotation, accounting data, procurement methods, etc.
 +                </p>
 +            </field>
          </record>
  
          <menuitem action="product_template_action"
  
          <!-- product product -->
  
 -        <menuitem id="prod_config_main" name="Product Categories &amp; Attributes" parent="base.menu_base_config" sequence="70" groups="base.group_no_one"/>
 +        <menuitem id="prod_config_main" name="Products" parent="base.menu_base_config" sequence="2" groups="base.group_no_one"/>
  
          <record id="product_product_tree_view" model="ir.ui.view">
              <field name="name">product.product.tree</field>
  
          <menuitem action="attribute_action"
              id="menu_attribute_action"
 -            parent="product.prod_config_main" sequence="9" />
 +            parent="product.prod_config_main" sequence="4" />
  
          <record id="variants_tree_view" model="ir.ui.view">
              <field name="name">product.attribute.value.tree</field>
  
          <menuitem action="variants_action"
              id="menu_variants_action"
 -            parent="product.prod_config_main" sequence="10" />
 +            parent="product.prod_config_main" sequence="5" />
  
          <!--  -->
  
              <field name="model">product.category</field>
              <field name="arch" type="xml">
                  <form string="Product Categories">
 -                    <sheet>
 -                        <div class="oe_title">
 -                            <label for="name" class="oe_edit_only"/>
 -                            <h1>
 -                                <field name="name"/>
 -                            </h1>
 -                        </div>
 -                        <group>
 -                            <group name="parent" col="4">
 -                                <field name="parent_id"/>
 -                                <field name="type"/>
 -                            </group>
 +                    <div class="oe_title">
 +                        <label for="name" class="oe_edit_only"/>
 +                        <h1>
 +                            <field name="name"/>
 +                        </h1>
 +                    </div>
 +                    <group>
 +                        <group name="parent" col="4">
 +                            <field name="parent_id"/>
 +                            <field name="type"/>
                          </group>
 -                    </sheet>
 +                    </group>
                  </form>
              </field>
          </record>
              parent="base.menu_product"
              sequence="30" groups="base.group_no_one"/>
          <record id="product_category_action_form" model="ir.actions.act_window">
 -            <field name="name">Product Categories</field>
 +            <field name="name">Internal Categories</field>
              <field name="type">ir.actions.act_window</field>
              <field name="res_model">product.category</field>
              <field name="view_type">form</field>
                          </group>
                          <group>
                              <field name="active"/>
-                             <field name="rounding"/>
+                             <field name="rounding" digits="[42, 5]"/>
                          </group>
                      </group>
                  </form>
              </field>
          </record>
          <menuitem id="next_id_16" name="Units of Measure" parent="prod_config_main" sequence="30" groups="product.group_uom"/>
 -        <menuitem action="product_uom_form_action" id="menu_product_uom_form_action" parent="base.menu_base_config" sequence="30" groups="product.group_uom"/>
 +        <menuitem action="product_uom_form_action" id="menu_product_uom_form_action" parent="product.prod_config_main" sequence="6" groups="product.group_uom"/>
  
          <record id="product_uom_categ_form_view" model="ir.ui.view">
              <field name="name">product.uom.categ.form</field>
                </p>
              </field>
          </record>
 -        <menuitem action="product_uom_categ_form_action" id="menu_product_uom_categ_form_action" parent="base.menu_base_config" sequence="25" groups="base.group_no_one"/>
 +        <menuitem action="product_uom_categ_form_action" id="menu_product_uom_categ_form_action" parent="product.prod_config_main" sequence="7" groups="base.group_no_one"/>
  
          <record id="product_ul_form_view" model="ir.ui.view">
              <field name="name">product.ul.form.view</field>
              </field>
          </record>
          <menuitem
 -            action="product_ul_form_action" groups="product.group_stock_packaging" id="menu_product_ul_form_action" parent="prod_config_main" sequence="5"/>
 +            action="product_ul_form_action" groups="product.group_stock_packaging" id="menu_product_ul_form_action" parent="prod_config_main" sequence="3"/>
  
          <record id="product_packaging_tree_view" model="ir.ui.view">
              <field name="name">product.packaging.tree.view</field>
diff --combined addons/stock/product.py
@@@ -23,6 -23,7 +23,7 @@@ from openerp.osv import fields, os
  from openerp.tools.translate import _
  from openerp.tools.safe_eval import safe_eval as eval
  import openerp.addons.decimal_precision as dp
+ from openerp.tools.float_utils import float_round
  
  class product_product(osv.osv):
      _inherit = "product.product"
          moves_in = dict(map(lambda x: (x['product_id'][0], x['product_qty']), moves_in))
          moves_out = dict(map(lambda x: (x['product_id'][0], x['product_qty']), moves_out))
          res = {}
-         for id in ids:
+         for product in self.browse(cr, uid, ids, context=context):
+             id = product.id
+             qty_available = float_round(quants.get(id, 0.0), precision_rounding=product.uom_id.rounding)
+             incoming_qty = float_round(moves_in.get(id, 0.0), precision_rounding=product.uom_id.rounding)
+             outgoing_qty = float_round(moves_out.get(id, 0.0), precision_rounding=product.uom_id.rounding)
+             virtual_available = float_round(quants.get(id, 0.0) + moves_in.get(id, 0.0) - moves_out.get(id, 0.0), precision_rounding=product.uom_id.rounding)
              res[id] = {
-                 'qty_available': quants.get(id, 0.0),
-                 'incoming_qty': moves_in.get(id, 0.0),
-                 'outgoing_qty': moves_out.get(id, 0.0),
-                 'virtual_available': quants.get(id, 0.0) + moves_in.get(id, 0.0) - moves_out.get(id, 0.0),
+                 'qty_available': qty_available,
+                 'incoming_qty': incoming_qty,
+                 'outgoing_qty': outgoing_qty,
+                 'virtual_available': virtual_available,
              }
          return res
  
      def _search_product_quantity(self, cr, uid, obj, name, domain, context):
      _columns = {
          'reception_count': fields.function(_stock_move_count, string="Receipt", type='integer', multi='pickings'),
          'delivery_count': fields.function(_stock_move_count, string="Delivery", type='integer', multi='pickings'),
 -        'qty_available_text': fields.function(_product_available_text, type='char'),
          'qty_available': fields.function(_product_available, multi='qty_available',
              type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
              string='Quantity On Hand',
                   "or any of its children.\n"
                   "Otherwise, this includes goods stored in any Stock Location "
                   "with 'internal' type."),
 +        'qty_available2': fields.related('qty_available', type="float", relation="product.product", string="On Hand"),
          'virtual_available': fields.function(_product_available, multi='qty_available',
              type='float', digits_compute=dp.get_precision('Product Unit of Measure'),
              string='Forecast Quantity',
@@@ -334,7 -339,7 +339,7 @@@ class product_template(osv.osv)
  
      _columns = {
          'type': fields.selection([('product', 'Stockable Product'), ('consu', 'Consumable'), ('service', 'Service')], 'Product Type', required=True, help="Consumable: Will not imply stock management for this product. \nStockable product: Will imply stock management for this product."),
 -        'qty_available_text': fields.function(_product_available_text, type='char'),
 +        'qty_available2': fields.related('qty_available', type="float", relation="product.template", string="On Hand"),
          'property_stock_procurement': fields.property(
              type='many2one',
              relation='stock.location',
              result['context'] = "{'tree_view_ref':'stock.view_move_tree'}"
          return result
  
 +    def write(self, cr, uid, ids, vals, context=None):
 +        if 'uom_po_id' in vals:
 +            product_ids = self.pool.get('product.product').search(cr, uid, [('product_tmpl_id', 'in', ids)], context=context)
 +            if self.pool.get('stock.move').search(cr, uid, [('product_id', 'in', product_ids)], context=context, limit=1):
 +                raise osv.except_osv(_('Error!'), _("You can not change the unit of measure of a product that has already been used in a stock move. If you need to change the unit of measure, you may deactivate this product.") % ())
 +        return super(product_template, self).write(cr, uid, ids, vals, context=context)
 +
  
  class product_removal_strategy(osv.osv):
      _name = 'product.removal'
diff --combined addons/stock/stock.py
@@@ -25,6 -25,7 +25,7 @@@ import jso
  import time
  
  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
@@@ -377,9 -378,10 +378,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
  class stock_picking(osv.osv):
      _name = "stock.picking"
      _inherit = ['mail.thread']
 -    _description = "Picking List"
 +    _description = "Transfer"
      _order = "priority desc, date asc, id desc"
  
      def _set_min_date(self, cr, uid, id, field, value, arg, context=None):
          if ('name' not in vals) or (vals.get('name') in ('/', False)):
              ptype_id = vals.get('picking_type_id', context.get('default_picking_type_id', False))
              sequence_id = self.pool.get('stock.picking.type').browse(cr, user, ptype_id, context=context).sequence_id.id
 -            vals['name'] = self.pool.get('ir.sequence').get_id(cr, user, sequence_id, 'id', context=context)
 +            vals['name'] = self.pool.get('ir.sequence').next_by_id(cr, user, sequence_id, context=context)
          return super(stock_picking, self).create(cr, user, vals, context)
  
      def _state_get(self, cr, uid, ids, field_name, arg, context=None):
          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
  
      @api.cr_uid_ids_context
      def open_barcode_interface(self, cr, uid, picking_ids, context=None):
 -        final_url="/barcode/web/#action=stock.ui&picking_id="+str(picking_ids[0])
 +        final_url="/stock/barcode/#action=stock.ui&picking_id="+str(picking_ids[0])
          return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
  
      @api.cr_uid_ids_context
                  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)
@@@ -1486,7 -1524,7 +1524,7 @@@ class stock_production_lot(osv.osv)
          'create_date': fields.datetime('Creation Date'),
      }
      _defaults = {
 -        'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'stock.lot.serial'),
 +        'name': lambda x, y, z, c: x.pool.get('ir.sequence').next_by_code(y, z, 'stock.lot.serial'),
          'product_id': lambda x, y, z, c: c.get('product_id', False),
      }
      _sql_constraints = [
@@@ -1549,7 -1587,7 +1587,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', 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'),
          'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
          'split_from': fields.many2one('stock.move', string="Move Split From", help="Technical field used to track the origin of a split move, which can be useful in case of debug", copy=False),
          'backorder_id': fields.related('picking_id', 'backorder_id', type='many2one', relation="stock.picking", string="Back Order of", select=True),
 -        'origin': fields.char("Source"),
 +        'origin': fields.char("Source Document"),
          'procure_method': fields.selection([('make_to_stock', 'Default: Take From Stock'), ('make_to_order', 'Advanced: Apply Procurement Rules')], 'Supply Method', required=True, 
                                             help="""By default, the system will take from the stock in the source location and passively wait for availability. The other possibility allows you to directly create a procurement on the source location (and thus ignore its current stock) to gather products. If we want to chain moves and have this one to wait for the previous, this second option should be chosen."""),
  
          '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 = {
@@@ -2571,7 -2611,6 +2611,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
@@@ -2746,14 -2785,6 +2785,14 @@@ class stock_inventory_line(osv.osv)
          'product_qty': 1,
      }
  
 +    def create(self, cr, uid, values, context=None):
 +        if context is None:
 +            context = {}
 +        product_obj = self.pool.get('product.product')
 +        if 'product_id' in values and not 'product_uom_id' in values:
 +            values['product_uom_id'] = product_obj.browse(cr, uid, values.get('product_id'), context=context).uom_id.id
 +        return super(stock_inventory_line, self).create(cr, uid, values, context=context)
 +
      def _resolve_inventory_line(self, cr, uid, inventory_line, context=None):
          stock_move_obj = self.pool.get('stock.move')
          diff = inventory_line.theoretical_qty - inventory_line.product_qty
@@@ -3325,7 -3356,6 +3364,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
@@@ -3587,7 -3616,6 +3624,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
@@@ -3692,7 -3720,7 +3728,7 @@@ class stock_package(osv.osv)
                                      }, readonly=True, select=True),
      }
      _defaults = {
 -        'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.quant.package') or _('Unknown Pack')
 +        'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').next_by_code(cr, uid, 'stock.quant.package') or _('Unknown Pack')
      }
  
      def _check_location_constraint(self, cr, uid, packs, context=None):
@@@ -3803,10 -3831,7 +3839,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),
@@@ -4087,7 -4112,7 +4120,7 @@@ class stock_warehouse_orderpoint(osv.os
          'active': lambda *a: 1,
          'logic': lambda *a: 'max',
          'qty_multiple': lambda *a: 1,
 -        'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').get(cr, uid, 'stock.orderpoint') or '',
 +        'name': lambda self, cr, uid, context: self.pool.get('ir.sequence').next_by_code(cr, uid, 'stock.orderpoint') or '',
          'product_uom': lambda self, cr, uid, context: context.get('product_uom', False),
          'company_id': lambda self, cr, uid, context: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.warehouse.orderpoint', context=context)
      }
@@@ -4138,7 -4163,7 +4171,7 @@@ class stock_picking_type(osv.osv)
      _order = 'sequence'
  
      def open_barcode_interface(self, cr, uid, ids, context=None):
 -        final_url = "/barcode/web/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
 +        final_url = "/stock/barcode/#action=stock.ui&picking_type_id=" + str(ids[0]) if len(ids) else '0'
          return {'type': 'ir.actions.act_url', 'url': final_url, 'target': 'self'}
  
      def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
@@@ -51,7 -51,6 +51,7 @@@ class actions(osv.osv)
          'name': fields.char('Name', required=True),
          'type': fields.char('Action Type', required=True),
          'usage': fields.char('Action Usage'),
 +        'xml_id': fields.function(osv.osv.get_external_id, type='char', string="External ID"),
          'help': fields.text('Action description',
              help='Optional help text for the users with a description of the target view, such as its usage and purpose.',
              translate=True),
@@@ -272,7 -271,7 +272,7 @@@ class ir_actions_act_window(osv.osv)
      _columns = {
          'name': fields.char('Action Name', translate=True),
          'type': fields.char('Action Type', required=True),
-         'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='cascade'),
+         'view_id': fields.many2one('ir.ui.view', 'View Ref.', ondelete='set null'),
          'domain': fields.char('Domain Value',
              help="Optional domain filtering of the destination data, as a Python expression"),
          'context': fields.char('Context Value', required=True,
diff --combined openerp/models.py
@@@ -241,6 -241,11 +241,11 @@@ class MetaModel(api.Meta)
          if not self._custom:
              self.module_to_models.setdefault(self._module, []).append(self)
  
+         # check for new-api conversion error: leave comma after field definition
+         for key, val in attrs.iteritems():
+             if type(val) is tuple and len(val) == 1 and isinstance(val[0], Field):
+                 _logger.error("Trailing comma after field definition: %s.%s", self, key)
          # transform columns into new-style fields (enables field inheritance)
          for name, column in self._columns.iteritems():
              if name in self.__dict__:
@@@ -2269,7 -2274,7 +2274,7 @@@ class BaseModel(object)
                  _schema.debug("Table '%s': column '%s': dropped NOT NULL constraint",
                                self._table, column['attname'])
  
 -    def _save_constraint(self, cr, constraint_name, type):
 +    def _save_constraint(self, cr, constraint_name, type, definition):
          """
          Record the creation of a constraint for this model, to make it possible
          to delete it later when the module is uninstalled. Type can be either
              return
          assert type in ('f', 'u')
          cr.execute("""
 -            SELECT 1 FROM ir_model_constraint, ir_module_module
 +            SELECT type, definition FROM ir_model_constraint, ir_module_module
              WHERE ir_model_constraint.module=ir_module_module.id
                  AND ir_model_constraint.name=%s
                  AND ir_module_module.name=%s
              """, (constraint_name, self._module))
 -        if not cr.rowcount:
 +        constraints = cr.dictfetchone()
 +        if not constraints:
              cr.execute("""
                  INSERT INTO ir_model_constraint
 -                    (name, date_init, date_update, module, model, type)
 +                    (name, date_init, date_update, module, model, type, definition)
                  VALUES (%s, now() AT TIME ZONE 'UTC', now() AT TIME ZONE 'UTC',
                      (SELECT id FROM ir_module_module WHERE name=%s),
 -                    (SELECT id FROM ir_model WHERE model=%s), %s)""",
 -                    (constraint_name, self._module, self._name, type))
 +                    (SELECT id FROM ir_model WHERE model=%s), %s, %s)""",
 +                    (constraint_name, self._module, self._name, type, definition))
 +        elif constraints['type'] != type or (definition and constraints['definition'] != definition):
 +            cr.execute("""
 +                UPDATE ir_model_constraint
 +                SET date_update=now() AT TIME ZONE 'UTC', type=%s, definition=%s
 +                WHERE name=%s AND module = (SELECT id FROM ir_module_module WHERE name=%s)""",
 +                    (type, definition, constraint_name, self._module))
  
      def _save_relation_table(self, cr, relation_table):
          """
          """ Create the foreign keys recorded by _auto_init. """
          for t, k, r, d in self._foreign_keys:
              cr.execute('ALTER TABLE "%s" ADD FOREIGN KEY ("%s") REFERENCES "%s" ON DELETE %s' % (t, k, r, d))
 -            self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f')
 +            self._save_constraint(cr, "%s_%s_fkey" % (t, k), 'f', False)
          cr.commit()
          del self._foreign_keys
  
          for (key, con, _) in self._sql_constraints:
              conname = '%s_%s' % (self._table, key)
  
 -            self._save_constraint(cr, conname, 'u')
 -            cr.execute("SELECT conname, pg_catalog.pg_get_constraintdef(oid, true) as condef FROM pg_constraint where conname=%s", (conname,))
 -            existing_constraints = cr.dictfetchall()
 +            # using 1 to get result if no imc but one pgc
 +            cr.execute("""SELECT definition, 1
 +                          FROM ir_model_constraint imc
 +                          RIGHT JOIN pg_constraint pgc
 +                          ON (pgc.conname = imc.name)
 +                          WHERE pgc.conname=%s
 +                          """, (conname, ))
 +            existing_constraints = cr.dictfetchone()
              sql_actions = {
                  'drop': {
                      'execute': False,
                  # constraint does not exists:
                  sql_actions['add']['execute'] = True
                  sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
 -            elif unify_cons_text(con) not in [unify_cons_text(item['condef']) for item in existing_constraints]:
 +            elif unify_cons_text(con) != existing_constraints['definition']:
                  # constraint exists but its definition has changed:
                  sql_actions['drop']['execute'] = True
 -                sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints[0]['condef'].lower(), )
 +                sql_actions['drop']['msg_ok'] = sql_actions['drop']['msg_ok'] % (existing_constraints['definition'] or '', )
                  sql_actions['add']['execute'] = True
                  sql_actions['add']['msg_err'] = sql_actions['add']['msg_err'] % (sql_actions['add']['query'], )
  
              # we need to add the constraint:
 +            self._save_constraint(cr, conname, 'u', unify_cons_text(con))
              sql_actions = [item for item in sql_actions.values()]
              sql_actions.sort(key=lambda x: x['order'])
              for sql_action in [action for action in sql_actions if action['execute']]:
  
          By convention, new records are returned as existing.
          """
-         ids = filter(None, self._ids)           # ids to check in database
+         ids, new_ids = [], []
+         for i in self._ids:
+             (ids if isinstance(i, (int, long)) else new_ids).append(i)
          if not ids:
              return self
          query = """SELECT id FROM "%s" WHERE id IN %%s""" % self._table
-         self._cr.execute(query, (ids,))
-         ids = ([r[0] for r in self._cr.fetchall()] +    # ids in database
-                [id for id in self._ids if not id])      # new ids
-         existing = self.browse(ids)
+         self._cr.execute(query, [tuple(ids)])
+         ids = [r[0] for r in self._cr.fetchall()]
+         existing = self.browse(ids + new_ids)
          if len(existing) < len(self):
              # mark missing records in cache with a failed value
              exc = MissingError(_("Record does not exist or has been deleted."))
@@@ -42,13 -42,6 +42,13 @@@ from openerp.tools.misc import stripped
  
  _logger = logging.getLogger(__name__)
  
 +try:
 +    import watchdog
 +    from watchdog.observers import Observer
 +    from watchdog.events import FileCreatedEvent, FileModifiedEvent
 +except ImportError:
 +    watchdog = None
 +
  SLEEP_INTERVAL = 60     # 1 min
  
  #----------------------------------------------------------
@@@ -113,37 -106,90 +113,37 @@@ class ThreadedWSGIServerReloadable(Logg
              super(ThreadedWSGIServerReloadable, self).server_activate()
  
  #----------------------------------------------------------
 -# AutoReload watcher
 +# FileSystem Watcher for autoreload and cache invalidation
  #----------------------------------------------------------
 -
 -class AutoReload(object):
 -    def __init__(self, server):
 -        self.server = server
 -        self.files = {}
 -        self.modules = {}
 -        import pyinotify
 -        class EventHandler(pyinotify.ProcessEvent):
 -            def __init__(self, autoreload):
 -                self.autoreload = autoreload
 -
 -            def process_IN_CREATE(self, event):
 -                _logger.debug('File created: %s', event.pathname)
 -                self.autoreload.files[event.pathname] = 1
 -
 -            def process_IN_MODIFY(self, event):
 -                _logger.debug('File modified: %s', event.pathname)
 -                self.autoreload.files[event.pathname] = 1
 -
 -        self.wm = pyinotify.WatchManager()
 -        self.handler = EventHandler(self)
 -        self.notifier = pyinotify.Notifier(self.wm, self.handler, timeout=0)
 -        mask = pyinotify.IN_MODIFY | pyinotify.IN_CREATE  # IN_MOVED_FROM, IN_MOVED_TO ?
 +class FSWatcher(object):
 +    def __init__(self):
 +        self.observer = Observer()
          for path in openerp.modules.module.ad_paths:
              _logger.info('Watching addons folder %s', path)
 -            self.wm.add_watch(path, mask, rec=True)
 -
 -    def process_data(self, files):
 -        xml_files = [i for i in files if i.endswith('.xml')]
 -        for i in xml_files:
 -            for path in openerp.modules.module.ad_paths:
 -                if i.startswith(path):
 -                    # find out wich addons path the file belongs to
 -                    # and extract it's module name
 -                    right = i[len(path) + 1:].split('/')
 -                    if len(right) < 2:
 -                        continue
 -                    module = right[0]
 -                    self.modules[module] = 1
 -        if self.modules:
 -            _logger.info('autoreload: xml change detected, autoreload activated')
 -            restart()
 -
 -    def process_python(self, files):
 -        # process python changes
 -        py_files = [i for i in files if i.endswith('.py')]
 -        py_errors = []
 -        # TODO keep python errors until they are ok
 -        if py_files:
 -            for i in py_files:
 -                try:
 -                    source = open(i, 'rb').read() + '\n'
 -                    compile(source, i, 'exec')
 -                except SyntaxError:
 -                    py_errors.append(i)
 -            if py_errors:
 -                _logger.info('autoreload: python code change detected, errors found')
 -                for i in py_errors:
 -                    _logger.info('autoreload: SyntaxError %s', i)
 -            else:
 -                _logger.info('autoreload: python code updated, autoreload activated')
 -                restart()
 -
 -    def check_thread(self):
 -        # Check if some files have been touched in the addons path.
 -        # If true, check if the touched file belongs to an installed module
 -        # in any of the database used in the registry manager.
 -        while 1:
 -            while self.notifier.check_events(1000):
 -                self.notifier.read_events()
 -                self.notifier.process_events()
 -            l = self.files.keys()
 -            self.files.clear()
 -            self.process_data(l)
 -            self.process_python(l)
 +            self.observer.schedule(self, path, recursive=True)
 +
 +    def dispatch(self, event):
 +        if isinstance(event, (FileCreatedEvent, FileModifiedEvent)):
 +            if not event.is_directory:
 +                path = event.src_path
 +                if path.endswith('.py'):
 +                    try:
 +                        source = open(path, 'rb').read() + '\n'
 +                        compile(source, path, 'exec')
 +                    except SyntaxError:
 +                        _logger.error('autoreload: python code change detected, SyntaxError in %s', path)
 +                    else:
 +                        _logger.info('autoreload: python code updated, autoreload activated')
 +                        restart()
  
 -    def run(self):
 -        t = threading.Thread(target=self.check_thread)
 -        t.setDaemon(True)
 -        t.start()
 +    def start(self):
 +        self.observer.start()
          _logger.info('AutoReload watcher running')
  
 +    def stop(self):
 +        self.observer.stop()
 +        self.observer.join()
 +
  #----------------------------------------------------------
  # Servers: Threaded, Gevented and Prefork
  #----------------------------------------------------------
@@@ -813,8 -859,7 +813,8 @@@ def _reexec(updated_modules=None)
          subprocess.call('net stop {0} && net start {0}'.format(nt_service_name), shell=True)
      exe = os.path.basename(sys.executable)
      args = stripped_sys_argv()
 -    args += ["-u", ','.join(updated_modules)]
 +    if updated_modules:
 +        args += ["-u", ','.join(updated_modules)]
      if not args or args[0] != exe:
          args.insert(0, exe)
      os.execv(sys.executable, args)
@@@ -862,10 -907,11 +862,11 @@@ def preload_registries(dbnames)
              # run test_file if provided
              if test_file:
                  _logger.info('loading test file %s', test_file)
-                 if test_file.endswith('yml'):
-                     load_test_file_yml(registry, test_file)
-                 elif test_file.endswith('py'):
-                     load_test_file_py(registry, test_file)
+                 with openerp.api.Environment.manage():
+                     if test_file.endswith('yml'):
+                         load_test_file_yml(registry, test_file)
+                     elif test_file.endswith('py'):
+                         load_test_file_py(registry, test_file)
  
              if registry._assertion_report.failures:
                  rc += 1
@@@ -886,21 -932,18 +887,21 @@@ def start(preload=None, stop=False)
      else:
          server = ThreadedServer(openerp.service.wsgi_server.application)
  
 -    if config['auto_reload']:
 -        autoreload = AutoReload(server)
 -        autoreload.run()
 +    watcher = None
 +    if config['dev_mode']:
 +        if watchdog:
 +            watcher = FSWatcher()
 +            watcher.start()
 +        else:
 +            _logger.warning("'watchdog' module not installed. Code autoreload feature is disabled")
  
      rc = server.run(preload, stop)
  
      # like the legend of the phoenix, all ends with beginnings
      if getattr(openerp, 'phoenix', False):
 -        modules = []
 -        if config['auto_reload']:
 -            modules = autoreload.modules.keys()
 -        _reexec(modules)
 +        if watcher:
 +            watcher.stop()
 +        _reexec()
  
      return rc if rc else 0