[MERGE] Forward-port of latest 7.0 bugfixes, up to rev. 10016 revid:dle@openerp.com...
authorDenis Ledoux <dle@openerp.com>
Fri, 25 Apr 2014 12:59:26 +0000 (14:59 +0200)
committerDenis Ledoux <dle@openerp.com>
Fri, 25 Apr 2014 12:59:26 +0000 (14:59 +0200)
bzr revid: dle@openerp.com-20140425125926-5nchz1tcq4fx18jx

1  2 
addons/hr_timesheet_sheet/hr_timesheet_sheet.py
addons/product/product.py
addons/sale_stock/sale_stock.py
addons/stock/stock_view.xml

  ##############################################################################
  
  import time
- from datetime import datetime, timedelta
+ from datetime import datetime
  from dateutil.relativedelta import relativedelta
  
  from openerp.osv import fields, osv
+ from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
  from openerp.tools.translate import _
 -from openerp import netsvc
  
  class hr_timesheet_sheet(osv.osv):
      _name = "hr_timesheet_sheet.sheet"
              self.check_employee_attendance_state(cr, uid, sheet.id, context=context)
              di = sheet.user_id.company_id.timesheet_max_difference
              if (abs(sheet.total_difference) < di) or not di:
 -                wf_service = netsvc.LocalService("workflow")
 -                wf_service.trg_validate(uid, 'hr_timesheet_sheet.sheet', sheet.id, 'confirm', cr)
 +                self.signal_confirm(cr, uid, [sheet.id])
              else:
                  raise osv.except_osv(_('Warning!'), _('Please verify that the total difference of the sheet is lower than %.2f.') %(di,))
          return True
  
      def action_set_to_draft(self, cr, uid, ids, *args):
          self.write(cr, uid, ids, {'state': 'draft'})
 -        wf_service = netsvc.LocalService('workflow')
 -        for id in ids:
 -            wf_service.trg_create(uid, self._name, id, cr)
 +        self.create_workflow(cr, uid, ids)
          return True
  
      def name_get(self, cr, uid, ids, context=None):
@@@ -355,6 -360,7 +356,6 @@@ class hr_timesheet_line(osv.osv)
          return dict([(el, self.on_change_account_id(cr, uid, ids, el, context.get('user_id', uid))) for el in account_ids])
  
  
 -hr_timesheet_line()
  
  class hr_attendance(osv.osv):
      _inherit = "hr.attendance"
              attendance_ids.extend([row[0] for row in cr.fetchall()])
          return attendance_ids
  
+     def _get_current_sheet(self, cr, uid, employee_id, date=False, context=None):
+         if not date:
+             date = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+         # ending date with no time to avoid timesheet with early date_to
+         date_to = date[0:10]+' 00:00:00'
+         # limit=1 because only one sheet possible for an employee between 2 dates
+         sheet_ids = self.pool.get('hr_timesheet_sheet.sheet').search(cr, uid, [
+             ('date_to', '>=', date_to), ('date_from', '<=', date),
+             ('employee_id', '=', employee_id)
+         ], limit=1, context=context)
+         return sheet_ids and sheet_ids[0] or False
      def _sheet(self, cursor, user, ids, name, args, context=None):
-         sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
          res = {}.fromkeys(ids, False)
          for attendance in self.browse(cursor, user, ids, context=context):
-             date_to = datetime.strftime(datetime.strptime(attendance.name[0:10], '%Y-%m-%d'), '%Y-%m-%d %H:%M:%S')
-             sheet_ids = sheet_obj.search(cursor, user,
-                 [('date_to', '>=', date_to), ('date_from', '<=', attendance.name),
-                  ('employee_id', '=', attendance.employee_id.id)],
-                 context=context)
-             if sheet_ids:
-                 # [0] because only one sheet possible for an employee between 2 dates
-                 res[attendance.id] = sheet_obj.name_get(cursor, user, sheet_ids, context=context)[0]
+             res[attendance.id] = self._get_current_sheet(cursor, user, attendance.employee_id.id, attendance.name, context=context)
          return res
  
      _columns = {
      def create(self, cr, uid, vals, context=None):
          if context is None:
              context = {}
-         if 'sheet_id' in context:
-             ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, context['sheet_id'], context=context)
+         sheet_id = context.get('sheet_id') or self._get_current_sheet(cr, uid, vals.get('employee_id'), vals.get('name'), context=context)
+         if sheet_id:
+             ts = self.pool.get('hr_timesheet_sheet.sheet').browse(cr, uid, sheet_id, context=context)
              if ts.state not in ('draft', 'new'):
-                 raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet.'))
-         res = super(hr_attendance,self).create(cr, uid, vals, context=context)
-         if 'sheet_id' in context:
-             if context['sheet_id'] != self.browse(cr, uid, res, context=context).sheet_id.id:
-                 raise osv.except_osv(_('User Error!'), _('You cannot enter an attendance ' \
-                         'date outside the current timesheet dates.'))
-         return res
+                 raise osv.except_osv(_('Error!'), _('You can not enter an attendance in a submitted timesheet. Ask your manager to reset it before adding attendance.'))
+             elif ts.date_from > vals.get('name') or ts.date_to < vals.get('name'):
+                 raise osv.except_osv(_('User Error!'), _('You can not enter an attendance date outside the current timesheet dates.'))
+         return super(hr_attendance,self).create(cr, uid, vals, context=context)
  
      def unlink(self, cr, uid, ids, *args, **kwargs):
          if isinstance(ids, (int, long)):
                  raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet'))
          return True
  
 -hr_attendance()
  
  class hr_timesheet_sheet_sheet_day(osv.osv):
      _name = "hr_timesheet_sheet.sheet.day"
                          GROUP BY name, sheet_id
                  )) AS bar""")
  
 -hr_timesheet_sheet_sheet_day()
  
  
  class hr_timesheet_sheet_sheet_account(osv.osv):
              group by l.account_id, s.id, l.to_invoice
          )""")
  
 -hr_timesheet_sheet_sheet_account()
  
  
  
@@@ -572,6 -584,7 +576,6 @@@ class res_company(osv.osv)
          'timesheet_max_difference': lambda *args: 0.0
      }
  
 -res_company()
  
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
  
@@@ -24,16 -24,15 +24,16 @@@ import r
  
  from _common import ceiling
  
 -from openerp import tools, SUPERUSER_ID
 -from openerp.osv import osv, fields
 +from openerp import SUPERUSER_ID
 +from openerp import tools
 +from openerp.osv import osv, orm, fields
  from openerp.tools.translate import _
  
  import openerp.addons.decimal_precision as dp
  
  def ean_checksum(eancode):
      """returns the checksum of an ean string of length 13, returns -1 if the string has the wrong length"""
 -    if len(eancode) <> 13:
 +    if len(eancode) != 13:
          return -1
      oddsum=0
      evensum=0
@@@ -56,7 -55,7 +56,7 @@@ def check_ean(eancode)
      """returns True if eancode is a valid ean13 string, or null"""
      if not eancode:
          return True
 -    if len(eancode) <> 13:
 +    if len(eancode) != 13:
          return False
      try:
          int(eancode)
@@@ -83,8 -82,9 +83,8 @@@ class product_uom_categ(osv.osv)
      _name = 'product.uom.categ'
      _description = 'Product uom categ'
      _columns = {
 -        'name': fields.char('Name', size=64, required=True, translate=True),
 +        'name': fields.char('Name', required=True, translate=True),
      }
 -product_uom_categ()
  
  class product_uom(osv.osv):
      _name = 'product.uom'
  
      def create(self, cr, uid, data, context=None):
          if 'factor_inv' in data:
 -            if data['factor_inv'] <> 1:
 +            if data['factor_inv'] != 1:
                  data['factor'] = self._compute_factor_inv(data['factor_inv'])
              del(data['factor_inv'])
          return super(product_uom, self).create(cr, uid, data, context)
  
      _order = "name"
      _columns = {
 -        'name': fields.char('Unit of Measure', size=64, required=True, translate=True),
 +        'name': fields.char('Unit of Measure', required=True, translate=True),
          'category_id': fields.many2one('product.uom.categ', 'Category', required=True, ondelete='cascade',
              help="Conversion between Units of Measure can only occur if they belong to the same category. The conversion will be made based on the ratios."),
          'factor': fields.float('Ratio', required=True,digits=(12, 12),
                      '1 * (reference unit) = ratio * (this unit)'),
          'factor_inv': fields.function(_factor_inv, digits=(12,12),
              fnct_inv=_factor_inv_write,
 -            string='Ratio',
 +            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,
      def _compute_qty_obj(self, cr, uid, from_unit, qty, to_unit, context=None):
          if context is None:
              context = {}
 -        if from_unit.category_id.id <> to_unit.category_id.id:
 +        if from_unit.category_id.id != to_unit.category_id.id:
              if context.get('raise-exception', True):
                  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:
              from_unit, to_unit = uoms[0], uoms[-1]
          else:
              from_unit, to_unit = uoms[-1], uoms[0]
 -        if from_unit.category_id.id <> to_unit.category_id.id:
 +        if from_unit.category_id.id != to_unit.category_id.id:
              return price
          amount = price * from_unit.factor
          if to_uom_id:
                      raise osv.except_osv(_('Warning!'),_("Cannot change the category of existing Unit of Measure '%s'.") % (uom.name,))
          return super(product_uom, self).write(cr, uid, ids, vals, context=context)
  
 -product_uom()
  
  
  class product_ul(osv.osv):
      _name = "product.ul"
      _description = "Shipping Unit"
      _columns = {
 -        'name' : fields.char('Name', size=64,select=True, required=True, translate=True),
 +        'name' : fields.char('Name', select=True, required=True, translate=True),
          'type' : fields.selection([('unit','Unit'),('pack','Pack'),('box', 'Box'), ('pallet', 'Pallet')], 'Type', required=True),
      }
 -product_ul()
  
  
  #----------------------------------------------------------
@@@ -245,7 -247,7 +245,7 @@@ class product_category(osv.osv)
      _name = "product.category"
      _description = "Product Category"
      _columns = {
 -        'name': fields.char('Name', size=64, required=True, translate=True, select=True),
 +        'name': fields.char('Name', required=True, translate=True, select=True),
          'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
          'parent_id': fields.many2one('product.category','Parent Category', select=True, ondelete='cascade'),
          'child_id': fields.one2many('product.category', 'parent_id', string='Child Categories'),
      _parent_order = 'sequence, name'
      _order = 'parent_left'
  
 -    def _check_recursion(self, cr, uid, ids, context=None):
 -        level = 100
 -        while len(ids):
 -            cr.execute('select distinct parent_id from product_category where id IN %s',(tuple(ids),))
 -            ids = filter(None, map(lambda x:x[0], cr.fetchall()))
 -            if not level:
 -                return False
 -            level -= 1
 -        return True
 -
      _constraints = [
 -        (_check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
 +        (osv.osv._check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
      ]
      def child_get(self, cr, uid, ids):
          return [ids]
  
 -product_category()
 +
 +class product_public_category(osv.osv):
 +    _name = "product.public.category"
 +    _description = "Public Category"
 +    _order = "sequence, name"
 +
 +    _constraints = [
 +        (osv.osv._check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
 +    ]
 +
 +    def name_get(self, cr, uid, ids, context=None):
 +        if not len(ids):
 +            return []
 +        reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
 +        res = []
 +        for record in reads:
 +            name = record['name']
 +            if record['parent_id']:
 +                name = record['parent_id'][1]+' / '+name
 +            res.append((record['id'], name))
 +        return res
 +
 +    def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
 +        res = self.name_get(cr, uid, ids, context=context)
 +        return dict(res)
 +
 +    def _get_image(self, cr, uid, ids, name, args, context=None):
 +        result = dict.fromkeys(ids, False)
 +        for obj in self.browse(cr, uid, ids, context=context):
 +            result[obj.id] = tools.image_get_resized_images(obj.image)
 +        return result
 +    
 +    def _set_image(self, cr, uid, id, name, value, args, context=None):
 +        return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
 +
 +    _columns = {
 +        'name': fields.char('Name', required=True, translate=True),
 +        'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
 +        'parent_id': fields.many2one('product.public.category','Parent Category', select=True),
 +        'child_id': fields.one2many('product.public.category', 'parent_id', string='Children Categories'),
 +        'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of product categories."),
 +        
 +        # NOTE: there is no 'default image', because by default we don't show thumbnails for categories. However if we have a thumbnail
 +        # for at least one category, then we display a default image on the other, so that the buttons have consistent styling.
 +        # In this case, the default image is set by the js code.
 +        # NOTE2: image: all image fields are base64 encoded and PIL-supported
 +        'image': fields.binary("Image",
 +            help="This field holds the image used as image for the cateogry, limited to 1024x1024px."),
 +        'image_medium': fields.function(_get_image, fnct_inv=_set_image,
 +            string="Medium-sized image", type="binary", multi="_get_image",
 +            store={
 +                'product.public.category': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
 +            },
 +            help="Medium-sized image of the category. It is automatically "\
 +                 "resized as a 128x128px image, with aspect ratio preserved. "\
 +                 "Use this field in form views or some kanban views."),
 +        'image_small': fields.function(_get_image, fnct_inv=_set_image,
 +            string="Smal-sized image", type="binary", multi="_get_image",
 +            store={
 +                'product.public.category': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
 +            },
 +            help="Small-sized image of the category. It is automatically "\
 +                 "resized as a 64x64px image, with aspect ratio preserved. "\
 +                 "Use this field anywhere a small image is required."),
 +    }
  
  
  #----------------------------------------------------------
  #----------------------------------------------------------
  class product_template(osv.osv):
      _name = "product.template"
 +    _inherit = ['mail.thread']
      _description = "Product Template"
  
 +    def _get_image(self, cr, uid, ids, name, args, context=None):
 +        result = dict.fromkeys(ids, False)
 +        for obj in self.browse(cr, uid, ids, context=context):
 +            result[obj.id] = tools.image_get_resized_images(obj.image, avoid_resize_medium=True)
 +        return result
 +
 +    def _set_image(self, cr, uid, id, name, value, args, context=None):
 +        return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
 +
      _columns = {
 -        'name': fields.char('Name', size=128, required=True, translate=True, select=True),
 +        'name': fields.char('Name', required=True, translate=True, select=True),
          'product_manager': fields.many2one('res.users','Product Manager'),
 -        'description': fields.text('Description',translate=True),
 -        'description_purchase': fields.text('Purchase Description',translate=True),
 -        'description_sale': fields.text('Sale Description',translate=True),
 +        'description': fields.text('Description',translate=True,
 +            help="A precise description of the Product, used only for internal information purposes."),
 +        'description_purchase': fields.text('Purchase Description',translate=True,
 +            help="A description of the Product that you want to communicate to your suppliers. "
 +                 "This description will be copied to every Purchase Order, Reception and Supplier Invoice/Refund."),
 +        'description_sale': fields.text('Sale Description',translate=True,
 +            help="A description of the Product that you want to communicate to your customers. "
 +                 "This description will be copied to every Sale Order, Delivery Order and Customer Invoice/Refund"),
          'type': fields.selection([('consu', 'Consumable'),('service','Service')], 'Product Type', required=True, help="Consumable are product where you don't manage stock, a service is a non-material product provided by a company or an individual."),
          'produce_delay': fields.float('Manufacturing Lead Time', help="Average delay in days to produce this product. In the case of multi-level BOM, the manufacturing lead times of the components will be added."),
          'rental': fields.boolean('Can be Rent'),
          'categ_id': fields.many2one('product.category','Category', required=True, change_default=True, domain="[('type','=','normal')]" ,help="Select category for the current product"),
 +        'public_categ_id': fields.many2one('product.public.category','Public Category', help="Those categories are used to group similar products for public sales (eg.: point of sale, e-commerce)."),
          'list_price': fields.float('Sale Price', digits_compute=dp.get_precision('Product Price'), help="Base price to compute the customer price. Sometimes called the catalog price."),
 -        'standard_price': fields.float('Cost', digits_compute=dp.get_precision('Product Price'), help="Cost price of the product used for standard stock valuation in accounting and used as a base price on purchase orders.", groups="base.group_user"),
 +        'standard_price': fields.float('Cost Price', digits_compute=dp.get_precision('Product Price'), help="Cost price of the product template used for standard stock valuation in accounting and used as a base price on purchase orders.", groups="base.group_user"),
          'volume': fields.float('Volume', help="The volume in m3."),
          'weight': fields.float('Gross Weight', digits_compute=dp.get_precision('Stock Weight'), help="The gross weight in Kg."),
          'weight_net': fields.float('Net Weight', digits_compute=dp.get_precision('Stock Weight'), help="The net weight in Kg."),
          'uom_id': fields.many2one('product.uom', 'Unit of Measure', required=True, help="Default Unit of Measure used for all stock operation."),
          'uom_po_id': fields.many2one('product.uom', 'Purchase Unit of Measure', required=True, help="Default Unit of Measure used for purchase orders. It must be in the same category than the default unit of measure."),
          'uos_id' : fields.many2one('product.uom', 'Unit of Sale',
 -            help='Sepcify a unit of measure here if invoicing is made in another unit of measure than inventory. Keep empty to use the default unit of measure.'),
 +            help='Specify a unit of measure here if invoicing is made in another unit of measure than inventory. Keep empty to use the default unit of measure.'),
          'uos_coeff': fields.float('Unit of Measure -> UOS Coeff', digits_compute= dp.get_precision('Product UoS'),
              help='Coefficient to convert default Unit of Measure to Unit of Sale\n'
              ' uos = uom * coeff'),
          'mes_type': fields.selection((('fixed', 'Fixed'), ('variable', 'Variable')), 'Measure Type'),
 -        'seller_ids': fields.one2many('product.supplierinfo', 'product_id', 'Supplier'),
 +        'seller_ids': fields.one2many('product.supplierinfo', 'product_tmpl_id', 'Supplier'),
          'company_id': fields.many2one('res.company', 'Company', select=1),
 +        # image: all image fields are base64 encoded and PIL-supported
 +        'image': fields.binary("Image",
 +            help="This field holds the image used as image for the product, limited to 1024x1024px."),
 +        'image_medium': fields.function(_get_image, fnct_inv=_set_image,
 +            string="Medium-sized image", type="binary", multi="_get_image",
 +            store={
 +                'product.template': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
 +            },
 +            help="Medium-sized image of the product. It is automatically "\
 +                 "resized as a 128x128px image, with aspect ratio preserved, "\
 +                 "only when the image exceeds one of those sizes. Use this field in form views or some kanban views."),
 +        'image_small': fields.function(_get_image, fnct_inv=_set_image,
 +            string="Small-sized image", type="binary", multi="_get_image",
 +            store={
 +                'product.template': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
 +            },
 +            help="Small-sized image of the product. It is automatically "\
 +                 "resized as a 64x64px image, with aspect ratio preserved. "\
 +                 "Use this field anywhere a small image is required."),
 +        'product_variant_ids': fields.one2many('product.product', 'product_tmpl_id', 'Product Variants', required=True),
      }
  
      def _get_uom_id(self, cr, uid, *args):
                      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,))
          return super(product_template, self).write(cr, uid, ids, vals, context=context)
  
 +    def copy(self, cr, uid, id, default=None, context=None):
 +        if default is None:
 +            default = {}
 +        template = self.browse(cr, uid, id, context=context)
 +        default['name'] = _("%s (copy)") % (template['name'])
 +        return super(product_template, self).copy(cr, uid, id, default=default, context=context)
 +
      _defaults = {
          'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'product.template', context=c),
          'list_price': 1,
  
      def _check_uom(self, cursor, user, ids, context=None):
          for product in self.browse(cursor, user, ids, context=context):
 -            if product.uom_id.category_id.id <> product.uom_po_id.category_id.id:
 +            if product.uom_id.category_id.id != product.uom_po_id.category_id.id:
                  return False
          return True
  
              pass
          return super(product_template, self).name_get(cr, user, ids, context)
  
 -product_template()
  
  class product_product(osv.osv):
 +    _name = "product.product"
 +    _description = "Product"
 +    _inherits = {'product.template': 'product_tmpl_id'}
 +    _inherit = ['mail.thread']
 +    _order = 'default_code,name_template'
 +
      def view_header_get(self, cr, uid, view_id, view_type, context=None):
          if context is None:
              context = {}
          return res
  
      def _product_price(self, cr, uid, ids, name, arg, context=None):
 +        plobj = self.pool.get('product.pricelist')
          res = {}
          if context is None:
              context = {}
          if pricelist:
              # Support context pricelists specified as display_name or ID for compatibility
              if isinstance(pricelist, basestring):
 -                pricelist_ids = self.pool.get('product.pricelist').name_search(
 +                pricelist_ids = plobj.name_search(
                      cr, uid, pricelist, operator='=', context=context, limit=1)
                  pricelist = pricelist_ids[0][0] if pricelist_ids else pricelist
 -            for id in ids:
 -                try:
 -                    price = self.pool.get('product.pricelist').price_get(cr,uid,[pricelist], id, quantity, partner=partner, context=context)[pricelist]
 -                except:
 -                    price = 0.0
 -                res[id] = price
 +
 +            if isinstance(pricelist, (int, long)):
 +                products = self.browse(cr, uid, ids, context=context)
 +                qtys = map(lambda x: (x, quantity, partner), products)
 +                pl = plobj.browse(cr, uid, pricelist, context=context)
 +                price = plobj._price_get_multi(cr,uid, pl, qtys, context=context)
 +                for id in ids:
 +                    res[id] = price.get(id, 0.0)
          for id in ids:
              res.setdefault(id, 0.0)
          return res
              res[product.id] =  (res[product.id] or 0.0) * (product.price_margin or 1.0) + product.price_extra
          return res
  
 +    def _save_product_lst_price(self, cr, uid, product_id, field_name, field_value, arg, context=None):
 +        field_value = field_value or 0.0
 +        product = self.browse(cr, uid, product_id, context=context)
 +        list_price = (field_value - product.price_extra) / (product.price_margin or 1.0)
 +        return self.write(cr, uid, [product_id], {'list_price': list_price}, context=context)
 +
 +
      def _get_partner_code_name(self, cr, uid, ids, product, partner_id, context=None):
          for supinfo in product.seller_ids:
              if supinfo.name.id == partner_id:
                      (data['name'] or '') + (data['variants'] and (' - '+data['variants']) or '')
          return res
  
 +    def _is_only_child(self, cr, uid, ids, name, arg, context=None):
 +        res = dict.fromkeys(ids, True)
 +        for product in self.browse(cr, uid, ids, context=context):
 +            if product.product_tmpl_id and len(product.product_tmpl_id.product_variant_ids) > 1:
 +                res[product.id] = False
 +        return res
  
      def _get_main_product_supplier(self, cr, uid, product, context=None):
          """Determines the main (best) product supplier for ``product``,
              }
          return result
  
 +    def _get_name_template_ids(self, cr, uid, ids, context=None):
 +        result = set()
 +        template_ids = self.pool.get('product.product').search(cr, uid, [('product_tmpl_id', 'in', ids)])
 +        for el in template_ids:
 +            result.add(el)
 +        return list(result)
  
 -    def _get_image(self, cr, uid, ids, name, args, context=None):
 -        result = dict.fromkeys(ids, False)
 -        for obj in self.browse(cr, uid, ids, context=context):
 -            result[obj.id] = tools.image_get_resized_images(obj.image, avoid_resize_medium=True)
 -        return result
 -
 -    def _set_image(self, cr, uid, id, name, value, args, context=None):
 -        return self.write(cr, uid, [id], {'image': tools.image_resize_image_big(value)}, context=context)
 -
 -    _defaults = {
 -        'active': lambda *a: 1,
 -        'price_extra': lambda *a: 0.0,
 -        'price_margin': lambda *a: 1.0,
 -        'color': 0,
 -    }
 -
 -    _name = "product.product"
 -    _description = "Product"
 -    _table = "product_product"
 -    _inherits = {'product.template': 'product_tmpl_id'}
 -    _inherit = ['mail.thread']
 -    _order = 'default_code,name_template'
      _columns = {
          'qty_available': fields.function(_product_qty_available, type='float', string='Quantity On Hand'),
          'virtual_available': fields.function(_product_virtual_available, type='float', string='Quantity Available'),
          'incoming_qty': fields.function(_product_incoming_qty, type='float', string='Incoming'),
          'outgoing_qty': fields.function(_product_outgoing_qty, type='float', string='Outgoing'),
 -        'price': fields.function(_product_price, type='float', string='Price', digits_compute=dp.get_precision('Product Price')),
 -        'lst_price' : fields.function(_product_lst_price, type='float', string='Public Price', digits_compute=dp.get_precision('Product Price')),
 +        'price': fields.function(_product_price, fnct_inv=_save_product_lst_price, type='float', string='Price', digits_compute=dp.get_precision('Product Price')),
 +        'lst_price' : fields.function(_product_lst_price, fnct_inv=_save_product_lst_price, type='float', string='Public Price', digits_compute=dp.get_precision('Product Price')),
          'code': fields.function(_product_code, type='char', string='Internal Reference'),
          'partner_ref' : fields.function(_product_partner_ref, type='char', string='Customer ref'),
 -        'default_code' : fields.char('Internal Reference', size=64, select=True),
 +        '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."),
 -        'variants': fields.char('Variants', size=64),
 +        'variants': fields.char('Variants', translate=True),
          'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade", select=True),
 +        'is_only_child': fields.function(
 +            _is_only_child, type='boolean', string='Sole child of the parent template'),
          'ean13': fields.char('EAN13 Barcode', size=13, help="International Article Number used for product identification."),
          'packaging' : fields.one2many('product.packaging', 'product_id', 'Logistical Units', help="Gives the different ways to package the same product. This has no impact on the picking order and is mainly used if you use the EDI module."),
 -        'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Product Price')),
 -        'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Product Price')),
 +        'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Product Price'), help="Price Extra: Extra price for the variant on sale price. eg. 200 price extra, 1000 + 200 = 1200."),
 +        'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Product Price'), help="Price Margin: Margin in percentage amount on sale price for the variant. eg. 10 price margin, 1000 * 1.1 = 1100."),
          'pricelist_id': fields.dummy(string='Pricelist', relation='product.pricelist', type='many2one'),
 -        'name_template': fields.related('product_tmpl_id', 'name', string="Template Name", type='char', size=128, store=True, select=True),
 +        '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),
 +        }, select=True),
          'color': fields.integer('Color Index'),
 -        # image: all image fields are base64 encoded and PIL-supported
 -        'image': fields.binary("Image",
 -            help="This field holds the image used as image for the product, limited to 1024x1024px."),
 -        'image_medium': fields.function(_get_image, fnct_inv=_set_image,
 -            string="Medium-sized image", type="binary", multi="_get_image",
 -            store={
 -                'product.product': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
 -            },
 -            help="Medium-sized image of the product. It is automatically "\
 -                 "resized as a 128x128px image, with aspect ratio preserved, "\
 -                 "only when the image exceeds one of those sizes. Use this field in form views or some kanban views."),
 -        'image_small': fields.function(_get_image, fnct_inv=_set_image,
 -            string="Small-sized image", type="binary", multi="_get_image",
 -            store={
 -                'product.product': (lambda self, cr, uid, ids, c={}: ids, ['image'], 10),
 -            },
 -            help="Small-sized image of the product. It is automatically "\
 -                 "resized as a 64x64px image, with aspect ratio preserved. "\
 -                 "Use this field anywhere a small image is required."),
          'seller_info_id': fields.function(_calc_seller, type='many2one', relation="product.supplierinfo", string="Supplier Info", multi="seller_info"),
          'seller_delay': fields.function(_calc_seller, type='integer', string='Supplier Lead Time', multi="seller_info", help="This is the average delay in days between the purchase order confirmation and the reception of goods for this product and for the default supplier. It is used by the scheduler to order requests based on reordering delays."),
          'seller_qty': fields.function(_calc_seller, type='float', string='Supplier Quantity', multi="seller_info", help="This is minimum quantity to purchase from Main Supplier."),
          'seller_id': fields.function(_calc_seller, type='many2one', relation="res.partner", string='Main Supplier', help="Main Supplier who has highest priority in Supplier List.", multi="seller_info"),
      }
 +
 +    _defaults = {
 +        'active': lambda *a: 1,
 +        'price_extra': lambda *a: 0.0,
 +        'price_margin': lambda *a: 1.0,
 +        'color': 0,
 +        'is_only_child': True,
 +    }
 +
      def unlink(self, cr, uid, ids, context=None):
          unlink_ids = []
          unlink_product_tmpl_ids = []
              res = check_ean(product['ean13'])
          return res
  
 -
      _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):
  
          partner_id = context.get('partner_id', False)
  
 +        # all user don't have access to seller and partner
 +        # check access and use superuser
 +        self.check_access_rights(cr, user, "read")
 +        self.check_access_rule(cr, user, ids, "read", context=context)
 +
          result = []
 -        for product in self.browse(cr, user, ids, context=context):
 +        for product in self.browse(cr, SUPERUSER_ID, ids, context=context):
              sellers = filter(lambda x: x.name.id == partner_id, product.seller_ids)
              if sellers:
                  for s in sellers:
                  # on a database with thousands of matching products, due to the huge merge+unique needed for the
                  # OR operator (and given the fact that the 'name' lookup results come from the ir.translation table
                  # Performing a quick memory merge of ids in Python will give much better performance
 -                ids = set()
 -                ids.update(self.search(cr, user, args + [('default_code',operator,name)], limit=limit, context=context))
 +                ids = set(self.search(cr, user, args + [('default_code', operator, name)], limit=limit, context=context))
                  if not limit or len(ids) < limit:
                      # we may underrun the limit because of dupes in the results, that's fine
 -                    ids.update(self.search(cr, user, args + [('name',operator,name)], limit=(limit and (limit-len(ids)) or False) , context=context))
 +                    limit2 = (limit - len(ids)) if limit else False
 +                    ids.update(self.search(cr, user, args + [('name', operator, name)], limit=limit2, context=context))
                  ids = list(ids)
              if not ids:
                  ptrn = re.compile('(\[(.*?)\])')
      # Could be overrided for variants matrices prices
      #
      def price_get(self, cr, uid, ids, ptype='list_price', context=None):
 +        products = self.browse(cr, uid, ids, context=context)
 +        return self._price_get(cr, uid, products, ptype=ptype, context=context)
 +
 +    def _price_get(self, cr, uid, products, ptype='list_price', context=None):
          if context is None:
              context = {}
  
              price_type_currency_id = pricetype_obj.browse(cr,uid,price_type_id).currency_id.id
  
          res = {}
++        # standard_price field can only be seen by users in base.group_user
++        # Thus, in order to compute the sale price from the cost price for users not in this group
++        # We fetch the standard price as the superuser
++        for product in products:
++            if ptype != 'standard_price':
++                res[product.id] = product[ptype] or 0.0
++            else: 
++                res[product.id] = self.read(cr, SUPERUSER_ID, product.id, [ptype], context=context)[ptype] or 0.0
++
          product_uom_obj = self.pool.get('product.uom')
 -        for product in self.browse(cr, SUPERUSER_ID, ids, context=context):
 -            res[product.id] = product[ptype] or 0.0
 +        for product in products:
-             res[product.id] = product[ptype] or 0.0
              if ptype == 'list_price':
                  res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \
                          product.price_extra
          return res
  
      def copy(self, cr, uid, id, default=None, context=None):
 -        if context is None:
 -            context={}
 -
 -        if not default:
 -            default = {}
 +        context = context or {}
 +        default = dict(default or {})
  
          # Craft our own `<name> (copy)` in en_US (self.copy_translation()
          # will do the other languages).
 -        context_wo_lang = context.copy()
 +        context_wo_lang = dict(context or {})
          context_wo_lang.pop('lang', None)
 -        product = self.read(cr, uid, id, ['name'], context=context_wo_lang)
 -        default = default.copy()
 -        default.update(name=_("%s (copy)") % (product['name']))
 -
 -        if context.get('variant',False):
 -            fields = ['product_tmpl_id', 'active', 'variants', 'default_code',
 -                    'price_margin', 'price_extra']
 -            data = self.read(cr, uid, id, fields=fields, context=context)
 -            for f in fields:
 -                if f in default:
 -                    data[f] = default[f]
 -            data['product_tmpl_id'] = data.get('product_tmpl_id', False) \
 -                    and data['product_tmpl_id'][0]
 -            del data['id']
 -            return self.create(cr, uid, data)
 -        else:
 -            return super(product_product, self).copy(cr, uid, id, default=default,
 -                    context=context)
 +        product = self.browse(cr, uid, id, context_wo_lang)
 +        if context.get('variant'):
 +            # if we copy a variant or create one, we keep the same template
 +            default['product_tmpl_id'] = product.product_tmpl_id.id
 +        elif 'name' not in default:
 +            default['name'] = _("%s (copy)") % (product.name,)
 +
 +        return super(product_product, self).copy(cr, uid, id, default=default, context=context)
  
      def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
          if context is None:
              context = {}
 -        if context and context.get('search_default_categ_id', False):
 +        if context.get('search_default_categ_id'):
              args.append((('categ_id', 'child_of', context['search_default_categ_id'])))
          return super(product_product, self).search(cr, uid, args, offset=offset, limit=limit, order=order, context=context, count=count)
  
 -product_product()
 +    def open_product_template(self, cr, uid, ids, context=None):
 +        """ Utility method used to add an "Open Template" button in product views """
 +        product = self.browse(cr, uid, ids[0], context=context)
 +        return {'type': 'ir.actions.act_window',
 +                'res_model': 'product.template',
 +                'view_mode': 'form',
 +                'res_id': product.product_tmpl_id.id,
 +                'target': 'new'}
 +
  
  class product_packaging(osv.osv):
      _name = "product.packaging"
      _order = 'sequence'
      _columns = {
          'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of packaging."),
 -        'name' : fields.text('Description', size=64),
 +        'name' : fields.text('Description'),
          'qty' : fields.float('Quantity by Package',
              help="The total number of products you can put by pallet or box."),
          'ul' : fields.many2one('product.ul', 'Type of Package', required=True),
          'rows' : fields.integer('Number of Layers', required=True,
              help='The number of layers on a pallet or box'),
          'product_id' : fields.many2one('product.product', 'Product', select=1, ondelete='cascade', required=True),
 -        'ean' : fields.char('EAN', size=14,
 -            help="The EAN code of the package unit."),
 -        'code' : fields.char('Code', size=14,
 -            help="The code of the transport unit."),
 +        'ean' : fields.char('EAN', size=14, help="The EAN code of the package unit."),
 +        '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.'),
          'weight_ul': fields.float('Empty Package Weight'),
          return (10 - (sum % 10)) % 10
      checksum = staticmethod(checksum)
  
 -product_packaging()
  
  
  class product_supplierinfo(osv.osv):
      _description = "Information about a product supplier"
      def _calc_qty(self, cr, uid, ids, fields, arg, context=None):
          result = {}
 -        product_uom_pool = self.pool.get('product.uom')
          for supplier_info in self.browse(cr, uid, ids, context=context):
              for field in fields:
                  result[supplier_info.id] = {field:False}
  
      _columns = {
          'name' : fields.many2one('res.partner', 'Supplier', required=True,domain = [('supplier','=',True)], ondelete='cascade', help="Supplier of this product"),
 -        'product_name': fields.char('Supplier Product Name', size=128, help="This supplier's product name will be used when printing a request for quotation. Keep empty to use the internal one."),
 -        'product_code': fields.char('Supplier Product Code', size=64, help="This supplier's product code will be used when printing a request for quotation. Keep empty to use the internal one."),
 +        'product_name': fields.char('Supplier Product Name', help="This supplier's product name will be used when printing a request for quotation. Keep empty to use the internal one."),
 +        'product_code': fields.char('Supplier Product Code', help="This supplier's product code will be used when printing a request for quotation. Keep empty to use the internal one."),
          'sequence' : fields.integer('Sequence', help="Assigns the priority to the list of product supplier."),
 -        'product_uom': fields.related('product_id', 'uom_po_id', type='many2one', relation='product.uom', string="Supplier Unit of Measure", readonly="1", help="This comes from the product form."),
 +        'product_uom': fields.related('product_tmpl_id', 'uom_po_id', type='many2one', relation='product.uom', string="Supplier Unit of Measure", readonly="1", help="This comes from the product form."),
          'min_qty': fields.float('Minimal Quantity', required=True, help="The minimal quantity to purchase to this supplier, expressed in the supplier Product Unit of Measure if not empty, in the default unit of measure of the product otherwise."),
          'qty': fields.function(_calc_qty, store=True, type='float', string='Quantity', multi="qty", help="This is a quantity which is converted into Default Unit of Measure."),
 -        'product_id' : fields.many2one('product.template', 'Product', required=True, ondelete='cascade', select=True),
 +        '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 reception 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'),
          'company_id':fields.many2one('res.company','Company',select=1),
          pricelist_pool = self.pool.get('product.pricelist')
          currency_pool = self.pool.get('res.currency')
          currency_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id.id
 +        # Compute price from standard price of product
 +        product_price = product_pool.price_get(cr, uid, [product_id], 'standard_price', context=context)[product_id]
 +        product = product_pool.browse(cr, uid, product_id, context=context)
          for supplier in partner_pool.browse(cr, uid, supplier_ids, context=context):
 -            # Compute price from standard price of product
 -            price = product_pool.price_get(cr, uid, [product_id], 'standard_price', context=context)[product_id]
 -
 +            price = product_price
              # Compute price from Purchase pricelist of supplier
              pricelist_id = supplier.property_product_pricelist_purchase.id
              if pricelist_id:
                  price = currency_pool.compute(cr, uid, pricelist_pool.browse(cr, uid, pricelist_id).currency_id.id, currency_id, price)
  
              # Compute price from supplier pricelist which are in Supplier Information
 -            supplier_info_ids = self.search(cr, uid, [('name','=',supplier.id),('product_id','=',product_id)])
 +            supplier_info_ids = self.search(cr, uid, [('name','=',supplier.id),('product_tmpl_id','=',product.product_tmpl_id.id)])
              if supplier_info_ids:
                  cr.execute('SELECT * ' \
                      'FROM pricelist_partnerinfo ' \
              res[supplier.id] = price
          return res
      _order = 'sequence'
 -product_supplierinfo()
  
  
  class pricelist_partnerinfo(osv.osv):
      _name = 'pricelist.partnerinfo'
      _columns = {
 -        'name': fields.char('Description', size=64),
 +        'name': fields.char('Description'),
          'suppinfo_id': fields.many2one('product.supplierinfo', 'Partner Information', required=True, ondelete='cascade'),
          'min_quantity': fields.float('Quantity', required=True, help="The minimal quantity to trigger this rule, expressed in the supplier Unit of Measure if any or in the default Unit of Measure of the product otherrwise."),
          'price': fields.float('Unit Price', required=True, digits_compute=dp.get_precision('Product Price'), help="This price will be considered as a price for the supplier Unit of Measure if any or the default Unit of Measure of the product otherwise"),
      }
      _order = 'min_quantity asc'
 -pricelist_partnerinfo()
  
  class res_currency(osv.osv):
      _inherit = 'res.currency'
@@@ -23,10 -23,19 +23,10 @@@ from datetime import datetime, timedelt
  from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP, float_compare
  from dateutil.relativedelta import relativedelta
  from openerp.osv import fields, osv
 -from openerp import netsvc
  from openerp.tools.translate import _
  import pytz
  from openerp import SUPERUSER_ID
  
 -class sale_shop(osv.osv):
 -    _inherit = "sale.shop"
 -    _columns = {
 -        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
 -    }
 -
 -sale_shop()
 -
  class sale_order(osv.osv):
      _inherit = "sale.order"
      
                  vals.update({'invoice_quantity': 'order'})
              if vals['order_policy'] == 'picking':
                  vals.update({'invoice_quantity': 'procurement'})
 -        order =  super(sale_order, self).create(cr, uid, vals, context=context)
 +        order = super(sale_order, self).create(cr, uid, vals, context=context)
          return order
  
 +    def _get_default_warehouse(self, cr, uid, context=None):
 +        company_id = self.pool.get('res.users')._get_company(cr, uid, context=context)
 +        warehouse_ids = self.pool.get('stock.warehouse').search(cr, uid, [('company_id', '=', company_id)], context=context)
 +        if not warehouse_ids:
 +            return False
 +        return warehouse_ids[0]
 +
      # This is False
      def _picked_rate(self, cr, uid, ids, name, arg, context=None):
          if not ids:
          'picking_ids': fields.one2many('stock.picking.out', 'sale_id', 'Related Picking', readonly=True, help="This is a list of delivery orders that has been generated for this sales order."),
          'shipped': fields.boolean('Delivered', readonly=True, help="It indicates that the sales order has been delivered. This field is updated only after the scheduler(s) have been launched."),
          'picked_rate': fields.function(_picked_rate, string='Picked', type='float'),
 +        'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', required=True),
          'invoice_quantity': fields.selection([('order', 'Ordered Quantities'), ('procurement', 'Shipped Quantities')], 'Invoice on', 
                                               help="The sales order will automatically create the invoice proposition (draft invoice).\
                                                You have to choose  if you want your invoice based on ordered ", required=True, readonly=True, states={'draft': [('readonly', False)]}),
      }
      _defaults = {
 +             'warehouse_id': _get_default_warehouse,
               'picking_policy': 'direct',
               'order_policy': 'manual',
               'invoice_quantity': 'order',
  
          return osv.osv.unlink(self, cr, uid, unlink_ids, context=context)
  
 +    def onchange_warehouse_id(self, cr, uid, ids, warehouse_id, context=None):
 +        val = {}
 +        if warehouse_id:
 +            warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
 +            if warehouse.company_id:
 +                val['company_id'] = warehouse.company_id.id
 +        return {'value': val}
 +
      def action_view_delivery(self, cr, uid, ids, context=None):
          '''
          This function returns an action that display existing delivery orders of given sales order ids. It can either be a in a list or in a form view, if there is only one delivery order to show.
          return res
  
      def action_cancel(self, cr, uid, ids, context=None):
 -        wf_service = netsvc.LocalService("workflow")
          if context is None:
              context = {}
          sale_order_line_obj = self.pool.get('sale.order.line')
          proc_obj = self.pool.get('procurement.order')
 +        stock_obj = self.pool.get('stock.picking')
          for sale in self.browse(cr, uid, ids, context=context):
              for pick in sale.picking_ids:
                  if pick.state not in ('draft', 'cancel'):
                      for mov in pick.move_lines:
                          proc_ids = proc_obj.search(cr, uid, [('move_id', '=', mov.id)])
                          if proc_ids:
 -                            for proc in proc_ids:
 -                                wf_service.trg_validate(uid, 'procurement.order', proc, 'button_check', cr)
 +                            proc_obj.signal_button_check(cr, uid, proc_ids)            
              for r in self.read(cr, uid, ids, ['picking_ids']):
 -                for pick in r['picking_ids']:
 -                    wf_service.trg_validate(uid, 'stock.picking', pick, 'button_cancel', cr)
 +                stock_obj.signal_button_cancel(cr, uid, r['picking_ids'])
          return super(sale_order, self).action_cancel(cr, uid, ids, context=context)
  
      def action_wait(self, cr, uid, ids, context=None):
                      or line.product_uom_qty,
              'product_uos': (line.product_uos and line.product_uos.id)\
                      or line.product_uom.id,
 -            'location_id': order.shop_id.warehouse_id.lot_stock_id.id,
 +            'location_id': order.warehouse_id.lot_stock_id.id,
              'procure_method': line.type,
              'move_id': move_id,
              'company_id': order.company_id.id,
          }
  
      def _prepare_order_line_move(self, cr, uid, order, line, picking_id, date_planned, context=None):
 -        location_id = order.shop_id.warehouse_id.lot_stock_id.id
 -        output_id = order.shop_id.warehouse_id.lot_output_id.id
 +        location_id = order.warehouse_id.lot_stock_id.id
 +        output_id = order.warehouse_id.lot_output_id.id
          return {
              'name': line.name,
              'picking_id': picking_id,
          }
  
      def ship_recreate(self, cr, uid, order, line, move_id, proc_id):
-         # FIXME: deals with potentially cancelled shipments, seems broken (specially if shipment has production lot)
          """
          Define ship_recreate for process after shipping exception
          param order: sales order to which the order lines belong
          param proc_id: the ID of procurement
          """
          move_obj = self.pool.get('stock.move')
-         if order.state == 'shipping_except':
-             for pick in order.picking_ids:
-                 for move in pick.move_lines:
-                     if move.state == 'cancel':
-                         mov_ids = move_obj.search(cr, uid, [('state', '=', 'cancel'),('sale_line_id', '=', line.id),('picking_id', '=', pick.id)])
-                         if mov_ids:
-                             for mov in move_obj.browse(cr, uid, mov_ids):
-                                 # FIXME: the following seems broken: what if move_id doesn't exist? What if there are several mov_ids? Shouldn't that be a sum?
-                                 move_obj.write(cr, uid, [move_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
-                                 self.pool.get('procurement.order').write(cr, uid, [proc_id], {'product_qty': mov.product_qty, 'product_uos_qty': mov.product_uos_qty})
+         proc_obj = self.pool.get('procurement.order')
+         if move_id and order.state == 'shipping_except':
+             current_move = move_obj.browse(cr, uid, move_id)
+             moves = []
+             for picking in order.picking_ids:
+                 if picking.id != current_move.picking_id.id and picking.state != 'cancel':
+                     moves.extend(move for move in picking.move_lines if move.state != 'cancel' and move.sale_line_id.id == line.id)
+             if moves:
+                 product_qty = current_move.product_qty
+                 product_uos_qty = current_move.product_uos_qty
+                 for move in moves:
+                     product_qty -= move.product_qty
+                     product_uos_qty -= move.product_uos_qty
+                 if product_qty > 0 or product_uos_qty > 0:
+                     move_obj.write(cr, uid, [move_id], {'product_qty': product_qty, 'product_uos_qty': product_uos_qty})
+                     proc_obj.write(cr, uid, [proc_id], {'product_qty': product_qty, 'product_uos_qty': product_uos_qty})
+                 else:
+                     current_move.unlink()
+                     proc_obj.unlink(cr, uid, [proc_id])
          return True
  
      def _get_date_planned(self, cr, uid, order, line, start_date, context=None):
                  line.write({'procurement_id': proc_id})
                  self.ship_recreate(cr, uid, order, line, move_id, proc_id)
  
 -        wf_service = netsvc.LocalService("workflow")
          if picking_id:
 -            wf_service.trg_validate(uid, 'stock.picking', picking_id, 'button_confirm', cr)
 -        for proc_id in proc_ids:
 -            wf_service.trg_validate(uid, 'procurement.order', proc_id, 'button_confirm', cr)
 +            picking_obj.signal_button_confirm(cr, uid, [picking_id])
 +        procurement_obj.signal_button_confirm(cr, uid, proc_ids)
  
          val = {}
          if order.state == 'shipping_except':
              action="product.product_category_action_form" id="menu_product_category_config_stock"
              parent="stock.menu_product_in_config_stock" sequence="0"/>
          <menuitem
 +            action="product.product_variant_action" id="menu_product_variant_config_stock"
 +            parent="stock.menu_product_in_config_stock" groups="product.group_product_variant" sequence="2"/>
 +        <menuitem
 +            action="product.product_template_action" id="menu_product_template_config_stock"
 +            parent="stock.menu_product_in_config_stock" groups="product.group_product_variant" sequence="1"/>
 +        <menuitem
              action="product.product_ul_form_action" groups="product.group_stock_packaging"
 -            id="menu_product_packaging_stock_action" parent="stock.menu_product_in_config_stock" sequence="1"/>
 +            id="menu_product_packaging_stock_action" parent="stock.menu_product_in_config_stock" sequence="3"/>
          <menuitem
              id="menu_stock_unit_measure_stock" name="Units of Measure"
              parent="stock.menu_product_in_config_stock"  sequence="35" groups="product.group_uom"/>
@@@ -87,7 -81,7 +87,7 @@@
                      <field name="company_id" groups="base.group_multi_company"/>
                      <group expand="0" string="Group By...">
                          <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
 -                        <filter string="Date" icon="terp-go-month" domain="[]" context="{'group_by':'date'}"/>
 +                        <filter string="Inventories Month" icon="terp-go-month" domain="[]" context="{'group_by':'date'}" help="Physical Inventories by Month"/>
                      </group>
                  </search>
  
                  </tree>
              </field>
          </record>
 +        <record model="ir.actions.act_window" id="action_product_location_tree">
 +            <field name="context">{'product_id': active_id}</field>
 +            <field name="name">Stock by Location</field>
 +            <field name="res_model">stock.location</field>
 +        </record>
 +        <record id="act_product_stock_move_open" model="ir.actions.act_window">
 +            <field name="context">{'search_default_done': 1,'search_default_product_id': active_id, 'default_product_id': active_id}</field>
 +            <field name="name">Moves</field>
 +            <field name="res_model">stock.move</field>
 +        </record>
          <record id="action_location_tree" model="ir.actions.act_window">
              <field name="name">Location Structure</field>
              <field name="res_model">stock.location</field>
                      <button name="action_assign" states="confirmed" string="Check Availability" type="object" class="oe_highlight"/>
                      <button name="force_assign" states="confirmed" string="Force Availability" type="object" class="oe_highlight" groups="base.group_user"/>
                      <button name="action_process" states="assigned" string="Confirm &amp; Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
 -                    <button name="%(action_stock_invoice_onshipping)d" string="Create Invoice/Refund"  attrs="{'invisible': ['|','|',('state','&lt;&gt;','done'),('invoice_state','=','invoiced'),('invoice_state','=','none')]}"  type="action" class="oe_highlight" groups="base.group_user"/>
 +                    <button name="%(action_stock_invoice_onshipping)d" string="Create Invoice/Refund"  attrs="{'invisible': ['|','|',('state','&lt;&gt;','done'),('invoice_state','=','invoiced'),('invoice_state','=','none')]}" context="{'group_field_invisible': True}" type="action" class="oe_highlight" groups="base.group_user"/>
                      <button name="%(act_stock_return_picking)d" string="Reverse Transfer" states="done" type="action" groups="base.group_user"/>
                      <button name="button_cancel" states="assigned,confirmed,draft" string="Cancel Transfer" groups="base.group_user"/>
                      <field name="state" widget="statusbar" statusbar_visible="draft,assigned,done" statusbar_colors='{"shipping_except":"red","invoice_except":"red","waiting_date":"blue"}'/>
                      <field name="company_id" groups="base.group_multi_company"/>
                      <group expand="0" string="Group By...">
                          <filter string="Status" icon="terp-stock_effects-object-colorize" domain="[]" context="{'group_by':'state'}"/>
 -                        <filter string="Order Date" icon="terp-go-month" domain="[]"  context="{'group_by':'date'}"/>
 -                        <filter string="Expected Date" icon="terp-go-month" domain="[]"  context="{'group_by':'min_date'}"/>
 +                        <filter string="Order Month" icon="terp-go-month" domain="[]"  context="{'group_by':'date'}"/>
 +                        <filter string="Expected Month" icon="terp-go-month" domain="[]"  context="{'group_by':'min_date'}"/>
                          <filter string="Journal" icon="terp-folder-orange" domain="[]" context="{'group_by':'stock_journal_id'}"/>
                      </group>
                  </search>
                      <field name="product_id"/>
                      <group expand="0" string="Group By...">
                          <filter icon="terp-stock_effects-object-colorize" name="state" string="Status" domain="[]" context="{'group_by':'state'}"/>
 -                        <filter string="Order Date" icon="terp-go-month" domain="[]"  context="{'group_by':'date'}"/>
 -                        <filter string="Expected Date" icon="terp-go-month" domain="[]"  context="{'group_by':'min_date'}"/>
 +                        <filter string="Order Month" icon="terp-go-month" domain="[]"  context="{'group_by':'date'}"/>
 +                        <filter string="Expected Month" icon="terp-go-month" domain="[]"  context="{'group_by':'min_date'}"/>
                          <filter string="Journal" icon="terp-folder-orange" domain="[]" context="{'group_by':'stock_journal_id'}"/>
                      </group>
                  </search>
                              <field name="date" attrs="{'invisible': [('state', '!=', 'done')]}"/>
                          </group>
                          <group string="Traceability"
-                             groups="stock.group_tracking_lot">
+                             groups="stock.group_tracking_lot,stock.group_production_lot">
                              <label for="tracking_id" groups="stock.group_tracking_lot"/>
                              <div groups="stock.group_tracking_lot">
                                  <field name="tracking_id" class="oe_inline"/>
              res_model="product.product"
              src_model="stock.location"/>
  
 -        <act_window
 -            context="{'search_default_done': 1,'search_default_product_id': [active_id], 'default_product_id': active_id}"
 -            id="act_product_stock_move_open"
 -            name="Inventory Move"
 -            res_model="stock.move"
 -            src_model="product.product"/>
 -
 -        <act_window
 -            context="{'search_default_future': 1,'search_default_product_id': [active_id], 'default_product_id': active_id}"
 -            domain="[('state','in',('waiting','confirmed','assigned'))]"
 -            id="act_product_stock_move_futur_open"
 -            name="Future Stock Moves"
 -            res_model="stock.move"
 -            src_model="product.product"/>
 -
          <record id="ir_act_product_location_open" model="ir.values">
              <field name="key2">tree_but_open</field>
              <field name="model">stock.location</field>
                  <form string="Stock Journal" version="7.0">
                      <group>
                          <field name="name"/>
 -                        <field name="user_id"/>
 +                        <field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice', 'stock.group_stock_manager']}"/>
                      </group>
                  </form>
              </field>