[MERGE] forward port of branch saas-4 up to fa07bc8
authorChristophe Simonis <chs@odoo.com>
Wed, 30 Jul 2014 18:30:14 +0000 (20:30 +0200)
committerChristophe Simonis <chs@odoo.com>
Wed, 30 Jul 2014 18:30:14 +0000 (20:30 +0200)
19 files changed:
1  2 
addons/account/account_move_line.py
addons/account_voucher/account_voucher.py
addons/delivery/delivery.py
addons/hr_holidays/hr_holidays.py
addons/mail/mail_thread.py
addons/project/report/project_report.py
addons/sale/res_config.py
addons/sale/sale.py
addons/stock/stock.py
addons/survey/controllers/main.py
addons/web/static/src/js/chrome.js
addons/web/static/src/js/views.js
addons/website/controllers/main.py
addons/website/models/ir_http.py
addons/website/static/src/js/website.editor.js
addons/website_crm/controllers/main.py
addons/website_crm_partner_assign/views/website_crm_partner_assign.xml
addons/website_membership/controllers/main.py
addons/website_membership/views/website_membership.xml

Simple merge
@@@ -211,15 -195,14 +211,15 @@@ class delivery_grid(osv.osv)
          for line in order.order_line:
              if not line.product_id or line.is_delivery:
                  continue
-             q = product_uom_obj._compute_qty(cr, uid, line.product_uom.id, line.product_uos_qty, line.product_id.uom_id.id)
+             q = product_uom_obj._compute_qty(cr, uid, line.product_uom.id, line.product_uom_qty, line.product_id.uom_id.id)
              weight += (line.product_id.weight or 0.0) * q
              volume += (line.product_id.volume or 0.0) * q
 +            quantity += q
          total = order.amount_total or 0.0
  
 -        return self.get_price_from_picking(cr, uid, id, total,weight, volume, context=context)
 +        return self.get_price_from_picking(cr, uid, id, total,weight, volume, quantity, context=context)
  
 -    def get_price_from_picking(self, cr, uid, id, total, weight, volume, context=None):
 +    def get_price_from_picking(self, cr, uid, id, total, weight, volume, quantity, context=None):
          grid = self.browse(cr, uid, id, context=context)
          price = 0.0
          ok = False
Simple merge
Simple merge
@@@ -48,9 -48,8 +48,8 @@@ class report_project_task_user(osv.osv)
                                         help="Number of Days to Open the task"),
          'delay_endings_days': fields.float('Overpassed Deadline', digits=(16,2), readonly=True),
          'nbr': fields.integer('# of tasks', readonly=True),
 -        'priority': fields.selection([('4', 'Very Low'), ('3', 'Low'), ('2', 'Medium'), ('1', 'Urgent'), ('0', 'Very urgent')],
 +        'priority': fields.selection([('0','Low'), ('1','Normal'), ('2','High')],
              string='Priority', readonly=True),
-         'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')],'Status', readonly=True),
          'company_id': fields.many2one('res.company', 'Company', readonly=True),
          'partner_id': fields.many2one('res.partner', 'Contact', readonly=True),
          'stage_id': fields.many2one('project.task.type', 'Stage'),
Simple merge
Simple merge
@@@ -892,562 -893,596 +892,561 @@@ class stock_picking(osv.osv)
              todo = []
              for move in pick.move_lines:
                  if move.state == 'draft':
 -                    self.pool.get('stock.move').action_confirm(cr, uid, [move.id],
 -                        context=context)
 -                    todo.append(move.id)
 -                elif move.state in ('assigned','confirmed'):
 +                    todo.extend(self.pool.get('stock.move').action_confirm(cr, uid, [move.id], context=context))
 +                elif move.state in ('assigned', 'confirmed'):
                      todo.append(move.id)
              if len(todo):
 -                self.pool.get('stock.move').action_done(cr, uid, todo,
 -                        context=context)
 +                self.pool.get('stock.move').action_done(cr, uid, todo, context=context)
          return True
  
 -    def get_currency_id(self, cr, uid, picking):
 -        return False
 -
 -    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 and purchase modules
 -            @param picking: object of the picking for which we are selecting the partner to invoice
 -            @return: object of the partner to invoice
 -        """
 -        return picking.partner_id and picking.partner_id.id
 -
 -    def _get_comment_invoice(self, cr, uid, picking):
 -        """
 -        @return: comment string for invoice
 -        """
 -        return picking.note or ''
 -
 -    def _get_price_unit_invoice(self, cr, uid, move_line, type, context=None):
 -        """ Gets price unit for invoice
 -        @param move_line: Stock move lines
 -        @param type: Type of invoice
 -        @return: The price unit for the move line
 -        """
 -        if context is None:
 -            context = {}
 -
 -        if type in ('in_invoice', 'in_refund'):
 -            # Take the user company and pricetype
 -            context['currency_id'] = move_line.company_id.currency_id.id
 -            amount_unit = move_line.product_id.price_get('standard_price', context=context)[move_line.product_id.id]
 -            return amount_unit
 -        else:
 -            return move_line.product_id.list_price
 +    def unlink(self, cr, uid, ids, context=None):
 +        #on picking deletion, cancel its move then unlink them too
 +        move_obj = self.pool.get('stock.move')
 +        context = context or {}
 +        for pick in self.browse(cr, uid, ids, context=context):
 +            move_ids = [move.id for move in pick.move_lines]
 +            move_obj.action_cancel(cr, uid, move_ids, context=context)
 +            move_obj.unlink(cr, uid, move_ids, context=context)
 +        return super(stock_picking, self).unlink(cr, uid, ids, context=context)
  
 -    def _get_discount_invoice(self, cr, uid, move_line):
 -        '''Return the discount for the move line'''
 -        return 0.0
 +    def write(self, cr, uid, ids, vals, context=None):
 +        res = super(stock_picking, self).write(cr, uid, ids, vals, context=context)
 +        #if we changed the move lines or the pack operations, we need to recompute the remaining quantities of both
 +        if 'move_lines' in vals or 'pack_operation_ids' in vals:
 +            self.do_recompute_remaining_quantities(cr, uid, ids, context=context)
 +        return res
  
 -    def _get_taxes_invoice(self, cr, uid, move_line, type):
 -        """ Gets taxes on invoice
 -        @param move_line: Stock move lines
 -        @param type: Type of invoice
 -        @return: Taxes Ids for the move line
 +    def _create_backorder(self, cr, uid, picking, backorder_moves=[], context=None):
 +        """ Move all non-done lines into a new backorder picking. If the key 'do_only_split' is given in the context, then move all lines not in context.get('split', []) instead of all non-done lines.
          """
 -        if type in ('in_invoice', 'in_refund'):
 -            taxes = move_line.product_id.supplier_taxes_id
 -        else:
 -            taxes = move_line.product_id.taxes_id
 -
 -        if move_line.picking_id and move_line.picking_id.partner_id and move_line.picking_id.partner_id.id:
 -            return self.pool.get('account.fiscal.position').map_tax(
 -                cr,
 -                uid,
 -                move_line.picking_id.partner_id.property_account_position,
 -                taxes
 -            )
 -        else:
 -            return map(lambda x: x.id, taxes)
 -
 -    def _get_account_analytic_invoice(self, cr, uid, picking, move_line):
 +        if not backorder_moves:
 +            backorder_moves = picking.move_lines
 +        backorder_move_ids = [x.id for x in backorder_moves if x.state not in ('done', 'cancel')]
 +        if 'do_only_split' in context and context['do_only_split']:
 +            backorder_move_ids = [x.id for x in backorder_moves if x.id not in context.get('split', [])]
 +
 +        if backorder_move_ids:
 +            backorder_id = self.copy(cr, uid, picking.id, {
 +                'name': '/',
 +                'move_lines': [],
 +                'pack_operation_ids': [],
 +                'backorder_id': picking.id,
 +            })
-             back_order_name = self.browse(cr, uid, backorder_id, context=context).name
-             self.message_post(cr, uid, picking.id, body=_("Back order <em>%s</em> <b>created</b>.") % (back_order_name), context=context)
++            self.message_post(cr, uid, picking.id, body=_("Back order <em>%s</em> <b>created</b>.") % (picking.name), context=context)
 +            move_obj = self.pool.get("stock.move")
 +            move_obj.write(cr, uid, backorder_move_ids, {'picking_id': backorder_id}, context=context)
 +
 +            self.write(cr, uid, [picking.id], {'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
 +            self.action_confirm(cr, uid, [backorder_id], context=context)
 +            return backorder_id
          return False
  
 -    def _invoice_line_hook(self, cr, uid, move_line, invoice_line_id):
 -        '''Call after the creation of the invoice line'''
 -        return
 -
 -    def _invoice_hook(self, cr, uid, picking, invoice_id):
 -        '''Call after the creation of the invoice'''
 -        return
 -
 -    def _get_invoice_type(self, pick):
 -        src_usage = dest_usage = None
 -        inv_type = None
 -        if pick.invoice_state == '2binvoiced':
 -            if pick.move_lines:
 -                src_usage = pick.move_lines[0].location_id.usage
 -                dest_usage = pick.move_lines[0].location_dest_id.usage
 -            if pick.type == 'out' and dest_usage == 'supplier':
 -                inv_type = 'in_refund'
 -            elif pick.type == 'out' and dest_usage == 'customer':
 -                inv_type = 'out_invoice'
 -            elif pick.type == 'in' and src_usage == 'supplier':
 -                inv_type = 'in_invoice'
 -            elif pick.type == 'in' and src_usage == 'customer':
 -                inv_type = 'out_refund'
 -            else:
 -                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),
 -        }
 +    def recheck_availability(self, cr, uid, picking_ids, context=None):
 +        self.action_assign(cr, uid, picking_ids, context=context)
 +        self.do_prepare_partial(cr, uid, picking_ids, context=context)
  
 -    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
 +    def _get_top_level_packages(self, cr, uid, quants_suggested_locations, context=None):
 +        """This method searches for the higher level packages that can be moved as a single operation, given a list of quants
 +           to move and their suggested destination, and returns the list of matching packages.
          """
 -        if isinstance(partner, int):
 -            partner = self.pool.get('res.partner').browse(cr, uid, partner, context=context)
 -        if inv_type in ('out_invoice', 'out_refund'):
 -            account_id = partner.property_account_receivable.id
 -            payment_term = partner.property_payment_term.id or False
 -        else:
 -            account_id = partner.property_account_payable.id
 -            payment_term = partner.property_supplier_payment_term.id or False
 -        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,
 -            'comment': comment,
 -            'payment_term': payment_term,
 -            '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.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.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.
 -        @param journal_id: Id of journal
 -        @param group: Whether to create a group invoice or not
 -        @param type: Type invoice to be created
 -        @return: Ids of created invoices for the pickings
 +        # Try to find as much as possible top-level packages that can be moved
 +        pack_obj = self.pool.get("stock.quant.package")
 +        quant_obj = self.pool.get("stock.quant")
 +        top_lvl_packages = set()
 +        quants_to_compare = quants_suggested_locations.keys()
 +        for pack in list(set([x.package_id for x in quants_suggested_locations.keys() if x and x.package_id])):
 +            loop = True
 +            test_pack = pack
 +            good_pack = False
 +            pack_destination = False
 +            while loop:
 +                pack_quants = pack_obj.get_content(cr, uid, [test_pack.id], context=context)
 +                all_in = True
 +                for quant in quant_obj.browse(cr, uid, pack_quants, context=context):
 +                    # If the quant is not in the quants to compare and not in the common location
 +                    if not quant in quants_to_compare:
 +                        all_in = False
 +                        break
 +                    else:
 +                        #if putaway strat apply, the destination location of each quant may be different (and thus the package should not be taken as a single operation)
 +                        if not pack_destination:
 +                            pack_destination = quants_suggested_locations[quant]
 +                        elif pack_destination != quants_suggested_locations[quant]:
 +                            all_in = False
 +                            break
 +                if all_in:
 +                    good_pack = test_pack
 +                    if test_pack.parent_id:
 +                        test_pack = test_pack.parent_id
 +                    else:
 +                        #stop the loop when there's no parent package anymore
 +                        loop = False
 +                else:
 +                    #stop the loop when the package test_pack is not totally reserved for moves of this picking
 +                    #(some quants may be reserved for other picking or not reserved at all)
 +                    loop = False
 +            if good_pack:
 +                top_lvl_packages.add(good_pack)
 +        return list(top_lvl_packages)
 +
 +    def _prepare_pack_ops(self, cr, uid, picking, quants, forced_qties, context=None):
 +        """ returns a list of dict, ready to be used in create() of stock.pack.operation.
 +
 +        :param picking: browse record (stock.picking)
 +        :param quants: browse record list (stock.quant). List of quants associated to the picking
 +        :param forced_qties: dictionary showing for each product (keys) its corresponding quantity (value) that is not covered by the quants associated to the picking
          """
 -        if context is None:
 -            context = {}
 +        def _picking_putaway_apply(product):
 +            location = False
 +            # Search putaway strategy
 +            if product_putaway_strats.get(product.id):
 +                location = product_putaway_strats[product.id]
 +            else:
 +                location = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, product, context=context)
 +                product_putaway_strats[product.id] = location
 +            return location or picking.location_dest_id.id
 +
 +        pack_obj = self.pool.get("stock.quant.package")
 +        quant_obj = self.pool.get("stock.quant")
 +        vals = []
 +        qtys_grouped = {}
 +        #for each quant of the picking, find the suggested location
 +        quants_suggested_locations = {}
 +        product_putaway_strats = {}
 +        for quant in quants:
 +            if quant.qty <= 0:
 +                continue
 +            suggested_location_id = _picking_putaway_apply(quant.product_id)
 +            quants_suggested_locations[quant] = suggested_location_id
 +
 +        #find the packages we can movei as a whole
 +        top_lvl_packages = self._get_top_level_packages(cr, uid, quants_suggested_locations, context=context)
 +        # and then create pack operations for the top-level packages found
 +        for pack in top_lvl_packages:
 +            pack_quant_ids = pack_obj.get_content(cr, uid, [pack.id], context=context)
 +            pack_quants = quant_obj.browse(cr, uid, pack_quant_ids, context=context)
 +            vals.append({
 +                    'picking_id': picking.id,
 +                    'package_id': pack.id,
 +                    'product_qty': 1.0,
 +                    'location_id': pack.location_id.id,
 +                    'location_dest_id': quants_suggested_locations[pack_quants[0]],
 +                })
 +            #remove the quants inside the package so that they are excluded from the rest of the computation
 +            for quant in pack_quants:
 +                del quants_suggested_locations[quant]
 +
 +        # Go through all remaining reserved quants and group by product, package, lot, owner, source location and dest location
 +        for quant, dest_location_id in quants_suggested_locations.items():
 +            key = (quant.product_id.id, quant.package_id.id, quant.lot_id.id, quant.owner_id.id, quant.location_id.id, dest_location_id)
 +            if qtys_grouped.get(key):
 +                qtys_grouped[key] += quant.qty
 +            else:
 +                qtys_grouped[key] = quant.qty
  
 -        invoice_obj = self.pool.get('account.invoice')
 -        invoice_line_obj = self.pool.get('account.invoice.line')
 -        partner_obj = self.pool.get('res.partner')
 -        invoices_group = {}
 -        res = {}
 -        inv_type = type
 -        for picking in self.browse(cr, uid, ids, context=context):
 -            if picking.invoice_state != '2binvoiced':
 +        # Do the same for the forced quantities (in cases of force_assign or incomming shipment for example)
 +        for product, qty in forced_qties.items():
 +            if qty <= 0:
                  continue
 -            partner = self._get_partner_to_invoice(cr, uid, picking, context=context)
 -            if isinstance(partner, int):
 -                partner = partner_obj.browse(cr, uid, [partner], context=context)[0]
 -            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)
 -
 -            invoice_vals = self._prepare_invoice(cr, uid, picking, partner, inv_type, journal_id, context=context)
 -            if group and partner.id in invoices_group:
 -                invoice_id = invoices_group[partner.id]
 -                invoice = invoice_obj.browse(cr, uid, invoice_id)
 -                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)
 +            suggested_location_id = _picking_putaway_apply(product)
 +            key = (product.id, False, False, False, picking.location_id.id, suggested_location_id)
 +            if qtys_grouped.get(key):
 +                qtys_grouped[key] += qty
              else:
 -                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
 -                if move_line.scrapped:
 -                    # do no invoice scrapped products
 +                qtys_grouped[key] = qty
 +
 +        # Create the necessary operations for the grouped quants and remaining qtys
 +        for key, qty in qtys_grouped.items():
 +            vals.append({
 +                'picking_id': picking.id,
 +                'product_qty': qty,
 +                'product_id': key[0],
 +                'package_id': key[1],
 +                'lot_id': key[2],
 +                'owner_id': key[3],
 +                'location_id': key[4],
 +                'location_dest_id': key[5],
 +                'product_uom_id': self.pool.get("product.product").browse(cr, uid, key[0], context=context).uom_id.id,
 +            })
 +        return vals
 +
 +    def open_barcode_interface(self, cr, uid, picking_ids, context=None):
 +        final_url="/barcode/web/#action=stock.ui&picking_id="+str(picking_ids[0])
 +        return {'type': 'ir.actions.act_url', 'url':final_url, 'target': 'self',}
 +
 +    def do_partial_open_barcode(self, cr, uid, picking_ids, context=None):
 +        self.do_prepare_partial(cr, uid, picking_ids, context=context)
 +        return self.open_barcode_interface(cr, uid, picking_ids, context=context)
 +
 +    def do_prepare_partial(self, cr, uid, picking_ids, context=None):
 +        context = context or {}
 +        pack_operation_obj = self.pool.get('stock.pack.operation')
 +        #used to avoid recomputing the remaining quantities at each new pack operation created
 +        ctx = context.copy()
 +        ctx['no_recompute'] = True
 +
 +        #get list of existing operations and delete them
 +        existing_package_ids = pack_operation_obj.search(cr, uid, [('picking_id', 'in', picking_ids)], context=context)
 +        if existing_package_ids:
 +            pack_operation_obj.unlink(cr, uid, existing_package_ids, context)
 +        for picking in self.browse(cr, uid, picking_ids, context=context):
 +            forced_qties = {}  # Quantity remaining after calculating reserved quants
 +            picking_quants = []
 +            #Calculate packages, reserved quants, qtys of this picking's moves
 +            for move in picking.move_lines:
 +                if move.state not in ('assigned', 'confirmed'):
                      continue
 -                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')))
 -            self.write(cr, uid, [picking.id], {
 -                'invoice_state': 'invoiced',
 -                }, context=context)
 -            self._invoice_hook(cr, uid, picking, invoice_id)
 -        self.write(cr, uid, res.keys(), {
 -            'invoice_state': 'invoiced',
 -            }, context=context)
 -        return res
 -
 -    def test_done(self, cr, uid, ids, context=None):
 -        """ Test whether the move lines are done or not.
 -        @return: True or False
 +                move_quants = move.reserved_quant_ids
 +                picking_quants += move_quants
 +                forced_qty = (move.state == 'assigned') and move.product_qty - sum([x.qty for x in move_quants]) or 0
 +                #if we used force_assign() on the move, or if the move is incomming, forced_qty > 0
 +                if forced_qty:
 +                    if forced_qties.get(move.product_id):
 +                        forced_qties[move.product_id] += forced_qty
 +                    else:
 +                        forced_qties[move.product_id] = forced_qty
 +            for vals in self._prepare_pack_ops(cr, uid, picking, picking_quants, forced_qties, context=context):
 +                pack_operation_obj.create(cr, uid, vals, context=ctx)
 +        #recompute the remaining quantities all at once
 +        self.do_recompute_remaining_quantities(cr, uid, picking_ids, context=context)
 +        self.write(cr, uid, picking_ids, {'recompute_pack_op': False}, context=context)
 +
 +    def do_unreserve(self, cr, uid, picking_ids, context=None):
          """
 -        ok = False
 -        for pick in self.browse(cr, uid, ids, context=context):
 -            if not pick.move_lines:
 -                return True
 -            for move in pick.move_lines:
 -                if move.state not in ('cancel','done'):
 -                    return False
 -                if move.state=='done':
 -                    ok = True
 -        return ok
 -
 -    def test_cancel(self, cr, uid, ids, context=None):
 -        """ Test whether the move lines are canceled or not.
 -        @return: True or False
 +          Will remove all quants for picking in picking_ids
          """
 -        for pick in self.browse(cr, uid, ids, context=context):
 -            for move in pick.move_lines:
 -                if move.state not in ('cancel',):
 -                    return False
 -        return True
 -
 -    def allow_cancel(self, cr, uid, ids, context=None):
 -        for pick in self.browse(cr, uid, ids, context=context):
 -            if not pick.move_lines:
 -                return True
 -            for move in pick.move_lines:
 -                if move.state == 'done':
 -                    raise osv.except_osv(_('Error!'), _('You cannot cancel the picking as some moves have been done. You should cancel the picking lines.'))
 -        return True
 -
 -    def unlink(self, cr, uid, ids, context=None):
 -        move_obj = self.pool.get('stock.move')
 -        if context is None:
 -            context = {}
 -        for pick in self.browse(cr, uid, ids, context=context):
 -            if pick.state in ['done','cancel']:
 -                raise osv.except_osv(_('Error!'), _('You cannot remove the picking which is in %s state!')%(pick.state,))
 +        moves_to_unreserve = []
 +        pack_line_to_unreserve = []
 +        for picking in self.browse(cr, uid, picking_ids, context=context):
 +            moves_to_unreserve += [m.id for m in picking.move_lines if m.state not in ('done', 'cancel')]
 +            pack_line_to_unreserve += [p.id for p in picking.pack_operation_ids]
 +        if moves_to_unreserve:
 +            if pack_line_to_unreserve:
 +                self.pool.get('stock.pack.operation').unlink(cr, uid, pack_line_to_unreserve, context=context)
 +            self.pool.get('stock.move').do_unreserve(cr, uid, moves_to_unreserve, context=context)
 +
 +    def recompute_remaining_qty(self, cr, uid, picking, context=None):
 +        def _create_link_for_index(operation_id, index, product_id, qty_to_assign, quant_id=False):
 +            move_dict = prod2move_ids[product_id][index]
 +            qty_on_link = min(move_dict['remaining_qty'], qty_to_assign)
 +            self.pool.get('stock.move.operation.link').create(cr, uid, {'move_id': move_dict['move'].id, 'operation_id': operation_id, 'qty': qty_on_link, 'reserved_quant_id': quant_id}, context=context)
 +            if move_dict['remaining_qty'] == qty_on_link:
 +                prod2move_ids[product_id].pop(index)
              else:
 -                ids2 = [move.id for move in pick.move_lines]
 -                ctx = context.copy()
 -                ctx.update({'call_unlink':True})
 -                if pick.state != 'draft':
 -                    #Cancelling the move in order to affect Virtual stock of product
 -                    move_obj.action_cancel(cr, uid, ids2, ctx)
 -                #Removing the move
 -                move_obj.unlink(cr, uid, ids2, ctx)
 -
 -        return super(stock_picking, self).unlink(cr, uid, ids, context=context)
 +                move_dict['remaining_qty'] -= qty_on_link
 +            return qty_on_link
 +
 +        def _create_link_for_quant(operation_id, quant, qty):
 +            """create a link for given operation and reserved move of given quant, for the max quantity possible, and returns this quantity"""
 +            if not quant.reservation_id.id:
 +                return _create_link_for_product(operation_id, quant.product_id.id, qty)
 +            qty_on_link = 0
 +            for i in range(0, len(prod2move_ids[quant.product_id.id])):
 +                if prod2move_ids[quant.product_id.id][i]['move'].id != quant.reservation_id.id:
 +                    continue
 +                qty_on_link = _create_link_for_index(operation_id, i, quant.product_id.id, qty, quant_id=quant.id)
 +                break
 +            return qty_on_link
 +
 +        def _create_link_for_product(operation_id, product_id, qty):
 +            '''method that creates the link between a given operation and move(s) of given product, for the given quantity.
 +            Returns True if it was possible to create links for the requested quantity (False if there was not enough quantity on stock moves)'''
 +            qty_to_assign = qty
 +            if prod2move_ids.get(product_id):
 +                while prod2move_ids[product_id] and qty_to_assign > 0:
 +                    qty_on_link = _create_link_for_index(operation_id, 0, product_id, qty_to_assign, quant_id=False)
 +                    qty_to_assign -= qty_on_link
 +            return qty_to_assign == 0
  
 -    # FIXME: needs refactoring, this code is partially duplicated in stock_move.do_partial()!
 -    def do_partial(self, cr, uid, ids, partial_datas, context=None):
 -        """ Makes partial picking and moves done.
 -        @param partial_datas : Dictionary containing details of partial picking
 -                          like partner_id, partner_id, delivery_date,
 -                          delivery moves with product_id, product_qty, uom
 -        @return: Dictionary of values
 -        """
 -        if context is None:
 -            context = {}
 -        else:
 -            context = dict(context)
 -        res = {}
 -        move_obj = self.pool.get('stock.move')
 -        product_obj = self.pool.get('product.product')
 -        currency_obj = self.pool.get('res.currency')
          uom_obj = self.pool.get('product.uom')
 -        sequence_obj = self.pool.get('ir.sequence')
 -        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, partial_qty, product_uoms = {}, {}, {}, {}, {}
 -            for move in pick.move_lines:
 -                if move.state in ('done', 'cancel'):
 -                    continue
 -                partial_data = partial_datas.get('move%s'%(move.id), {})
 -                product_qty = partial_data.get('product_qty',0.0)
 -                move_product_qty[move.id] = product_qty
 -                product_uom = partial_data.get('product_uom',False)
 -                product_price = partial_data.get('product_price',0.0)
 -                product_currency = partial_data.get('product_currency',False)
 -                prodlot_id = partial_data.get('prodlot_id')
 -                prodlot_ids[move.id] = prodlot_id
 -                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 > partial_qty[move.id]:
 -                    too_few.append(move)
 -                else:
 -                    too_many.append(move)
 -
 -                # Average price computation
 -                if (pick.type == 'in') and (move.product_id.cost_method == 'average'):
 -                    product = product_obj.browse(cr, uid, move.product_id.id)
 -                    move_currency_id = move.company_id.currency_id.id
 -                    context['currency_id'] = move_currency_id
 -                    qty = uom_obj._compute_qty(cr, uid, product_uom, product_qty, product.uom_id.id)
 -
 -                    if product.id not in product_avail:
 -                        # keep track of stock on hand including processed lines not yet marked as done
 -                        product_avail[product.id] = product.qty_available
 -
 -                    if qty > 0:
 -                        new_price = currency_obj.compute(cr, uid, product_currency,
 -                                move_currency_id, product_price, round=False)
 -                        new_price = uom_obj._compute_price(cr, uid, product_uom, new_price,
 -                                product.uom_id.id)
 -                        if product_avail[product.id] <= 0:
 -                            product_avail[product.id] = 0
 -                            new_std_price = new_price
 +        package_obj = self.pool.get('stock.quant.package')
 +        quant_obj = self.pool.get('stock.quant')
 +        quants_in_package_done = set()
 +        prod2move_ids = {}
 +        still_to_do = []
 +        #make a dictionary giving for each product, the moves and related quantity that can be used in operation links
 +        for move in picking.move_lines:
 +            if not prod2move_ids.get(move.product_id.id):
 +                prod2move_ids[move.product_id.id] = [{'move': move, 'remaining_qty': move.product_qty}]
 +            else:
 +                prod2move_ids[move.product_id.id].append({'move': move, 'remaining_qty': move.product_qty})
 +
 +        need_rereserve = False
 +        #sort the operations in order to give higher priority to those with a package, then a serial number
 +        operations = picking.pack_operation_ids
 +        operations.sort(key=lambda x: ((x.package_id and not x.product_id) and -4 or 0) + (x.package_id and -2 or 0) + (x.lot_id and -1 or 0))
 +        #delete existing operations to start again from scratch
 +        cr.execute("DELETE FROM stock_move_operation_link WHERE operation_id in %s", (tuple([x.id for x in operations]),))
 +
 +        #1) first, try to create links when quants can be identified without any doubt
 +        for ops in operations:
 +            #for each operation, create the links with the stock move by seeking on the matching reserved quants,
 +            #and deffer the operation if there is some ambiguity on the move to select
 +            if ops.package_id and not ops.product_id:
 +                #entire package
 +                quant_ids = package_obj.get_content(cr, uid, [ops.package_id.id], context=context)
 +                for quant in quant_obj.browse(cr, uid, quant_ids, context=context):
 +                    remaining_qty_on_quant = quant.qty
 +                    if quant.reservation_id:
 +                        #avoid quants being counted twice
 +                        quants_in_package_done.add(quant.id)
 +                        qty_on_link = _create_link_for_quant(ops.id, quant, quant.qty)
 +                        remaining_qty_on_quant -= qty_on_link
 +                    if remaining_qty_on_quant:
 +                        still_to_do.append((ops, quant.product_id.id, remaining_qty_on_quant))
 +                        need_rereserve = True
 +            elif ops.product_id.id:
 +                #Check moves with same product
 +                qty_to_assign = uom_obj._compute_qty_obj(cr, uid, ops.product_uom_id, ops.product_qty, ops.product_id.uom_id, context=context)
 +                for move_dict in prod2move_ids.get(ops.product_id.id, []):
 +                    move = move_dict['move']
 +                    for quant in move.reserved_quant_ids:
 +                        if not qty_to_assign > 0:
 +                            break
 +                        if quant.id in quants_in_package_done:
 +                            continue
 +
 +                        #check if the quant is matching the operation details
 +                        if ops.package_id:
 +                            flag = quant.package_id and bool(package_obj.search(cr, uid, [('id', 'child_of', [ops.package_id.id]), ('id', '=', quant.package_id.id)], context=context)) or False
                          else:
 -                            # Get the standard price
 -                            amount_unit = product.price_get('standard_price', context=context)[product.id]
 -                            new_std_price = ((amount_unit * product_avail[product.id])\
 -                                + (new_price * qty))/(product_avail[product.id] + qty)
 -                        # Write the field according to price type field
 -                        product_obj.write(cr, uid, [product.id], {'standard_price': new_std_price})
 -
 -                        # Record the values that were chosen in the wizard, so they can be
 -                        # used for inventory valuation if real-time valuation is enabled.
 -                        move_obj.write(cr, uid, [move.id],
 -                                {'price_unit': product_price,
 -                                 'price_currency_id': product_currency})
 -
 -                        product_avail[product.id] += qty
 -
 -
 -
 -            for move in too_few:
 -                product_qty = move_product_qty[move.id]
 -                if not new_picking:
 -                    new_picking_name = pick.name
 -                    self.write(cr, uid, [pick.id], 
 -                               {'name': sequence_obj.get(cr, uid,
 -                                            'stock.picking.%s'%(pick.type)),
 -                               })
 -                    pick.refresh()
 -                    new_picking = self.copy(cr, uid, pick.id,
 -                            {
 -                                'name': new_picking_name,
 -                                'move_lines' : [],
 -                                'state':'draft',
 -                            })
 -                if product_qty != 0:
 -                    defaults = {
 -                            'product_qty' : product_qty,
 -                            'product_uos_qty': product_qty, #TODO: put correct uos_qty
 -                            'picking_id' : new_picking,
 -                            'state': 'assigned',
 -                            'move_dest_id': False,
 -                            'price_unit': move.price_unit,
 -                            'product_uom': product_uoms[move.id]
 +                            flag = not quant.package_id.id
 +                        flag = flag and ((ops.lot_id and ops.lot_id.id == quant.lot_id.id) or not ops.lot_id)
 +                        flag = flag and (ops.owner_id.id == quant.owner_id.id)
 +                        if flag:
 +                            max_qty_on_link = min(quant.qty, qty_to_assign)
 +                            qty_on_link = _create_link_for_quant(ops.id, quant, max_qty_on_link)
 +                            qty_to_assign -= qty_on_link
 +                if qty_to_assign > 0:
 +                    #qty reserved is less than qty put in operations. We need to create a link but it's deferred after we processed
 +                    #all the quants (because they leave no choice on their related move and needs to be processed with higher priority)
 +                    still_to_do += [(ops, ops.product_id.id, qty_to_assign)]
 +                    need_rereserve = True
 +
 +        #2) then, process the remaining part
 +        all_op_processed = True
 +        for ops, product_id, remaining_qty in still_to_do:
 +            all_op_processed = all_op_processed and _create_link_for_product(ops.id, product_id, remaining_qty)
 +        return (need_rereserve, all_op_processed)
 +
 +    def picking_recompute_remaining_quantities(self, cr, uid, picking, context=None):
 +        need_rereserve = False
 +        all_op_processed = True
 +        if picking.pack_operation_ids:
 +            need_rereserve, all_op_processed = self.recompute_remaining_qty(cr, uid, picking, context=context)
 +        return need_rereserve, all_op_processed
 +
 +    def do_recompute_remaining_quantities(self, cr, uid, picking_ids, context=None):
 +        for picking in self.browse(cr, uid, picking_ids, context=context):
 +            if picking.pack_operation_ids:
 +                self.recompute_remaining_qty(cr, uid, picking, context=context)
 +
 +    def _create_extra_moves(self, cr, uid, picking, context=None):
 +        '''This function creates move lines on a picking, at the time of do_transfer, based on
 +        unexpected product transfers (or exceeding quantities) found in the pack operations.
 +        '''
 +        move_obj = self.pool.get('stock.move')
 +        operation_obj = self.pool.get('stock.pack.operation')
 +        moves = []
 +        for op in picking.pack_operation_ids:
 +            for product_id, remaining_qty in operation_obj._get_remaining_prod_quantities(cr, uid, op, context=context).items():
 +                if remaining_qty > 0:
 +                    product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
 +                    vals = {
 +                        'picking_id': picking.id,
 +                        'location_id': picking.location_id.id,
 +                        'location_dest_id': picking.location_dest_id.id,
 +                        'product_id': product_id,
 +                        'product_uom': product.uom_id.id,
 +                        'product_uom_qty': remaining_qty,
 +                        'name': _('Extra Move: ') + product.name,
 +                        'state': 'draft',
                      }
 -                    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 - partial_qty[move.id],
 -                            'product_uos_qty': move.product_qty - partial_qty[move.id], #TODO: put correct uos_qty
 -                            'prodlot_id': False,
 -                            'tracking_id': False,
 -                        })
 -
 -            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):
 -                    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(prodlot_id=prodlot_id)
 -                if new_picking:
 -                    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:
 -                self.signal_button_confirm(cr, uid, [new_picking])
 -                # Then we finish the good picking
 -                self.write(cr, uid, [pick.id], {'backorder_id': new_picking})
 -                self.action_move(cr, uid, [new_picking], context=context)
 -                self.signal_button_done(cr, uid, [new_picking])
 -                workflow.trg_write(uid, 'stock.picking', pick.id, cr)
 -                delivered_pack_id = new_picking
 -                self.message_post(cr, uid, new_picking, body=_("Back order <em>%s</em> has been <b>created</b>.") % (pick.name), context=context)
 -            else:
 -                self.action_move(cr, uid, [pick.id], context=context)
 -                self.signal_button_done(cr, uid, [pick.id])
 -                delivered_pack_id = pick.id
 -
 -            delivered_pack = self.browse(cr, uid, delivered_pack_id, context=context)
 -            res[pick.id] = {'delivered_picking': delivered_pack.id or False}
 -
 -        return res
 -    
 -    # views associated to each picking type
 -    _VIEW_LIST = {
 -        'out': 'view_picking_out_form',
 -        'in': 'view_picking_in_form',
 -        'internal': 'view_picking_form',
 -    }
 -    def _get_view_id(self, cr, uid, type):
 -        """Get the view id suiting the given type
 -        
 -        @param type: the picking type as a string
 -        @return: view i, or False if no view found
 -        """
 -        res = self.pool.get('ir.model.data').get_object_reference(cr, uid, 
 -            'stock', self._VIEW_LIST.get(type, 'view_picking_form'))            
 -        return res and res[1] or False
 -
 -
 -class stock_production_lot(osv.osv):
 -
 -    def name_get(self, cr, uid, ids, context=None):
 -        if not ids:
 -            return []
 -        reads = self.read(cr, uid, ids, ['name', 'prefix', 'ref'], context)
 -        res = []
 -        for record in reads:
 -            name = record['name']
 -            prefix = record['prefix']
 -            if prefix:
 -                name = prefix + '/' + name
 -            if record['ref']:
 -                name = '%s [%s]' % (name, record['ref'])
 -            res.append((record['id'], name))
 -        return res
 +                    moves.append(move_obj.create(cr, uid, vals, context=context))
 +        if moves:
 +            move_obj.action_confirm(cr, uid, moves, context=context)
 +        return moves
  
 -    def name_search(self, cr, uid, name, args=None, operator='ilike', context=None, limit=100):
 -        args = args or []
 -        ids = []
 -        if name:
 -            ids = self.search(cr, uid, [('prefix', '=', name)] + args, limit=limit, context=context)
 -            if not ids:
 -                ids = self.search(cr, uid, [('name', operator, name)] + args, limit=limit, context=context)
 +    def rereserve_quants(self, cr, uid, picking, move_ids=[], context=None):
 +        """ Unreserve quants then try to reassign quants."""
 +        stock_move_obj = self.pool.get('stock.move')
 +        if not move_ids:
 +            self.do_unreserve(cr, uid, [picking.id], context=context)
 +            self.action_assign(cr, uid, [picking.id], context=context)
          else:
 -            ids = self.search(cr, uid, args, limit=limit, context=context)
 -        return self.name_get(cr, uid, ids, context)
 +            stock_move_obj.do_unreserve(cr, uid, move_ids, context=context)
 +            stock_move_obj.action_assign(cr, uid, move_ids, context=context)
  
 -    _name = 'stock.production.lot'
 -    _description = 'Serial Number'
 -
 -    def _get_stock(self, cr, uid, ids, field_name, arg, context=None):
 -        """ Gets stock of products for locations
 -        @return: Dictionary of values
 +    def do_transfer(self, cr, uid, picking_ids, context=None):
 +        """
 +            If no pack operation, we do simple action_done of the picking
 +            Otherwise, do the pack operations
          """
 +        if not context:
 +            context = {}
 +        stock_move_obj = self.pool.get('stock.move')
 +        for picking in self.browse(cr, uid, picking_ids, context=context):
 +            if not picking.pack_operation_ids:
 +                self.action_done(cr, uid, [picking.id], context=context)
 +                continue
 +            else:
 +                need_rereserve, all_op_processed = self.picking_recompute_remaining_quantities(cr, uid, picking, context=context)
 +                #create extra moves in the picking (unexpected product moves coming from pack operations)
 +                todo_move_ids = []
 +                if not all_op_processed:
 +                    todo_move_ids += self._create_extra_moves(cr, uid, picking, context=context)
 +                    
 +                picking.refresh()
 +                #split move lines eventually
 +                
 +                toassign_move_ids = []
 +                for move in picking.move_lines:
 +                    remaining_qty = move.remaining_qty
 +                    if move.state in ('done', 'cancel'):
 +                        #ignore stock moves cancelled or already done
 +                        continue
 +                    elif move.state == 'draft':
 +                        toassign_move_ids.append(move.id)
 +                    if remaining_qty == 0:
 +                        if move.state in ('draft', 'assigned', 'confirmed'):
 +                            todo_move_ids.append(move.id)
 +                    elif remaining_qty > 0 and remaining_qty < move.product_qty:
 +                        new_move = stock_move_obj.split(cr, uid, move, remaining_qty, context=context)
 +                        todo_move_ids.append(move.id)
 +                        #Assign move as it was assigned before
 +                        toassign_move_ids.append(new_move)
 +                if (need_rereserve or not all_op_processed) and not picking.location_id.usage in ("supplier", "production", "inventory"):
 +                    self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, context=context)
 +                    self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context)
 +                if todo_move_ids and not context.get('do_only_split'):
 +                    self.pool.get('stock.move').action_done(cr, uid, todo_move_ids, context=context)
 +                elif context.get('do_only_split'):
 +                    context.update({'split': todo_move_ids})
 +            picking.refresh()
 +            self._create_backorder(cr, uid, picking, context=context)
 +            if toassign_move_ids:
 +                stock_move_obj.action_assign(cr, uid, toassign_move_ids, context=context)
 +        return True
 +
 +    def do_split(self, cr, uid, picking_ids, context=None):
 +        """ just split the picking (create a backorder) without making it 'done' """
          if context is None:
              context = {}
 -        if 'location_id' not in context:
 -            locations = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal')], context=context)
 -        else:
 -            locations = context['location_id'] and [context['location_id']] or []
 +        ctx = context.copy()
 +        ctx['do_only_split'] = True
 +        return self.do_transfer(cr, uid, picking_ids, context=ctx)
  
 -        if isinstance(ids, (int, long)):
 -            ids = [ids]
 +    def get_next_picking_for_ui(self, cr, uid, context=None):
 +        """ returns the next pickings to process. Used in the barcode scanner UI"""
 +        if context is None:
 +            context = {}
 +        domain = [('state', 'in', ('assigned', 'partially_available'))]
 +        if context.get('default_picking_type_id'):
 +            domain.append(('picking_type_id', '=', context['default_picking_type_id']))
 +        return self.search(cr, uid, domain, context=context)
 +
 +    def action_done_from_ui(self, cr, uid, picking_id, context=None):
 +        """ called when button 'done' is pushed in the barcode scanner UI """
 +        #write qty_done into field product_qty for every package_operation before doing the transfer
 +        pack_op_obj = self.pool.get('stock.pack.operation')
 +        for operation in self.browse(cr, uid, picking_id, context=context).pack_operation_ids:
 +            pack_op_obj.write(cr, uid, operation.id, {'product_qty': operation.qty_done}, context=context)
 +        self.do_transfer(cr, uid, [picking_id], context=context)
 +        #return id of next picking to work on
 +        return self.get_next_picking_for_ui(cr, uid, context=context)
 +
 +    def action_pack(self, cr, uid, picking_ids, operation_filter_ids=None, context=None):
 +        """ Create a package with the current pack_operation_ids of the picking that aren't yet in a pack.
 +        Used in the barcode scanner UI and the normal interface as well. 
 +        operation_filter_ids is used by barcode scanner interface to specify a subset of operation to pack"""
 +        if operation_filter_ids == None:
 +            operation_filter_ids = []
 +        stock_operation_obj = self.pool.get('stock.pack.operation')
 +        package_obj = self.pool.get('stock.quant.package')
 +        stock_move_obj = self.pool.get('stock.move')
 +        for picking_id in picking_ids:
 +            operation_search_domain = [('picking_id', '=', picking_id), ('result_package_id', '=', False)]
 +            if operation_filter_ids != []:
 +                operation_search_domain.append(('id', 'in', operation_filter_ids))
 +            operation_ids = stock_operation_obj.search(cr, uid, operation_search_domain, context=context)
 +            pack_operation_ids = []
 +            if operation_ids:
 +                for operation in stock_operation_obj.browse(cr, uid, operation_ids, context=context):
 +                    #If we haven't done all qty in operation, we have to split into 2 operation
 +                    op = operation
 +                    if (operation.qty_done < operation.product_qty):
 +                        new_operation = stock_operation_obj.copy(cr, uid, operation.id, {'product_qty': operation.qty_done,'qty_done': operation.qty_done}, context=context)
 +                        stock_operation_obj.write(cr, uid, operation.id, {'product_qty': operation.product_qty - operation.qty_done,'qty_done': 0, 'lot_id': False}, context=context)
 +                        op = stock_operation_obj.browse(cr, uid, new_operation, context=context)
 +                    pack_operation_ids.append(op.id)
 +                    for record in op.linked_move_operation_ids:
 +                        stock_move_obj.check_tracking(cr, uid, record.move_id, op.package_id.id or op.lot_id.id, context=context)
 +                package_id = package_obj.create(cr, uid, {}, context=context)
 +                stock_operation_obj.write(cr, uid, pack_operation_ids, {'result_package_id': package_id}, context=context)
 +        return True
  
 -        res = {}.fromkeys(ids, 0.0)
 -        if locations:
 -            cr.execute('''select
 -                    prodlot_id,
 -                    sum(qty)
 -                from
 -                    stock_report_prodlots
 -                where
 -                    location_id IN %s and prodlot_id IN %s group by prodlot_id''',(tuple(locations),tuple(ids),))
 -            res.update(dict(cr.fetchall()))
 +    def process_product_id_from_ui(self, cr, uid, picking_id, product_id, op_id, increment=True, context=None):
 +        return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id),('id', '=', op_id)], increment=increment, context=context)
  
 -        return res
 +    def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, visible_op_ids, context=None):
 +        '''This function is called each time there barcode scanner reads an input'''
 +        lot_obj = self.pool.get('stock.production.lot')
 +        package_obj = self.pool.get('stock.quant.package')
 +        product_obj = self.pool.get('product.product')
 +        stock_operation_obj = self.pool.get('stock.pack.operation')
 +        stock_location_obj = self.pool.get('stock.location')
 +        answer = {'filter_loc': False, 'operation_id': False}
 +        #check if the barcode correspond to a location
 +        matching_location_ids = stock_location_obj.search(cr, uid, [('loc_barcode', '=', barcode_str)], context=context)
 +        if matching_location_ids:
 +            #if we have a location, return immediatly with the location name
 +            location = stock_location_obj.browse(cr, uid, matching_location_ids[0], context=None)
 +            answer['filter_loc'] = stock_location_obj._name_get(cr, uid, location, context=None)
 +            answer['filter_loc_id'] = matching_location_ids[0]
 +            return answer
 +        #check if the barcode correspond to a product
 +        matching_product_ids = product_obj.search(cr, uid, ['|', ('ean13', '=', barcode_str), ('default_code', '=', barcode_str)], context=context)
 +        if matching_product_ids:
 +            op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', matching_product_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
 +            answer['operation_id'] = op_id
 +            return answer
 +        #check if the barcode correspond to a lot
 +        matching_lot_ids = lot_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
 +        if matching_lot_ids:
 +            lot = lot_obj.browse(cr, uid, matching_lot_ids[0], context=context)
 +            op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
 +            answer['operation_id'] = op_id
 +            return answer
 +        #check if the barcode correspond to a package
 +        matching_package_ids = package_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)
 +        if matching_package_ids:
 +            op_id = stock_operation_obj._search_and_increment(cr, uid, picking_id, [('package_id', '=', matching_package_ids[0])], filter_visible=True, visible_op_ids=visible_op_ids, increment=True, context=context)
 +            answer['operation_id'] = op_id
 +            return answer
 +        return answer
  
 -    def _stock_search(self, cr, uid, obj, name, args, context=None):
 -        """ Searches Ids of products
 -        @return: Ids of locations
 -        """
 -        locations = self.pool.get('stock.location').search(cr, uid, [('usage', '=', 'internal')])
 -        cr.execute('''select
 -                prodlot_id,
 -                sum(qty)
 -            from
 -                stock_report_prodlots
 -            where
 -                location_id IN %s group by prodlot_id
 -            having  sum(qty) '''+ str(args[0][1]) + str(args[0][2]),(tuple(locations),))
 -        res = cr.fetchall()
 -        ids = [('id', 'in', map(lambda x: x[0], res))]
 -        return ids
  
 +class stock_production_lot(osv.osv):
 +    _name = 'stock.production.lot'
 +    _inherit = ['mail.thread']
 +    _description = 'Lot/Serial'
      _columns = {
 -        'name': fields.char('Serial Number', size=64, required=True, help="Unique Serial Number, will be displayed as: PREFIX/SERIAL [INT_REF]"),
 +        'name': fields.char('Serial Number', size=64, required=True, help="Unique Serial Number"),
          'ref': fields.char('Internal Reference', size=256, help="Internal reference number in case it differs from the manufacturer's serial number"),
 -        'prefix': fields.char('Prefix', size=64, help="Optional prefix to prepend when displaying this serial number: PREFIX/SERIAL [INT_REF]"),
          'product_id': fields.many2one('product.product', 'Product', required=True, domain=[('type', '<>', 'service')]),
 -        'date': fields.datetime('Creation Date', required=True),
 -        'stock_available': fields.function(_get_stock, fnct_search=_stock_search, type="float", string="Available", select=True,
 -            help="Current quantity of products with this Serial Number available in company warehouses",
 -            digits_compute=dp.get_precision('Product Unit of Measure')),
 -        'revisions': fields.one2many('stock.production.lot.revision', 'lot_id', 'Revisions'),
 -        'company_id': fields.many2one('res.company', 'Company', select=True),
 -        'move_ids': fields.one2many('stock.move', 'prodlot_id', 'Moves for this serial number', readonly=True),
 +        'quant_ids': fields.one2many('stock.quant', 'lot_id', 'Quants', readonly=True),
 +        'create_date': fields.datetime('Creation Date'),
      }
      _defaults = {
 -        'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
          'name': lambda x, y, z, c: x.pool.get('ir.sequence').get(y, z, 'stock.lot.serial'),
          'product_id': lambda x, y, z, c: c.get('product_id', False),
      }
      _sql_constraints = [
-         ('name_ref_uniq', 'unique (name, ref)', 'The combination of Serial Number and internal reference must be unique !'),
+         ('name_ref_uniq', 'unique (name, ref, product_id, company_id)', 'The combination of Serial Number, internal reference, Product and Company must be unique !'),
      ]
 +
      def action_traceability(self, cr, uid, ids, context=None):
 -        """ It traces the information of a product
 +        """ It traces the information of lots
          @param self: The object pointer.
          @param cr: A database cursor
          @param uid: ID of the user currently logged in
@@@ -295,10 -295,14 +295,14 @@@ class WebsiteSurvey(http.Controller)
                                         'quizz_correction': True if survey.quizz_mode and token else False})
  
      @http.route(['/survey/results/<model("survey.survey"):survey>'],
 -                type='http', auth='user', multilang=True, website=True)
 +                type='http', auth='user', website=True)
      def survey_reporting(self, survey, token=None, **post):
          '''Display survey Results & Statistics for given survey.'''
-         result_template, current_filters, filter_display_data, filter_finish = 'survey.result', [], [], False
+         result_template ='survey.result'
+         current_filters = []
+         filter_display_data = []
+         filter_finish = False
          survey_obj = request.registry['survey.survey']
          if not survey.user_input_ids or not [input_id.id for input_id in survey.user_input_ids if input_id.state != 'new']:
              result_template = 'survey.no_result'
@@@ -183,8 -183,6 +183,7 @@@ instance.web.Dialog = instance.web.Widg
      */
      close: function(reason) {
          if (this.dialog_inited && !this.__tmp_dialog_hiding) {
 +            $('.tooltip').remove(); //remove open tooltip if any to prevent them staying when modal has disappeared
-             this.trigger("closing", reason);
              if (this.$el.is(":data(bs.modal)")) {     // may have been destroyed by closing signal
                  this.__tmp_dialog_hiding = true;
                  this.$dialog_box.modal('hide');
Simple merge
@@@ -196,17 -191,30 +198,20 @@@ class Website(openerp.addons.web.contro
              })
              if view.model_data_id.module not in modules_to_update:
                  modules_to_update.append(view.model_data_id.module)
-         module_obj = request.registry['ir.module.module']
-         module_ids = module_obj.search(request.cr, request.uid, [('name', 'in', modules_to_update)], context=request.context)
-         module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
+         if modules_to_update:
+             module_obj = request.registry['ir.module.module']
+             module_ids = module_obj.search(request.cr, request.uid, [('name', 'in', modules_to_update)], context=request.context)
+             if module_ids:
+                 module_obj.button_immediate_upgrade(request.cr, request.uid, module_ids, context=request.context)
          return request.redirect(redirect)
  
 -    @http.route('/website/customize_template_toggle', type='json', auth='user', website=True)
 -    def customize_template_set(self, view_id):
 -        view_obj = request.registry.get("ir.ui.view")
 -        view = view_obj.browse(request.cr, request.uid, int(view_id),
 -                               context=request.context)
 -        if view.inherit_id:
 -            value = False
 -        else:
 -            value = view.inherit_option_id and view.inherit_option_id.id or False
 -        view_obj.write(request.cr, request.uid, [view_id], {
 -            'inherit_id': value
 -        }, context=request.context)
 -        return True
 -
      @http.route('/website/customize_template_get', type='json', auth='user', website=True)
 -    def customize_template_get(self, xml_id, optional=True):
 +    def customize_template_get(self, xml_id, full=False):
 +        """ Lists the templates customizing ``xml_id``. By default, only
 +        returns optional templates (which can be toggled on and off), if
 +        ``full=True`` returns all templates customizing ``xml_id``
 +        """
          imd = request.registry['ir.model.data']
          view_model, view_theme_id = imd.get_object_reference(
              request.cr, request.uid, 'website', 'theme')
Simple merge
  
              observer.disconnect();
              var editor = this.rte.editor;
 -            var root = editor.element.$;
 +            var root = editor.element && editor.element.$;
-             editor.destroy();
+             try {
+                 editor.destroy();
+             }
+             catch(err) {
+                 // Hack to avoid the lost of all changes because ckeditor fails in destroy
+                 console.log("Error in editor.destroy() : " + err.toString() + "\n  " + err.stack);
+             }
              // FIXME: select editables then filter by dirty?
              var defs = this.rte.fetch_editables(root)
                  .filter('.oe_dirty')
@@@ -83,7 -92,7 +92,7 @@@ class contactus(http.Controller)
              post_description.append("%s: %s" % ("REFERER", environ.get("HTTP_REFERER")))
              values['description'] += dict_to_str(_("Environ Fields: "), post_description)
  
-         lead_id = self.create_lead(request, dict(values, user_id=False))
 -        lead_id = self.create_lead(request, values, kwargs)
++        lead_id = self.create_lead(request, dict(values, user_id=False), kwargs)
          if lead_id:
              for field_value in post_file:
                  attachment_value = {