[MERGE] lp881356
authorQuentin (OpenERP) <qdp-launchpad@openerp.com>
Wed, 15 Feb 2012 17:16:25 +0000 (18:16 +0100)
committerQuentin (OpenERP) <qdp-launchpad@openerp.com>
Wed, 15 Feb 2012 17:16:25 +0000 (18:16 +0100)
lp bug: https://launchpad.net/bugs/881356 fixed

bzr revid: qdp-launchpad@openerp.com-20120215171625-i3zf4nhhjuz82bob

1  2 
addons/sale/sale.py
addons/stock/stock.py

diff --combined addons/sale/sale.py
@@@ -25,7 -25,7 +25,7 @@@ import tim
  import pooler
  from osv import fields, osv
  from tools.translate import _
- from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
+ from tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, float_compare
  import decimal_precision as dp
  import netsvc
  
@@@ -206,7 -206,7 +206,7 @@@ class sale_order(osv.osv)
              ('invoice_except', 'Invoice Exception'),
              ('done', 'Done'),
              ('cancel', 'Cancelled')
 -            ], 'Order State', readonly=True, help="Givwizard = self.browse(cr, uid, ids)[0]es the state of the quotation or sales order. \nThe exception state is automatically set when a cancel operation occurs in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception). \nThe 'Waiting Schedule' state is set when the invoice is confirmed but waiting for the scheduler to run on the order date.", select=True),
 +            ], 'Order State', readonly=True, help="Gives the state of the quotation or sales order. \nThe exception state is automatically set when a cancel operation occurs in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception). \nThe 'Waiting Schedule' state is set when the invoice is confirmed but waiting for the scheduler to run on the order date.", select=True),
          'date_order': fields.date('Date', required=True, readonly=True, select=True, states={'draft': [('readonly', False)]}),
          'create_date': fields.datetime('Creation Date', readonly=True, select=True, help="Date on which sales order is created."),
          'date_confirm': fields.date('Confirmation Date', readonly=True, select=True, help="Date on which sales order is confirmed."),
      }
      _defaults = {
          'picking_policy': 'direct',
 -        'date_order': lambda *a: time.strftime(DEFAULT_SERVER_DATE_FORMAT),
 +        'date_order': fields.date.context_today,
          'order_policy': 'manual',
          'state': 'draft',
          'user_id': lambda obj, cr, uid, context: uid,
          self.write(cr, uid, ids, {'state': 'cancel'})
          return True
  
 -    def action_wait(self, cr, uid, ids, *args):
 +    def action_wait(self, cr, uid, ids, context=None):
          for o in self.browse(cr, uid, ids):
              if not o.order_line:
                  raise osv.except_osv(_('Error !'),_('You cannot confirm a sale order which has no line.'))
              if (o.order_policy == 'manual'):
 -                self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': time.strftime(DEFAULT_SERVER_DATE_FORMAT)})
 +                self.write(cr, uid, [o.id], {'state': 'manual', 'date_confirm': fields.date.context_today(self, cr, uid, context=context)})
              else:
 -                self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': time.strftime(DEFAULT_SERVER_DATE_FORMAT)})
 +                self.write(cr, uid, [o.id], {'state': 'progress', 'date_confirm': fields.date.context_today(self, cr, uid, context=context)})
              self.pool.get('sale.order.line').button_confirm(cr, uid, [x.id for x in o.order_line])
              message = _("The quotation '%s' has been converted to a sales order.") % (o.name,)
              self.log(cr, uid, o.id, message)
@@@ -956,7 -956,7 +956,7 @@@ class sale_order_line(osv.osv)
          'number_packages': fields.function(_number_packages, type='integer', string='Number Packages'),
          'notes': fields.text('Notes'),
          'th_weight': fields.float('Weight', readonly=True, states={'draft': [('readonly', False)]}),
 -        'state': fields.selection([('draft', 'Draft'),('confirmed', 'Confirmed'),('done', 'Done'),('cancel', 'Cancelled'),('exception', 'Exception')], 'State', required=True, readonly=True,
 +        'state': fields.selection([('cancel', 'Cancelled'),('draft', 'Draft'),('confirmed', 'Confirmed'),('exception', 'Exception'),('done', 'Done')], 'State', required=True, readonly=True,
                  help='* The \'Draft\' state is set when the related sales order in draft state. \
                      \n* The \'Confirmed\' state is set when the related sales order is confirmed. \
                      \n* The \'Exception\' state is set when the related sales order is set as exception. \
          'price_unit': 0.0,
      }
  
 -    def invoice_line_create(self, cr, uid, ids, context=None):
 -        if context is None:
 -            context = {}
 +    def _prepare_order_line_invoice_line(self, cr, uid, ids, line, account_id=False, context=None):
 +        """ Builds the invoice line dict from a sale order line
 +            @param line: sale order line object
 +            @param account_id: the id of the account to force eventually (the method is used for picking return including service)
 +            @return: dict that will be used to create the invoice line
 +        """
  
          def _get_line_qty(line):
              if (line.order_id.invoice_quantity=='order') or not line.procurement_id:
                  return self.pool.get('procurement.order').uom_get(cr, uid,
                          line.procurement_id.id, context=context)
  
 -        create_ids = []
 -        sales = {}
 -        for line in self.browse(cr, uid, ids, context=context):
 -            if not line.invoiced:
 +        if not line.invoiced:
 +            if not account_id:
                  if line.product_id:
 -                    a = line.product_id.product_tmpl_id.property_account_income.id
 -                    if not a:
 -                        a = line.product_id.categ_id.property_account_income_categ.id
 -                    if not a:
 +                    account_id = line.product_id.product_tmpl_id.property_account_income.id
 +                    if not account_id:
 +                        account_id = line.product_id.categ_id.property_account_income_categ.id
 +                    if not account_id:
                          raise osv.except_osv(_('Error !'),
 -                                _('There is no income account defined ' \
 -                                        'for this product: "%s" (id:%d)') % \
 -                                        (line.product_id.name, line.product_id.id,))
 +                                _('There is no income account defined for this product: "%s" (id:%d)') % \
 +                                    (line.product_id.name, line.product_id.id,))
                  else:
                      prop = self.pool.get('ir.property').get(cr, uid,
                              'property_account_income_categ', 'product.category',
                              context=context)
 -                    a = prop and prop.id or False
 -                uosqty = _get_line_qty(line)
 -                uos_id = _get_line_uom(line)
 -                pu = 0.0
 -                if uosqty:
 -                    pu = round(line.price_unit * line.product_uom_qty / uosqty,
 -                            self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
 -                fpos = line.order_id.fiscal_position or False
 -                a = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, a)
 -                if not a:
 -                    raise osv.except_osv(_('Error !'),
 -                                _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
 -                inv_id = self.pool.get('account.invoice.line').create(cr, uid, {
 -                    'name': line.name,
 -                    'origin': line.order_id.name,
 -                    'account_id': a,
 -                    'price_unit': pu,
 -                    'quantity': uosqty,
 -                    'discount': line.discount,
 -                    'uos_id': uos_id,
 -                    'product_id': line.product_id.id or False,
 -                    'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
 -                    'note': line.notes,
 -                    'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
 -                })
 +                    account_id = prop and prop.id or False
 +            uosqty = _get_line_qty(line)
 +            uos_id = _get_line_uom(line)
 +            pu = 0.0
 +            if uosqty:
 +                pu = round(line.price_unit * line.product_uom_qty / uosqty,
 +                        self.pool.get('decimal.precision').precision_get(cr, uid, 'Sale Price'))
 +            fpos = line.order_id.fiscal_position or False
 +            account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, fpos, account_id)
 +            if not account_id:
 +                raise osv.except_osv(_('Error !'),
 +                            _('There is no income category account defined in default Properties for Product Category or Fiscal Position is not defined !'))
 +            return {
 +                'name': line.name,
 +                'origin': line.order_id.name,
 +                'account_id': account_id,
 +                'price_unit': pu,
 +                'quantity': uosqty,
 +                'discount': line.discount,
 +                'uos_id': uos_id,
 +                'product_id': line.product_id.id or False,
 +                'invoice_line_tax_id': [(6, 0, [x.id for x in line.tax_id])],
 +                'note': line.notes,
 +                'account_analytic_id': line.order_id.project_id and line.order_id.project_id.id or False,
 +            }
 +
 +        return False
 +
 +    def invoice_line_create(self, cr, uid, ids, context=None):
 +        if context is None:
 +            context = {}
 +
 +        create_ids = []
 +        sales = set()
 +        for line in self.browse(cr, uid, ids, context=context):
 +            vals = self._prepare_order_line_invoice_line(cr, uid, ids, line, False, context)
 +            if vals:
 +                inv_id = self.pool.get('account.invoice.line').create(cr, uid, vals, context=context)
                  cr.execute('insert into sale_order_line_invoice_rel (order_line_id,invoice_id) values (%s,%s)', (line.id, inv_id))
                  self.write(cr, uid, [line.id], {'invoiced': True})
 -                sales[line.order_id.id] = True
 +                sales.add(line.order_id.id)
                  create_ids.append(inv_id)
          # Trigger workflow events
          wf_service = netsvc.LocalService("workflow")
 -        for sid in sales.keys():
 -            wf_service.trg_write(uid, 'sale.order', sid, cr)
 +        for sale_id in sales:
 +            wf_service.trg_write(uid, 'sale.order', sale_id, cr)
          return create_ids
  
      def button_cancel(self, cr, uid, ids, context=None):
  
          if not uom2:
              uom2 = product_obj.uom_id
-         if (product_obj.type=='product') and (product_obj.virtual_available * uom2.factor < qty * product_obj.uom_id.factor) \
+         compare_qty = float_compare(product_obj.virtual_available * uom2.factor, qty * product_obj.uom_id.factor, precision_rounding=product_obj.uom_id.rounding)
+         if (product_obj.type=='product') and int(compare_qty) == -1 \
            and (product_obj.procure_method=='make_to_stock'):
              warn_msg = _('You plan to sell %.2f %s but you only have %.2f %s available !\nThe real stock is %.2f %s. (without reservations)') % \
                      (qty, uom2 and uom2.name or product_obj.uom_id.name,
diff --combined addons/stock/stock.py
@@@ -1,4 -1,3 +1,4 @@@
 +# -*- coding: utf-8 -*-
  ##############################################################################
  #
  #    OpenERP, Open Source Management Solution
@@@ -29,6 -28,7 +29,7 @@@ from osv import fields, os
  from tools.translate import _
  import netsvc
  import tools
+ from tools import float_compare
  import decimal_precision as dp
  import logging
  
@@@ -75,27 -75,37 +76,27 @@@ class stock_location(osv.osv)
      _order = 'parent_left'
  
      def name_get(self, cr, uid, ids, context=None):
 -        res = []
 -        if context is None:
 -            context = {}
 -        if not len(ids):
 -            return []
 -        reads = self.read(cr, uid, ids, ['name','location_id'], context=context)
 -        for record in reads:
 -            name = record['name']
 -            if context.get('full',False):
 -                if record['location_id']:
 -                    name = record['location_id'][1] + ' / ' + name
 -                res.append((record['id'], name))
 -            else:
 -                res.append((record['id'], name))
 -        return res
 +        # always return the full hierarchical name
 +        res = self._complete_name(cr, uid, ids, 'complete_name', None, context=context)
 +        return res.items()
  
      def _complete_name(self, cr, uid, ids, name, args, context=None):
          """ Forms complete name of location from parent location to child location.
          @return: Dictionary of values
          """
 -        def _get_one_full_name(location, level=4):
 -            if location.location_id:
 -                parent_path = _get_one_full_name(location.location_id, level-1) + "/"
 -            else:
 -                parent_path = ''
 -            return parent_path + location.name
          res = {}
          for m in self.browse(cr, uid, ids, context=context):
 -            res[m.id] = _get_one_full_name(m)
 +            names = [m.name]
 +            parent = m.location_id
 +            while parent:
 +                names.append(parent.name)
 +                parent = parent.location_id
 +            res[m.id] = ' / '.join(reversed(names))
          return res
  
 +    def _get_sublocations(self, cr, uid, ids, context=None):
 +        """ return all sublocations of the given stock locations (included) """
 +        return self.search(cr, uid, [('id', 'child_of', ids)], context=context)
  
      def _product_value(self, cr, uid, ids, field_names, arg, context=None):
          """Computes stock value (real and virtual) for a product, as well as stock qty (real and virtual).
                         \n* Production: Virtual counterpart location for production operations: this location consumes the raw material and produces finished products
                        """, select = True),
           # temporarily removed, as it's unused: 'allocation_method': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO'), ('nearest', 'Nearest')], 'Allocation Method', required=True),
 -        'complete_name': fields.function(_complete_name, type='char', size=256, string="Location Name", store=True),
 +        'complete_name': fields.function(_complete_name, type='char', size=256, string="Location Name",
 +                            store={'stock.location': (_get_sublocations, ['name', 'location_id'], 10)}),
  
          'stock_real': fields.function(_product_value, type='float', string='Real Stock', multi="stock"),
          'stock_virtual': fields.function(_product_value, type='float', string='Virtual Stock', multi="stock"),
          :param lock: if True, the stock.move lines of product with id ``product_id`` in all locations (and children locations) with ``ids`` will
                       be write-locked using postgres's "FOR UPDATE NOWAIT" option until the transaction is committed or rolled back. This is
                       to prevent reserving twice the same products.
-         :param context: optional context dictionary: it a 'uom' key is present it will be used instead of the default product uom to
+         :param context: optional context dictionary: if a 'uom' key is present it will be used instead of the default product uom to
                          compute the ``product_qty`` and in the return value.
          :return: List of tuples in the form (qty, location_id) with the (partial) quantities that can be taken in each location to
                   reach the requested product_qty (``qty`` is expressed in the default uom of the product), of False if enough
          amount = 0.0
          if context is None:
              context = {}
+         uom_obj = self.pool.get('product.uom')
+         uom_rounding = self.pool.get('product.product').browse(cr, uid, product_id, context=context).uom_id.rounding
+         if context.get('uom'):
+             uom_rounding = uom_obj.browse(cr, uid, context.get('uom'), context=context).rounding
          for id in self.search(cr, uid, [('location_id', 'child_of', ids)]):
              if lock:
                  try:
                         """,
                         (id, id, product_id))
              results += cr.dictfetchall()
              total = 0.0
              results2 = 0.0
              for r in results:
-                 amount = self.pool.get('product.uom')._compute_qty(cr, uid, r['product_uom'], r['product_qty'], context.get('uom', False))
+                 amount = uom_obj._compute_qty(cr, uid, r['product_uom'], r['product_qty'], context.get('uom', False))
                  results2 += amount
                  total += amount
 -
              if total <= 0.0:
                  continue
  
              amount = results2
-             if amount > 0:
+             compare_qty = float_compare(amount, 0, precision_rounding=uom_rounding)
+             if compare_qty == 1:
                  if amount > min(total, product_qty):
                      amount = min(product_qty, total)
                  result.append((amount, id))
@@@ -521,8 -537,9 +527,8 @@@ class stock_tracking(osv.osv)
          @param context: A standard dictionary
          @return: A dictionary of values
          """
 -        value={}
 -        value=self.pool.get('action.traceability').action_traceability(cr,uid,ids,context)
 -        return value
 +        return self.pool.get('action.traceability').action_traceability(cr,uid,ids,context)
 +
  stock_tracking()
  
  #----------------------------------------------------------
@@@ -811,7 -828,6 +817,7 @@@ class stock_picking(osv.osv)
          """ Tests whether the move is in assigned state or not.
          @return: True or False
          """
 +        #TOFIX: assignment of move lines should be call before testing assigment otherwise picking never gone in assign state
          ok = True
          for pick in self.browse(cr, uid, ids):
              mt = pick.move_type
      def get_currency_id(self, cr, uid, picking):
          return False
  
 -    def _get_payment_term(self, cr, uid, picking):
 -        """ Gets payment term from partner.
 -        @return: Payment term
 +    def _get_partner_to_invoice(self, cr, uid, picking, context=None):
 +        """ Gets the partner that will be invoiced
 +            Note that this function is inherited in the sale module
 +            @param picking: object of the picking for which we are selecting the partner to invoice
 +            @return: object of the partner to invoice
          """
 -        partner = picking.address_id.partner_id
 -        return partner.property_payment_term and partner.property_payment_term.id or False
 -
 -    def _get_address_invoice(self, cr, uid, picking):
 -        """ Gets invoice address of a partner
 -        @return {'contact': address, 'invoice': address} for invoice
 -        """
 -        partner_obj = self.pool.get('res.partner')
 -        partner = picking.address_id.partner_id
 -        return partner_obj.address_get(cr, uid, [partner.id],
 -                ['contact', 'invoice'])
 +        return picking.address_id and picking.address_id.partner_id
  
      def _get_comment_invoice(self, cr, uid, picking):
          """
                  inv_type = 'out_invoice'
          return inv_type
  
 +    def _prepare_invoice_group(self, cr, uid, picking, partner, invoice, context=None):
 +        """ Builds the dict for grouped invoices
 +            @param picking: picking object
 +            @param partner: object of the partner to invoice (not used here, but may be usefull if this function is inherited)
 +            @param invoice: object of the invoice that we are updating
 +            @return: dict that will be used to update the invoice
 +        """
 +        comment = self._get_comment_invoice(cr, uid, picking)
 +        return {
 +            'name': (invoice.name or '') + ', ' + (picking.name or ''),
 +            'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
 +            'comment': (comment and (invoice.comment and invoice.comment + "\n" + comment or comment)) or (invoice.comment and invoice.comment or ''),
 +            'date_invoice': context.get('date_inv', False),
 +            'user_id': uid,
 +        }
 +
 +    def _prepare_invoice(self, cr, uid, picking, partner, inv_type, journal_id, context=None):
 +        """ Builds the dict containing the values for the invoice
 +            @param picking: picking object
 +            @param partner: object of the partner to invoice
 +            @param inv_type: type of the invoice ('out_invoice', 'in_invoice', ...)
 +            @param journal_id: ID of the accounting journal
 +            @return: dict that will be used to create the invoice object
 +        """
 +        if inv_type in ('out_invoice', 'out_refund'):
 +            account_id = partner.property_account_receivable.id
 +        else:
 +            account_id = partner.property_account_payable.id
 +        address_contact_id, address_invoice_id = \
 +                self.pool.get('res.partner').address_get(cr, uid, [partner.id],
 +                        ['contact', 'invoice']).values()
 +        comment = self._get_comment_invoice(cr, uid, picking)
 +        invoice_vals = {
 +            'name': picking.name,
 +            'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
 +            'type': inv_type,
 +            'account_id': account_id,
 +            'partner_id': partner.id,
 +            'address_invoice_id': address_invoice_id,
 +            'address_contact_id': address_contact_id,
 +            'comment': comment,
 +            'payment_term': partner.property_payment_term and partner.property_payment_term.id or False,
 +            'fiscal_position': partner.property_account_position.id,
 +            'date_invoice': context.get('date_inv', False),
 +            'company_id': picking.company_id.id,
 +            'user_id': uid,
 +        }
 +        cur_id = self.get_currency_id(cr, uid, picking)
 +        if cur_id:
 +            invoice_vals['currency_id'] = cur_id
 +        if journal_id:
 +            invoice_vals['journal_id'] = journal_id
 +        return invoice_vals
 +
 +    def _prepare_invoice_line(self, cr, uid, group, picking, move_line, invoice_id,
 +        invoice_vals, context=None):
 +        """ Builds the dict containing the values for the invoice line
 +            @param group: True or False
 +            @param picking: picking object
 +            @param: move_line: move_line object
 +            @param: invoice_id: ID of the related invoice
 +            @param: invoice_vals: dict used to created the invoice
 +            @return: dict that will be used to create the invoice line
 +        """
 +        if group:
 +            name = (picking.name or '') + '-' + move_line.name
 +        else:
 +            name = move_line.name
 +        origin = move_line.picking_id.name or ''
 +        if move_line.picking_id.origin:
 +            origin += ':' + move_line.picking_id.origin
 +
 +        if invoice_vals['type'] in ('out_invoice', 'out_refund'):
 +            account_id = move_line.product_id.product_tmpl_id.\
 +                    property_account_income.id
 +            if not account_id:
 +                account_id = move_line.product_id.categ_id.\
 +                        property_account_income_categ.id
 +        else:
 +            account_id = move_line.product_id.product_tmpl_id.\
 +                    property_account_expense.id
 +            if not account_id:
 +                account_id = move_line.product_id.categ_id.\
 +                        property_account_expense_categ.id
 +        if invoice_vals['fiscal_position']:
 +            fp_obj = self.pool.get('account.fiscal.position')
 +            fiscal_position = fp_obj.browse(cr, uid, invoice_vals['fiscal_position'], context=context)
 +            account_id = fp_obj.map_account(cr, uid, fiscal_position, account_id)
 +        # set UoS if it's a sale and the picking doesn't have one
 +        uos_id = move_line.product_uos and move_line.product_uos.id or False
 +        if not uos_id and invoice_vals['type'] in ('out_invoice', 'out_refund'):
 +            uos_id = move_line.product_uom.id
 +
 +        return {
 +            'name': name,
 +            'origin': origin,
 +            'invoice_id': invoice_id,
 +            'uos_id': uos_id,
 +            'product_id': move_line.product_id.id,
 +            'account_id': account_id,
 +            'price_unit': self._get_price_unit_invoice(cr, uid, move_line, invoice_vals['type']),
 +            'discount': self._get_discount_invoice(cr, uid, move_line),
 +            'quantity': move_line.product_uos_qty or move_line.product_qty,
 +            'invoice_line_tax_id': [(6, 0, self._get_taxes_invoice(cr, uid, move_line, invoice_vals['type']))],
 +            'account_analytic_id': self._get_account_analytic_invoice(cr, uid, picking, move_line),
 +        }
 +
      def action_invoice_create(self, cr, uid, ids, journal_id=False,
              group=False, type='out_invoice', context=None):
          """ Creates invoice based on the invoice state selected for picking.
  
          invoice_obj = self.pool.get('account.invoice')
          invoice_line_obj = self.pool.get('account.invoice.line')
 -        address_obj = self.pool.get('res.partner.address')
          invoices_group = {}
          res = {}
          inv_type = type
          for picking in self.browse(cr, uid, ids, context=context):
              if picking.invoice_state != '2binvoiced':
                  continue
 -            partner =  picking.address_id and picking.address_id.partner_id
 +            partner = self._get_partner_to_invoice(cr, uid, picking, context=context)
              if not partner:
                  raise osv.except_osv(_('Error, no partner !'),
                      _('Please put a partner on the picking list if you want to generate invoice.'))
              if not inv_type:
                  inv_type = self._get_invoice_type(picking)
  
              if group and partner.id in invoices_group:
                  invoice_id = invoices_group[partner.id]
                  invoice = invoice_obj.browse(cr, uid, invoice_id)
 -                invoice_vals = {
 -                    'name': (invoice.name or '') + ', ' + (picking.name or ''),
 -                    'origin': (invoice.origin or '') + ', ' + (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
 -                    'comment': (comment and (invoice.comment and invoice.comment+"\n"+comment or comment)) or (invoice.comment and invoice.comment or ''),
 -                    'date_invoice':context.get('date_inv',False),
 -                    'user_id':uid
 -                }
 -                invoice_obj.write(cr, uid, [invoice_id], invoice_vals, context=context)
 +                invoice_vals_group = self._prepare_invoice_group(cr, uid, picking, partner, invoice, context=context)
 +                invoice_obj.write(cr, uid, [invoice_id], invoice_vals_group, context=context)
              else:
 -                invoice_vals = {
 -                    'name': picking.name,
 -                    'origin': (picking.name or '') + (picking.origin and (':' + picking.origin) or ''),
 -                    'type': inv_type,
 -                    'account_id': account_id,
 -                    'partner_id': address.partner_id.id,
 -                    'address_invoice_id': address_invoice_id,
 -                    'address_contact_id': address_contact_id,
 -                    'comment': comment,
 -                    'payment_term': self._get_payment_term(cr, uid, picking),
 -                    'fiscal_position': partner.property_account_position.id,
 -                    'date_invoice': context.get('date_inv',False),
 -                    'company_id': picking.company_id.id,
 -                    'user_id':uid
 -                }
 -                cur_id = self.get_currency_id(cr, uid, picking)
 -                if cur_id:
 -                    invoice_vals['currency_id'] = cur_id
 -                if journal_id:
 -                    invoice_vals['journal_id'] = journal_id
 -                invoice_id = invoice_obj.create(cr, uid, invoice_vals,
 -                        context=context)
 +                invoice_vals = self._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
 +                invoice_id = invoice_obj.create(cr, uid, invoice_vals, context=context)
                  invoices_group[partner.id] = invoice_id
              res[picking.id] = invoice_id
              for move_line in picking.move_lines:
                  if move_line.state == 'cancel':
                      continue
 -                origin = move_line.picking_id.name or ''
 -                if move_line.picking_id.origin:
 -                    origin += ':' + move_line.picking_id.origin
 -                if group:
 -                    name = (picking.name or '') + '-' + move_line.name
 -                else:
 -                    name = move_line.name
 -
 -                if inv_type in ('out_invoice', 'out_refund'):
 -                    account_id = move_line.product_id.product_tmpl_id.\
 -                            property_account_income.id
 -                    if not account_id:
 -                        account_id = move_line.product_id.categ_id.\
 -                                property_account_income_categ.id
 -                else:
 -                    account_id = move_line.product_id.product_tmpl_id.\
 -                            property_account_expense.id
 -                    if not account_id:
 -                        account_id = move_line.product_id.categ_id.\
 -                                property_account_expense_categ.id
 -
 -                price_unit = self._get_price_unit_invoice(cr, uid,
 -                        move_line, inv_type)
 -                discount = self._get_discount_invoice(cr, uid, move_line)
 -                tax_ids = self._get_taxes_invoice(cr, uid, move_line, inv_type)
 -                account_analytic_id = self._get_account_analytic_invoice(cr, uid, picking, move_line)
 -
 -                #set UoS if it's a sale and the picking doesn't have one
 -                uos_id = move_line.product_uos and move_line.product_uos.id or False
 -                if not uos_id and inv_type in ('out_invoice', 'out_refund'):
 -                    uos_id = move_line.product_uom.id
 -
 -                account_id = self.pool.get('account.fiscal.position').map_account(cr, uid, partner.property_account_position, account_id)
 -                invoice_line_id = invoice_line_obj.create(cr, uid, {
 -                    'name': name,
 -                    'origin': origin,
 -                    'invoice_id': invoice_id,
 -                    'uos_id': uos_id,
 -                    'product_id': move_line.product_id.id,
 -                    'account_id': account_id,
 -                    'price_unit': price_unit,
 -                    'discount': discount,
 -                    'quantity': move_line.product_uos_qty or move_line.product_qty,
 -                    'invoice_line_tax_id': [(6, 0, tax_ids)],
 -                    'account_analytic_id': account_analytic_id,
 -                }, context=context)
 -                self._invoice_line_hook(cr, uid, move_line, invoice_line_id)
 +                vals = self._prepare_invoice_line(cr, uid, group, picking, move_line,
 +                                invoice_id, invoice_vals, context=context)
 +                if vals:
 +                    invoice_line_id = invoice_line_obj.create(cr, uid, vals, context=context)
 +                    self._invoice_line_hook(cr, uid, move_line, invoice_line_id)
  
              invoice_obj.button_compute(cr, uid, [invoice_id], context=context,
                      set_total=(inv_type in ('in_invoice', 'in_refund')))
          for pick in self.browse(cr, uid, ids, context=context):
              new_picking = None
              complete, too_many, too_few = [], [], []
 -            move_product_qty = {}
 -            prodlot_ids = {}
 -            product_avail = {}
 +            move_product_qty, prodlot_ids, product_avail, partial_qty, product_uoms = {}, {}, {}, {}, {}
              for move in pick.move_lines:
                  if move.state in ('done', 'cancel'):
                      continue
                  product_currency = partial_data.get('product_currency',False)
                  prodlot_id = partial_data.get('prodlot_id')
                  prodlot_ids[move.id] = prodlot_id
 -                if move.product_qty == product_qty:
 +                product_uoms[move.id] = product_uom
 +                partial_qty[move.id] = uom_obj._compute_qty(cr, uid, product_uoms[move.id], product_qty, move.product_uom.id)
 +                if move.product_qty == partial_qty[move.id]:
                      complete.append(move)
 -                elif move.product_qty > product_qty:
 +                elif move.product_qty > partial_qty[move.id]:
                      too_few.append(move)
                  else:
                      too_many.append(move)
  
              for move in too_few:
                  product_qty = move_product_qty[move.id]
 -
                  if not new_picking:
                      new_picking = self.copy(cr, uid, pick.id,
                              {
                              'state': 'assigned',
                              'move_dest_id': False,
                              'price_unit': move.price_unit,
 +                            'product_uom': product_uoms[move.id]
                      }
                      prodlot_id = prodlot_ids[move.id]
                      if prodlot_id:
                          defaults.update(prodlot_id=prodlot_id)
                      move_obj.copy(cr, uid, move.id, defaults)
 -
                  move_obj.write(cr, uid, [move.id],
                          {
 -                            'product_qty' : move.product_qty - product_qty,
 -                            'product_uos_qty':move.product_qty - product_qty, #TODO: put correct uos_qty
 +                            'product_qty' : move.product_qty - partial_qty[move.id],
 +                            'product_uos_qty': move.product_qty - partial_qty[move.id], #TODO: put correct uos_qty
 +                            
                          })
  
              if new_picking:
                  move_obj.write(cr, uid, [c.id for c in complete], {'picking_id': new_picking})
              for move in complete:
 +                defaults = {'product_uom': product_uoms[move.id], 'product_qty': move_product_qty[move.id]}
                  if prodlot_ids.get(move.id):
 -                    move_obj.write(cr, uid, [move.id], {'prodlot_id': prodlot_ids[move.id]})
 +                    defaults.update({'prodlot_id': prodlot_ids[move.id]})
 +                move_obj.write(cr, uid, [move.id], defaults)
              for move in too_many:
                  product_qty = move_product_qty[move.id]
                  defaults = {
                      'product_qty' : product_qty,
                      'product_uos_qty': product_qty, #TODO: put correct uos_qty
 +                    'product_uom': product_uoms[move.id]
                  }
                  prodlot_id = prodlot_ids.get(move.id)
                  if prodlot_ids.get(move.id):
                      defaults.update(picking_id=new_picking)
                  move_obj.write(cr, uid, [move.id], defaults)
  
 -
              # At first we confirm the new picking (if necessary)
              if new_picking:
                  wf_service.trg_validate(uid, 'stock.picking', new_picking, 'button_confirm', cr)
@@@ -1482,7 -1475,7 +1488,7 @@@ class stock_production_lot_revision(osv
  
      _defaults = {
          'author_id': lambda x, y, z, c: z,
 -        'date': time.strftime('%Y-%m-%d'),
 +        'date': fields.date.context_today,
      }
  
  stock_production_lot_revision()
@@@ -1712,7 -1705,7 +1718,7 @@@ class stock_move(osv.osv)
                  WHERE indexname = \'stock_move_location_id_location_dest_id_product_id_state\'')
          if not cursor.fetchone():
              cursor.execute('CREATE INDEX stock_move_location_id_location_dest_id_product_id_state \
 -                    ON stock_move (location_id, location_dest_id, product_id, state)')
 +                    ON stock_move (product_id, state, location_id, location_dest_id)')
          return res
  
      def onchange_lot_id(self, cr, uid, ids, prodlot_id=False, product_qty=False,
          @param context: context arguments
          @return: Scraped lines
          """
 +        #quantity should in MOVE UOM
          if quantity <= 0:
              raise osv.except_osv(_('Warning!'), _('Please provide a positive quantity to scrap!'))
          res = []
          @param context: context arguments
          @return: Consumed lines
          """
 +        #quantity should in MOVE UOM
          if context is None:
              context = {}
          if quantity <= 0:
                  quantity = move.product_qty
  
              uos_qty = quantity / move_qty * move.product_uos_qty
 -
 +            location_dest_id = move.product_id.property_stock_production or move.location_dest_id
              if quantity_rest > 0:
                  default_val = {
                      'product_qty': quantity,
                      'product_uos_qty': uos_qty,
                      'state': move.state,
                      'location_id': location_id or move.location_id.id,
 +                    'location_dest_id': location_dest_id.id,
                  }
                  current_move = self.copy(cr, uid, move.id, default_val)
                  res += [current_move]
                  update_val = {
                          'product_qty' : quantity_rest,
                          'product_uos_qty' : uos_qty_rest,
 -                        'location_id': location_id or move.location_id.id
 +                        'location_id': location_id or move.location_id.id,
 +                        'location_dest_id': location_dest_id.id,
                  }
                  self.write(cr, uid, [move.id], update_val)
  
@@@ -2625,13 -2614,6 +2631,13 @@@ class stock_inventory(osv.osv)
          'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c)
      }
  
 +    def copy(self, cr, uid, id, default=None, context=None):
 +        if default is None:
 +            default = {}
 +        default = default.copy()
 +        default.update({'move_ids': [], 'date_done': False})
 +        return super(stock_inventory, self).copy(cr, uid, id, default, context=context)
 +
      def _inventory_line_hook(self, cr, uid, inventory_line, move_vals):
          """ Creates a stock move from an inventory line
          @param inventory_line:
              message = _("Inventory '%s' is done.") %(inv.name)
              self.log(cr, uid, inv.id, message)
              self.write(cr, uid, [inv.id], {'state': 'confirm', 'move_ids': [(6, 0, move_ids)]})
 +            self.pool.get('stock.move').action_confirm(cr, uid, move_ids, context=context)
          return True
  
      def action_cancel_draft(self, cr, uid, ids, context=None):
              self.write(cr, uid, [inv.id], {'state':'draft'}, context=context)
          return True
  
 -    def action_cancel_inventary(self, cr, uid, ids, context=None):
 +    def action_cancel_inventory(self, cr, uid, ids, context=None):
          """ Cancels both stock move and inventory
          @return: True
          """