[FIX]move domain check in _quants_get_order
[odoo/odoo.git] / addons / stock / stock.py
index f4d34b3..d954042 100644 (file)
@@ -27,7 +27,7 @@ import time
 from openerp.osv import fields, osv
 from openerp.tools.translate import _
 from openerp import tools
-from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
+from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT
 from openerp import SUPERUSER_ID
 import openerp.addons.decimal_precision as dp
 import logging
@@ -80,7 +80,7 @@ class stock_location(osv.osv):
         if context is None:
             context = {}
         context_with_inactive = context.copy()
-        context_with_inactive['active_test']=False
+        context_with_inactive['active_test'] = False
         return self.search(cr, uid, [('id', 'child_of', ids)], context=context_with_inactive)
 
     _columns = {
@@ -94,7 +94,7 @@ class stock_location(osv.osv):
                        \n* Inventory: Virtual location serving as counterpart for inventory operations used to correct stock levels (Physical inventories)
                        \n* Procurement: Virtual location serving as temporary counterpart for procurement operations when the source (supplier or production) is not known yet. This location should be empty when the procurement scheduler has finished running.
                        \n* Production: Virtual counterpart location for production operations: this location consumes the raw material and produces finished products
-                      """, select = True),
+                      """, select=True),
 
         'complete_name': fields.function(_complete_name, type='char', string="Location Name",
                             store={'stock.location': (_get_sublocations, ['name', 'location_id', 'active'], 10)}),
@@ -125,7 +125,7 @@ class stock_location(osv.osv):
         'posz': 0,
         'scrap_location': False,
     }
-    
+
     def get_putaway_strategy(self, cr, uid, location, product, context=None):
         pa = self.pool.get('product.putaway')
         categ = product.categ_id
@@ -134,13 +134,10 @@ class stock_location(osv.osv):
             categ = categ.parent_id
             categs.append(categ.id)
 
-        result = pa.search(cr,uid, [
-            ('location_id', '=', location.id),
-            ('product_categ_id', 'in', categs)
-        ], context=context)
+        result = pa.search(cr, uid, [('location_id', '=', location.id), ('product_categ_id', 'in', categs)], context=context)
         if result:
             return pa.browse(cr, uid, result[0], context=context)
-   
+
     def get_removal_strategy(self, cr, uid, location, product, context=None):
         pr = self.pool.get('product.removal')
         categ = product.categ_id
@@ -149,10 +146,7 @@ class stock_location(osv.osv):
             categ = categ.parent_id
             categs.append(categ.id)
 
-        result = pr.search(cr,uid, [
-            ('location_id', '=', location.id),
-            ('product_categ_id', 'in', categs)
-        ], context=context)
+        result = pr.search(cr, uid, [('location_id', '=', location.id), ('product_categ_id', 'in', categs)], context=context)
         if result:
             return pr.browse(cr, uid, result[0], context=context).method
 
@@ -177,16 +171,17 @@ class stock_location_route(osv.osv):
         'warehouse_selectable': fields.boolean('Applicable on Warehouse'),
         'supplied_wh_id': fields.many2one('stock.warehouse', 'Supplied Warehouse'),
         'supplier_wh_id': fields.many2one('stock.warehouse', 'Supplier Warehouse'),
+        'company_id': fields.many2one('res.company', 'Company', select=1, help='Let this field empty if this route is shared between all companies'),
     }
 
     _defaults = {
         'sequence': lambda self, cr, uid, ctx: 0,
         'active': True,
         'product_selectable': True,
+        'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.location.route', context=c),
     }
 
 
-
 #----------------------------------------------------------
 # Quants
 #----------------------------------------------------------
@@ -207,10 +202,26 @@ class stock_quant(osv.osv):
 
             res[q.id] = q.product_id.code or ''
             if q.lot_id:
-                res[q.id] = q.lot_id.name 
-            res[q.id] += ': '+  str(q.qty) + q.product_id.uom_id.name
+                res[q.id] = q.lot_id.name
+            res[q.id] += ': ' + str(q.qty) + q.product_id.uom_id.name
         return res
 
+    def _calc_inventory_value(self, cr, uid, ids, name, attr, context=None):
+        res = {}
+        uid_company_id = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.id
+        for quant in self.browse(cr, uid, ids, context=context):
+            context.pop('force_company', None)
+            if quant.company_id.id != uid_company_id:
+                #if the company of the quant is different than the current user company, force the company in the context
+                #then re-do a browse to read the property fields for the good company.
+                context['force_company'] = quant.company_id.id
+                quant = self.browse(cr, uid, quant.id, context=context)
+            res[quant.id] = self._get_inventory_value(cr, uid, quant, context=context)
+        return res
+
+    def _get_inventory_value(self, cr, uid, quant, context=None):
+        return quant.product_id.standard_price * quant.qty
+
     _columns = {
         'name': fields.function(_get_quant_name, type='char', string='Identifier'),
         'product_id': fields.many2one('product.product', 'Product', required=True),
@@ -219,7 +230,7 @@ class stock_quant(osv.osv):
         'package_id': fields.many2one('stock.quant.package', string='Package', help="The package containing this quant"),
         'packaging_type_id': fields.related('package_id', 'packaging_id', type='many2one', relation='product.packaging', string='Type of packaging', store=True),
         'reservation_id': fields.many2one('stock.move', 'Reserved for Move', help="The move the quant is reserved for"),
-        'reservation_op_id': fields.many2one('stock.pack.operation', 'Reserved for Pack Operation', help="The operation the quant is reserved for"),
+        'link_move_operation_id': fields.many2one('stock.move.operation.link', 'Reserved for Link between Move and Pack Operation', help="Technical field decpicting for with tuple (move, operation) this quant is reserved for"),
         'lot_id': fields.many2one('stock.production.lot', 'Lot'),
         'cost': fields.float('Unit Cost'),
         'owner_id': fields.many2one('res.partner', 'Owner', help="This is the owner of the quant"),
@@ -233,12 +244,26 @@ class stock_quant(osv.osv):
         # Used for negative quants to reconcile after compensated by a new positive one
         'propagated_from_id': fields.many2one('stock.quant', 'Linked Quant', help='The negative quant this is coming from'),
         'negative_dest_location_id': fields.many2one('stock.location', 'Destination Location', help='Technical field used to record the destination location of a move that created a negative quant'),
+        'inventory_value': fields.function(_calc_inventory_value, string="Inventory Value", type='float', readonly=True),
     }
 
     _defaults = {
         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.quant', context=c),
     }
 
+    def read_group(self, cr, uid, domain, fields, groupby, offset=0, limit=None, context=None, orderby=False):
+        ''' Overwrite the read_group in order to sum the function field 'inventory_value' in group by'''
+        res = super(stock_quant, self).read_group(cr, uid, domain, fields, groupby, offset=offset, limit=limit, context=context, orderby=orderby)
+        if 'inventory_value' in fields:
+            for line in res:
+                if '__domain' in line:
+                    lines = self.search(cr, uid, line['__domain'], context=context)
+                    inv_value = 0.0
+                    for line2 in self.browse(cr, uid, lines, context=context):
+                        inv_value += line2.inventory_value
+                    line['inventory_value'] = inv_value
+        return res
+
     def action_view_quant_history(self, cr, uid, ids, context=None):
         '''
         This function returns an action that display the history of the quant, which
@@ -254,14 +279,17 @@ class stock_quant(osv.osv):
         move_ids = []
         for quant in self.browse(cr, uid, ids, context=context):
             move_ids += [move.id for move in quant.history_ids]
-            
+
         result['domain'] = "[('id','in',[" + ','.join(map(str, move_ids)) + "])]"
         return result
 
-    def quants_reserve(self, cr, uid, quants, move, context=None):
-        '''This function reserves quants for the given move. If the total of quantity reserved is enough, the move's state is also set to 'assigned'
+    def quants_reserve(self, cr, uid, quants, move, link=False, context=None):
+        '''This function reserves quants for the given move (and optionally given link). If the total of quantity reserved is enough, the move's state
+        is also set to 'assigned'
+
         :param quants: list of tuple(quant browse record or None, qty to reserve). If None is given as first tuple element, the item will be ignored
         :param move: browse record
+        :param link: browse record (stock.move.operation.link)
         '''
         toreserve = []
         #split quants if needed
@@ -272,40 +300,77 @@ class stock_quant(osv.osv):
             toreserve.append(quant.id)
         #reserve quants
         if toreserve:
-            self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id}, context=context)
+            self.write(cr, SUPERUSER_ID, toreserve, {'reservation_id': move.id, 'link_move_operation_id': link and link.id or False}, context=context)
         #check if move'state needs to be set as 'assigned'
         move.refresh()
-        if sum([q.qty for q in move.reserved_quant_ids]) == move.product_qty and move.state == 'confirmed':
+        if sum([q.qty for q in move.reserved_quant_ids]) == move.product_qty and move.state in ('confirmed', 'waiting'):
             self.pool.get('stock.move').write(cr, uid, [move.id], {'state': 'assigned'}, context=context)
 
+    def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, context=None):
+        """Moves all given stock.quant in the destination location of the given move.
 
-    # add location_dest_id in parameters (False=use the destination of the move)
-    def quants_move(self, cr, uid, quants, move, lot_id=False, owner_id=False, package_id=False, context=None):
+        :param quants: list of tuple(browse record(stock.quant) or None, quantity to move)
+        :param move: browse record (stock.move)
+        :param lot_id: ID of the lot that mus be set on the quants to move
+        :param owner_id: ID of the partner that must own the quants to move
+        :param src_package_id: ID of the package that contains the quants to move
+        :param dest_package_id: ID of the package that must be set on the moved quant
+        """
         for quant, qty in quants:
-            #quant may be a browse record or None
-            quant_record = self.move_single_quant(cr, uid, quant, qty, move, lot_id=lot_id, package_id=package_id, context=context)
-            #quant_record is the quant newly created or already split
-            self._quant_reconcile_negative(cr, uid, quant_record, context=context)
-
-    def check_preferred_location(self, cr, uid, move, context=None):
-        if move.putaway_ids and move.putaway_ids[0]:
-            #Take only first suggestion for the moment
-            return move.putaway_ids[0].location_id
-        return move.location_dest_id
-
-    def move_single_quant(self, cr, uid, quant, qty, move, lot_id=False, owner_id=False, package_id=False, context=None):
-        if not quant:
-            quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, package_id=package_id, context=context)
-        else:
-            self._quant_split(cr, uid, quant, qty, context=context)
-        # FP Note: improve this using preferred locations
-        location_to = self.check_preferred_location(cr, uid, move, context=context)
-        self.write(cr, SUPERUSER_ID, [quant.id], {
+            if not quant:
+                #If quant is None, we will create a quant to move (and potentially a negative counterpart too)
+                quant = self._quant_create(cr, uid, qty, move, lot_id=lot_id, owner_id=owner_id, src_package_id=src_package_id, dest_package_id=dest_package_id, context=context)
+            self.move_single_quant_tuple(cr, uid, quant, qty, move, context=context)
+
+    def check_preferred_location(self, cr, uid, move, qty, context=None):
+        '''Checks the preferred location on the move, if any returned by a putaway strategy, and returns a list of
+        tuple(location, qty) where the quant have to be moved
+
+        :param move: browse record (stock.move)
+        :param qty: float
+        :returns: list of tuple build as [(browe record (stock.move), float)]
+        '''
+        if move.putaway_ids:
+            res = []
+            for record in move.putaway_ids:
+                res.append((record.location_id, record.quantity))
+            return res
+        return [(move.location_dest_id, qty)]
+
+    def move_single_quant(self, cr, uid, quant, location_to, qty, move, context=None):
+        '''Moves the given 'quant' in 'location_to' for the given 'qty', and logs the stock.move that triggered this move in the quant history.
+        If needed, the quant may be split if it's not totally moved.
+
+        :param quant: browse record (stock.quant)
+        :param location_to: browse record (stock.location)
+        :param qty: float
+        :param move: browse record (stock.move)
+        '''
+        new_quant = self._quant_split(cr, uid, quant, qty, context=context)
+        vals = {
             'location_id': location_to.id,
-            'history_ids': [(4, move.id)]
-        })
+            'history_ids': [(4, move.id)],
+        }
+        #if the quant we are moving had been split and was inside a package, it means we unpacked it
+        if new_quant and new_quant.package_id:
+            vals['package_id'] = False
+        self.write(cr, SUPERUSER_ID, [quant.id], vals, context=context)
         quant.refresh()
-        return quant
+        return new_quant
+
+    def move_single_quant_tuple(self, cr, uid, quant, qty, move, context=None):
+        '''Effectively process the move of a tuple (quant record, qty to move). This may result in several quants moved
+        if the preferred locations on the move say so but by default it will only move the quant record given as argument
+        :param quant: browse record (stock.quant)
+        :param qty: float
+        :param move: browse record (stock.move)
+        '''
+        for location_to, qty in self.check_preferred_location(cr, uid, move, qty, context=context):
+            if not quant:
+                break
+            new_quant = self.move_single_quant(cr, uid, quant, location_to, qty, move, context=context)
+            self._quant_reconcile_negative(cr, uid, quant, context=context)
+            quant = new_quant
 
     def quants_get_prefered_domain(self, cr, uid, location, product, qty, domain=None, prefered_domain=False, fallback_domain=False, restrict_lot_id=False, restrict_partner_id=False, context=None):
         ''' This function tries to find quants in the given location for the given domain, by trying to first limit
@@ -358,7 +423,7 @@ class stock_quant(osv.osv):
                 raise osv.except_osv(_('Error!'), _('Removal strategy %s not implemented.' % (removal_strategy,)))
         return result
 
-    def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, package_id = False, force_location=False, context=None):
+    def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location=False, context=None):
         '''Create a quant in the destination location and create a negative quant in the source location if it's an internal location.
         '''
         if context is None:
@@ -371,10 +436,11 @@ class stock_quant(osv.osv):
             'qty': qty,
             'cost': price_unit,
             'history_ids': [(4, move.id)],
-            'in_date': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
+            'in_date': datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT),
             'company_id': move.company_id.id,
             'lot_id': lot_id,
             'owner_id': owner_id,
+            'package_id': dest_package_id,
         }
 
         if move.location_id.usage == 'internal':
@@ -385,7 +451,7 @@ class stock_quant(osv.osv):
             negative_vals['qty'] = -qty
             negative_vals['cost'] = price_unit
             negative_vals['negative_dest_location_id'] = move.location_dest_id.id
-            negative_vals['package_id'] = package_id
+            negative_vals['package_id'] = src_package_id
             negative_quant_id = self.create(cr, SUPERUSER_ID, negative_vals, context=context)
             vals.update({'propagated_from_id': negative_quant_id})
 
@@ -409,23 +475,6 @@ class stock_quant(osv.osv):
                 move = m
         return move
 
-    #def _reconcile_single_negative_quant(self, cr, uid, to_solve_quant, quant, quant_neg, qty, context=None):
-    #    move = self._get_latest_move(cr, uid, to_solve_quant, context=context)
-    #    remaining_solving_quant = self._quant_split(cr, uid, quant, qty, context=context)
-    #    remaining_to_solve_quant = self._quant_split(cr, uid, to_solve_quant, qty, context=context)
-    #    remaining_neg_quant = self._quant_split(cr, uid, quant_neg, -qty, context=context)
-    #    #if the reconciliation was not complete, we need to link together the remaining parts
-    #    if remaining_to_solve_quant and remaining_neg_quant:
-    #        self.write(cr, uid, remaining_to_solve_quant.id, {'propagated_from_id': remaining_neg_quant.id}, context=context)
-    #    #delete the reconciled quants, as it is replaced by the solving quant
-    #    if remaining_neg_quant:
-    #        otherquant_ids = self.search(cr, uid, [('propagated_from_id', '=', quant_neg.id)], context=context)
-    #        self.write(cr, uid, otherquant_ids, {'propagated_from_id': remaining_neg_quant.id}, context=context)
-    #    self.unlink(cr, SUPERUSER_ID, [quant_neg.id, to_solve_quant.id], context=context)
-    #    #call move_single_quant to ensure recursivity if necessary and do the stock valuation
-    #    self.move_single_quant(cr, uid, quant, qty, move, context=context)
-    #    return remaining_solving_quant, remaining_to_solve_quant
-
     def _quants_merge(self, cr, uid, solved_quant_ids, solving_quant, context=None):
         path = []
         for move in solving_quant.history_ids:
@@ -476,25 +525,12 @@ class stock_quant(osv.osv):
             self.unlink(cr, SUPERUSER_ID, [solving_quant.id], context=context)
             solving_quant = remaining_solving_quant
 
-            #solving_quant, dummy = self._reconcile_single_negative_quant(cr, uid, to_solve_quant, solving_quant, quant_neg, qty, context=context)
-
     def _price_update(self, cr, uid, ids, newprice, context=None):
         self.write(cr, SUPERUSER_ID, ids, {'cost': newprice}, context=context)
 
-    def write(self, cr, uid, ids, vals, context=None):
-        #We want to trigger the move with nothing on reserved_quant_ids for the store of the remaining quantity
-        if 'reservation_id' in vals:
-            reservation_ids = self.browse(cr, uid, ids, context=context)
-            moves_to_warn = set()
-            for reser in reservation_ids:
-                if reser.reservation_id:
-                    moves_to_warn.add(reser.reservation_id.id)
-            self.pool.get('stock.move').write(cr, uid, list(moves_to_warn), {'reserved_quant_ids': []}, context=context)
-        return super(stock_quant, self).write(cr, SUPERUSER_ID, ids, vals, context=context)
-
     def quants_unreserve(self, cr, uid, move, context=None):
         related_quants = [x.id for x in move.reserved_quant_ids]
-        return self.write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False, 'reservation_op_id': False}, context=context)
+        return self.write(cr, SUPERUSER_ID, related_quants, {'reservation_id': False, 'link_move_operation_id': False}, context=context)
 
     def _quants_get_order(self, cr, uid, location, product, quantity, domain=[], orderby='in_date', context=None):
         ''' Implementation of removal strategies
@@ -502,6 +538,9 @@ class stock_quant(osv.osv):
         '''
         domain += location and [('location_id', 'child_of', location.id)] or []
         domain += [('product_id', '=', product.id)] + domain
+        #don't take into account location that are production, supplier or inventory
+        ignore_location_ids = self.pool.get('stock.location').search(cr, uid, [('usage', 'in', ('production', 'supplier', 'inventory'))], context=context)
+        domain.append(('location_id','not in',ignore_location_ids))
         res = []
         offset = 0
         while quantity > 0:
@@ -535,30 +574,11 @@ class stock_quant(osv.osv):
     def _check_location(self, cr, uid, ids, context=None):
         for record in self.browse(cr, uid, ids, context=context):
             if record.location_id.usage == 'view':
-                raise osv.except_osv(_('Error'), _('You cannot move product %s to a location of type view %s.')% (record.product_id.name, record.location_id.name))
+                raise osv.except_osv(_('Error'), _('You cannot move product %s to a location of type view %s.') % (record.product_id.name, record.location_id.name))
         return True
 
-    # FP Note: rehab this, with the auto creation algo
-    # def _check_tracking(self, cr, uid, ids, context=None):
-    #     """ Checks if serial number is assigned to stock move or not.
-    #     @return: True or False
-    #     """
-    #     for move in self.browse(cr, uid, ids, context=context):
-    #         if not move.lot_id and \
-    #            (move.state == 'done' and \
-    #            ( \
-    #                (move.product_id.track_production and move.location_id.usage == 'production') or \
-    #                (move.product_id.track_production and move.location_dest_id.usage == 'production') or \
-    #                (move.product_id.track_incoming and move.location_id.usage == 'supplier') or \
-    #                (move.product_id.track_outgoing and move.location_dest_id.usage == 'customer') or \
-    #                (move.product_id.track_incoming and move.location_id.usage == 'inventory') \
-    #            )):
-    #             return False
-    #     return True
-
     _constraints = [
         (_check_location, 'You cannot move products to a location of the type view.', ['location_id'])
-    #    (_check_tracking, 'You must assign a serial number for this product.', ['prodlot_id']),
     ]
 
 
@@ -596,7 +616,7 @@ class stock_picking(osv.osv):
             where
                 picking_id IN %s
             group by
-                picking_id""",(tuple(ids),))
+                picking_id""", (tuple(ids),))
         for pick, dt1, dt2 in cr.fetchall():
             res[pick]['min_date'] = dt1
             res[pick]['max_date'] = dt2
@@ -608,7 +628,6 @@ class stock_picking(osv.osv):
             ptype_id = vals.get('picking_type_id', context.get('default_picking_type_id', False))
             sequence_id = self.pool.get('stock.picking.type').browse(cr, user, ptype_id, context=context).sequence_id.id
             vals['name'] = self.pool.get('ir.sequence').get_id(cr, user, sequence_id, 'id', context=context)
-                
         return super(stock_picking, self).create(cr, user, vals, context)
 
     def _state_get(self, cr, uid, ids, field_name, arg, context=None):
@@ -625,13 +644,13 @@ class stock_picking(osv.osv):
             if all([x.state == 'cancel' for x in pick.move_lines]):
                 res[pick.id] = 'cancel'
                 continue
-            if all([x.state in ('cancel','done') for x in pick.move_lines]):
+            if all([x.state in ('cancel', 'done') for x in pick.move_lines]):
                 res[pick.id] = 'done'
                 continue
 
-            order = {'confirmed':0, 'waiting':1, 'assigned':2}
-            order_inv = dict(zip(order.values(),order.keys()))
-            lst = [order[x.state] for x in pick.move_lines if x.state not in ('cancel','done')]
+            order = {'confirmed': 0, 'waiting': 1, 'assigned': 2}
+            order_inv = dict(zip(order.values(), order.keys()))
+            lst = [order[x.state] for x in pick.move_lines if x.state not in ('cancel', 'done')]
             if pick.move_lines == 'one':
                 res[pick.id] = order_inv[min(lst)]
             else:
@@ -655,63 +674,64 @@ class stock_picking(osv.osv):
 
     def _get_quant_reserved_exist(self, cr, uid, ids, field_name, arg, context=None):
         res = {}
-        for pick in self.browse(cr, uid, ids, context=context):            
+        for pick in self.browse(cr, uid, ids, context=context):
             res[pick.id] = False
             for move in pick.move_lines:
                 if move.reserved_quant_ids:
                     res[pick.id] = True
-                    continue      
+                    continue
         return res
 
     def action_assign_owner(self, cr, uid, ids, context=None):
         for picking in self.browse(cr, uid, ids, context=context):
-            packop_ids = [op.id for op in picking.pack_operation_ids] 
+            packop_ids = [op.id for op in picking.pack_operation_ids]
             self.pool.get('stock.pack.operation').write(cr, uid, packop_ids, {'owner_id': picking.owner_id.id}, context=context)
 
     _columns = {
-        'name': fields.char('Reference', size=64, select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
-        'origin': fields.char('Source Document', size=64, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}, help="Reference of the document", select=True),
-        'backorder_id': fields.many2one('stock.picking', 'Back Order of', states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}, help="If this shipment was split, then this field links to the shipment which contains the already processed part.", select=True),
-        'note': fields.text('Notes', states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
-        'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}, help="It specifies goods to be deliver partially or all at once"),
-        'state': fields.function(_state_get, type="selection", store = {
+        'name': fields.char('Reference', size=64, select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
+        'origin': fields.char('Source Document', size=64, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="Reference of the document", select=True),
+        'backorder_id': fields.many2one('stock.picking', 'Back Order of', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="If this shipment was split, then this field links to the shipment which contains the already processed part.", select=True),
+        'note': fields.text('Notes', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
+        'move_type': fields.selection([('direct', 'Partial'), ('one', 'All at once')], 'Delivery Method', required=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, help="It specifies goods to be deliver partially or all at once"),
+        'state': fields.function(_state_get, type="selection", store={
             'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_type', 'move_lines'], 20),
-            'stock.move': (_get_pickings, ['state', 'picking_id'], 20)}, selection = [
-            ('draft', 'Draft'),
-            ('cancel', 'Cancelled'),
-            ('waiting', 'Waiting Another Operation'),
-            ('confirmed', 'Waiting Availability'),
-            ('assigned', 'Ready to Transfer'),
-            ('done', 'Transferred'),
-            ], string='Status', readonly=True, select=True, track_visibility='onchange', help="""
-            * Draft: not confirmed yet and will not be scheduled until confirmed\n
-            * Waiting Another Operation: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows)\n
-            * Waiting Availability: still waiting for the availability of products\n
-            * Ready to Transfer: products reserved, simply waiting for confirmation.\n
-            * Transferred: has been processed, can't be modified or cancelled anymore\n
-            * Cancelled: has been cancelled, can't be confirmed anymore"""
+            'stock.move': (_get_pickings, ['state', 'picking_id'], 20)}, selection=[
+                ('draft', 'Draft'),
+                ('cancel', 'Cancelled'),
+                ('waiting', 'Waiting Another Operation'),
+                ('confirmed', 'Waiting Availability'),
+                ('assigned', 'Ready to Transfer'),
+                ('done', 'Transferred'),
+                ], string='Status', readonly=True, select=True, track_visibility='onchange', help="""
+                * Draft: not confirmed yet and will not be scheduled until confirmed\n
+                * Waiting Another Operation: waiting for another move to proceed before it becomes automatically available (e.g. in Make-To-Order flows)\n
+                * Waiting Availability: still waiting for the availability of products\n
+                * Ready to Transfer: products reserved, simply waiting for confirmation.\n
+                * Transferred: has been processed, can't be modified or cancelled anymore\n
+                * Cancelled: has been cancelled, can't be confirmed anymore"""
         ),
         'priority': fields.selection([('0', 'Low'), ('1', 'Normal'), ('2', 'High')], string='Priority', required=True),
         'min_date': fields.function(get_min_max_date, multi="min_max_date", fnct_inv=_set_min_date,
-                 store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed"),
+                 store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Scheduled Date', select=1, help="Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves.", track_visibility='onchange'),
         'max_date': fields.function(get_min_max_date, multi="min_max_date",
                  store={'stock.move': (_get_pickings, ['state', 'date_expected'], 20)}, type='datetime', string='Max. Expected Date', select=2, help="Scheduled time for the last part of the shipment to be processed"),
-        'date': fields.datetime('Commitment Date', help="Date promised for the completion of the transfer order, usually set the time of the order and revised later on.", select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
-        'date_done': fields.datetime('Date of Transfer', help="Date of Completion", states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
+        'date': fields.datetime('Commitment Date', help="Date promised for the completion of the transfer order, usually set the time of the order and revised later on.", select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}, track_visibility='onchange'),
+        'date_done': fields.datetime('Date of Transfer', help="Date of Completion", states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
         'move_lines': fields.one2many('stock.move', 'picking_id', 'Internal Moves', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
         'quant_reserved_exist': fields.function(_get_quant_reserved_exist, type='boolean', string='Quant already reserved ?', help='technical field used to know if there is already at least one quant reserved on moves of a given picking'),
-        'partner_id': fields.many2one('res.partner', 'Partner', states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
-        'company_id': fields.many2one('res.company', 'Company', required=True, select=True, states={'done':[('readonly', True)], 'cancel':[('readonly',True)]}),
+        'partner_id': fields.many2one('res.partner', 'Partner', states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
+        'company_id': fields.many2one('res.company', 'Company', required=True, select=True, states={'done': [('readonly', True)], 'cancel': [('readonly', True)]}),
         'pack_operation_ids': fields.one2many('stock.pack.operation', 'picking_id', string='Related Packing Operations'),
         'pack_operation_exist': fields.function(_get_pack_operation_exist, type='boolean', string='Pack Operation Exists?', help='technical field for attrs in view'),
         'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type', required=True),
+        'picking_type_code': fields.related('picking_type_id', 'code', type='char', string='Picking Type Code', help="Technical field used to display the correct label on print button in the picking view"),
 
         'owner_id': fields.many2one('res.partner', 'Owner', help="Default Owner"),
         # Used to search on pickings
-        'product_id': fields.related('move_lines', 'product_id', type='many2one', relation='product.product', string='Product'),#?
+        'product_id': fields.related('move_lines', 'product_id', type='many2one', relation='product.product', string='Product'),
         'location_id': fields.related('move_lines', 'location_id', type='many2one', relation='stock.location', string='Location', readonly=True),
         'location_dest_id': fields.related('move_lines', 'location_dest_id', type='many2one', relation='stock.location', string='Destination Location', readonly=True),
-        'group_id': fields.related('move_lines', 'group_id', type='many2one', relation='procurement.group', string='Procurement Group', readonly=True, 
+        'group_id': fields.related('move_lines', 'group_id', type='many2one', relation='procurement.group', string='Procurement Group', readonly=True,
               store={
                   'stock.picking': (lambda self, cr, uid, ids, ctx: ids, ['move_lines'], 10),
                   'stock.move': (_get_pickings, ['group_id', 'picking_id'], 10),
@@ -723,7 +743,7 @@ class stock_picking(osv.osv):
         'state': 'draft',
         'move_type': 'one',
         'priority': '1',  # normal
-        'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+        'date': fields.datetime.now,
         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.picking', context=c)
     }
     _sql_constraints = [
@@ -740,12 +760,30 @@ class stock_picking(osv.osv):
         if not default.get('backorder_id'):
             default['backorder_id'] = False
         default['pack_operation_ids'] = []
+        default['date_done'] = False
         return super(stock_picking, self).copy(cr, uid, id, default, context)
 
+    def do_print_delivery(self, cr, uid, ids, context=None):
+        '''This function prints the delivery order'''
+        assert len(ids) == 1, 'This option should only be used for a single id at a time'
+        datas = {
+                 'model': 'stock.picking',
+                 'ids': ids,
+        }
+        return {'type': 'ir.actions.report.xml', 'report_name': 'stock.picking.list', 'datas': datas, 'nodestroy': True}
+
+    def do_print_picking(self, cr, uid, ids, context=None):
+        '''This function prints the picking list'''
+        assert len(ids) == 1, 'This option should only be used for a single id at a time'
+        datas = {
+                 'model': 'stock.picking',
+                 'ids': ids,
+        }
+        return {'type': 'ir.actions.report.xml', 'report_name': 'stock.picking.list.internal', 'datas': datas, 'nodestroy': True}
+
     def action_confirm(self, cr, uid, ids, context=None):
         todo = []
         todo_force_assign = []
-        
         for picking in self.browse(cr, uid, ids, context=context):
             if picking.picking_type_id.auto_force_assign:
                 todo_force_assign.append(picking.id)
@@ -765,46 +803,14 @@ class stock_picking(osv.osv):
         also impact the state of the picking as it is computed based on move's states.
         @return: True
         """
-        def build_domain(record):
-            op = record.operation_id
-            move = record.move_id
-            domain = [('product_id', '=', move.product_id.id)]
-            if op.package_id:
-                domain.append(('id', 'in', package_obj.get_content(cr, uid, [op.package_id.id], context=context)))
-            elif op.quant_id:
-                domain.append(('id', '=', op.quant_id.id))
-            elif op.lot_id:
-                domain.append(('lot_id', '=', op.lot_id.id))
-            else:
-                #fallback on the default behavior
-                return []
-            return domain
-
-        package_obj = self.pool.get('stock.quant.package')
-        quant_obj = self.pool.get('stock.quant')
-        deferred_move_ids = []
         for pick in self.browse(cr, uid, ids, context=context):
             if pick.state == 'draft':
                 self.action_confirm(cr, uid, [pick.id], context=context)
+            #skip the moves that don't need to be checked
             move_ids = [x.id for x in pick.move_lines if x.state not in ('draft', 'cancel', 'done')]
             if not move_ids:
                 raise osv.except_osv(_('Warning!'), _('Nothing to check the availability for.'))
-            for move in pick.move_lines:
-                #skip the move that doesn't need to be checked
-                if move.state in ('draft', 'cancel', 'done'):
-                    continue
-                #assign quants based on the pack operations of the picking
-                for record in move.linked_move_operation_ids:
-                    domain = build_domain(record)
-                    if domain:
-                        quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, record.qty, domain=domain)
-                        quant_obj.quants_reserve(cr, uid, quants, move, context=context)
-                #if the move is still not totally available (not possible to reserve everything based on the pack operations),
-                #queue this move in the list to recompute based on generic action_assign of the move.
-                if move.state != 'assigned':
-                    deferred_move_ids.append(move.id)
-        if deferred_move_ids:
-            self.pool.get('stock.move').action_assign(cr, uid, deferred_move_ids, context=context)
+            self.pool.get('stock.move').action_assign(cr, uid, move_ids, context=context)
         return True
 
     def force_assign(self, cr, uid, ids, context=None):
@@ -816,13 +822,13 @@ class stock_picking(osv.osv):
             self.pool.get('stock.move').force_assign(cr, uid, move_ids, context=context)
         return True
 
-    def cancel_assign(self, cr, uid, ids, *args):
-        """ Cancels picking and moves.
+    def cancel_assign(self, cr, uid, ids, context=None):
+        """ Cancels picking for the moves that are in the assigned state
         @return: True
         """
-        for pick in self.browse(cr, uid, ids):
-            move_ids = [x.id for x in pick.move_lines]
-            self.pool.get('stock.move').cancel_assign(cr, uid, move_ids)
+        for pick in self.browse(cr, uid, ids, context=context):
+            move_ids = [x.id for x in pick.move_lines if x not in ['done', 'cancel']]
+            self.pool.get('stock.move').cancel_assign(cr, uid, move_ids, context=context)
         return True
 
     def action_cancel(self, cr, uid, ids, context=None):
@@ -844,32 +850,37 @@ class stock_picking(osv.osv):
                     self.pool.get('stock.move').action_confirm(cr, uid, [move.id],
                         context=context)
                     todo.append(move.id)
-                elif move.state in ('assigned','confirmed'):
+                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)
         return True
 
     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):
-            ids2 = [move.id for move in pick.move_lines]
-            move_obj.action_cancel(cr, uid, ids2, context=context)
-            move_obj.unlink(cr, uid, ids2, 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)
 
-    # Methods for partial pickings
+    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 _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.
+        """ 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 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')]
+        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',[])]
+            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, {
@@ -882,20 +893,22 @@ class stock_picking(osv.osv):
             self.message_post(cr, uid, picking.id, body=_("Back order <em>%s</em> <b>created</b>.") % (back_order_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.pool.get("stock.picking").action_confirm(cr, uid, [picking.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 do_prepare_partial(self, cr, uid, picking_ids, context=None):
+        #TODO refactore me
         context = context or {}
         pack_operation_obj = self.pool.get('stock.pack.operation')
         pack_obj = self.pool.get("stock.quant.package")
         quant_obj = self.pool.get("stock.quant")
         for picking in self.browse(cr, uid, picking_ids, context=context):
             for move in picking.move_lines:
-                if move.state != 'assigned': continue
+                if move.state != 'assigned':
+                    continue
                 #Check which of the reserved quants are entirely in packages (can be in separate method)
                 packages = list(set([x.package_id for x in move.reserved_quant_ids if x.package_id]))
                 done_packages = []
@@ -940,7 +953,6 @@ class stock_picking(osv.osv):
                     pack_operation_obj.create(cr, uid, {
                         'picking_id': picking.id,
                         'product_qty': qty,
-                        'quant_id': quant.id,
                         'product_id': quant.product_id.id,
                         'lot_id': quant.lot_id and quant.lot_id.id or False,
                         'product_uom_id': quant.product_id.uom_id.id,
@@ -957,13 +969,6 @@ class stock_picking(osv.osv):
                         'cost': move.product_id.standard_price,
                     }, context=context)
 
-    def available_quants_assign(self, cr, uid, picking_ids, context=None):
-        '''Recompute the availability of quants for the given picking and assign them on the stock moves'''
-        #first unreserve the quants of related picking, because this function could be used to unreserve
-        #quants on several pickings in order to group the available ones on a single picking.
-        self.do_unreserve(cr, uid, picking_ids, context=context)
-        return self.action_assign(cr, uid, picking_ids, context=context)
-
     def do_unreserve(self, cr, uid, picking_ids, context=None):
         """
           Will remove all quants for picking in picking_ids
@@ -978,10 +983,11 @@ class stock_picking(osv.osv):
         def _create_link_for_product(product_id, qty):
             qty_to_assign = qty
             for move in picking.move_lines:
-                if move.product_id.id == product_id:
+                if move.product_id.id == product_id and move.state not in ['done', 'cancel']:
                     qty_on_link = min(move.remaining_qty, qty_to_assign)
                     link_obj.create(cr, uid, {'move_id': move.id, 'operation_id': op.id, 'qty': qty_on_link}, context=context)
                     qty_to_assign -= qty_on_link
+                    move.refresh()
                     if qty_to_assign <= 0:
                         break
 
@@ -993,169 +999,16 @@ class stock_picking(osv.osv):
                 to_unlink_ids = [x.id for x in op.linked_move_operation_ids]
                 if to_unlink_ids:
                     link_obj.unlink(cr, uid, to_unlink_ids, context=context)
-                if op.package_id:
-                    for product_id, qty in package_obj._get_all_products_quantities(cr, uid, op.package_id.id, context=context).items():
-                        _create_link_for_product(product_id, qty)
-                elif op.product_id:
+                if op.product_id:
                     normalized_qty = uom_obj._compute_qty(cr, uid, op.product_uom_id.id, op.product_qty, op.product_id.uom_id.id)
                     _create_link_for_product(op.product_id.id, normalized_qty)
-
-
-    #def _reserve_quants_ops_move(self, cr, uid, ops, move, qty, create=False, context=None):
-    #    """
-    #      Will return the quantity that could not be reserved
-    #    """
-    #    quant_obj = self.pool.get("stock.quant")
-    #    op_obj = self.pool.get("stock.pack.operation")
-    #    if create and move.location_id.usage != 'internal':
-    #        # Create quants
-    #        quant = quant_obj._quant_create(cr, uid, qty, move, lot_id=ops.lot_id and ops.lot_id.id or False, owner_id=ops.owner_id and ops.owner_id.id or False, context=context)
-    #        quant.write({'reservation_op_id': ops.id, 'location_id': move.location_id.id})
-    #        quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
-    #        return 0
-    #    else:
-    #        #Quants get
-    #        dom = op_obj._get_domain(cr, uid, ops, context=context)
-    #        dom = dom + [('reservation_id', 'not in', [x.id for x in move.picking_id.move_lines])]
-    #        quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_domain=[('reservation_id', '=', False)], fallback_domain=[('reservation_id', '!=', False)], context=context)
-    #        res_qty = qty
-    #        for quant in quants:
-    #            if quant[0]:  # If quant can be reserved
-    #                res_qty -= quant[1]
-    #        quant_obj.quants_reserve(cr, uid, quants, move, context=context)
-    #        quant_obj.write(cr, SUPERUSER_ID, [x[0].id for x in quants if x[0]], {'reservation_op_id': ops.id}, context=context)
-    #        return res_qty
-
-
-
-
-### MEMO                
-###remaining_qty sur move et pack sont des champ calculés grace Ã  des one2many vers une nouvelle table(move_id, pack_id, qty_in_move_uom)
-###check_availability() (pas sur du nom)
-###1) unreserve
-###2) action_assign
-###
-###build_domain()
-###if op.package_id: return [('product_id', '=', move.produc_id), ('id', 'in', _get_all_quants)]
-###elif op.quant_id: return [('product_id', '=', move.produc_id), ('id', '=', op.quant_id)]
-###elif op.lot_id: return [('product_id', '=', move.producT_id), ('lot_id', '=', op.lot_id)]
-###else: return [] #fallback on the default behavior
-###
-###
-###do_transfer: 
-###1) create extra moves based on remaining_qty of pack_operations
-###2) check_availability
-###2) a) unreserve
-###2) b) assign quants: loop on move_lines, 
-###    for record in move.newtablelink:
-###        domain = buil_domain(record)
-###        if domain:
-###            quants = quant_get(domain, record.qty)
-###            for quant, qtty in quants:
-###                if quant:
-###                    reserve quant for move (split)
-###                else: rien
-###    if move pas totallement available:
-###        action_assign(move) #mieux de faire action_assign car ca va checker le move précedent pyus faure un fallback (/!\ qty?)
-###3) action_done du move: base sur les quants assigned (semble ok pour l'instant)
-###4) packaging    (donc hors du action_done du move)
-
-### package_obj._get_remaining_prod_quantities(operation):
-###res = {}
-####total found per product included in the operation
-###for record in operation.linked_move_operation_ids:
-###    if record.product_id.id not in res:
-###        res[record.product_id.id] = 0
-###    res[record.product_id.id] += record.qty
-####total per product included int the package
-###res2 = {}
-###for quant in quant_obj.browse(cr, uid, package_obj._get_content_package(cr, uid, op.package_id, context=context))
-###    if quant.product_id.id not in res2:
-###        res2[quant.product_id.id] = 0
-###    res2[quant.product_id.id] += quant.qty
-
-
-    
-
-
-    #def rereserve(self, cr, uid, picking_ids, create=False, context=None):
-    #    """
-    #        This will unreserve all products and reserve the quants from the operations again
-    #        :return: Tuple (res, res2, resneg)
-    #            res: dictionary of ops with quantity that could not be processed matching ops and moves
-    #            res2: dictionary of moves with quantity that could not be processed matching ops and moves
-    #            resneg: the negative quants to be created: resneg[move][ops] gives negative quant to be created
-    #        tuple of dictionary with quantities of quant operation and product that can not be matched between ops and moves
-    #        and dictionary with remaining values on moves
-    #    """
-    #    quant_obj = self.pool.get("stock.quant")
-    #    pack_obj = self.pool.get("stock.quant.package")
-    #    uom_obj = self.pool.get('product.uom')
-    #    res = {} # Qty still to do from ops
-    #    res2 = {} #what is left from moves
-    #    resneg= {} #Number of negative quants to create for move/op
-    #    for picking in self.browse(cr, uid, picking_ids, context=context):
-    #        products_moves = {}
-    #        # unreserve everything and initialize res2
-    #        for move in picking.move_lines:
-    #            quant_obj.quants_unreserve(cr, uid, move, context=context)
-    #            res2[move.id] = move.product_qty
-    #            resneg[move.id] = {}
-    #            if move.state in ('confirmed', 'assigned'):
-    #                products_moves.setdefault(move.product_id.id, []).append(move)
-    #            
-    #            
-    #        # Resort pack_operation_ids such that package transfers happen first and then the most specific operations from the product
-    #        
-    #        orderedpackops = picking.pack_operation_ids
-    #        orderedpackops.sort(key = lambda x: ((x.package_id and not x.product_id) and -3 or 0) + (x.package_id and -1 or 0) + (x.lot_id and -1 or 0))
-
-    #        for ops in orderedpackops:
-    #            #If a product is specified in the ops, search for appropriate quants
-    #            if ops.product_id:
-    #                # Match with moves
-    #                move_ids = ops.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[ops.product_id.id]) or []
-    #                qty_to_do = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, to_uom_id=ops.product_id.uom_id.id)
-    #                while qty_to_do > 0 and move_ids:
-    #                    move = move_ids.pop()
-    #                    if res2[move.id] > qty_to_do: 
-    #                        qty = qty_to_do
-    #                        qty_to_do = 0
-    #                    else:
-    #                        qty = res2[move.id]
-    #                        qty_to_do -= res2[move.id]
-    #                    neg_qty = self._reserve_quants_ops_move(cr, uid, ops, move, qty, create=create, context=context)
-    #                    if neg_qty > 0:
-    #                        resneg[move.id].setdefault(ops.id, 0)
-    #                        resneg [move.id][ops.id] += neg_qty
-    #                    res2[move.id] -= qty
-    #                res[ops.id] = {}
-    #                res[ops.id][ops.product_id.id] = qty_to_do
-    #            # In case only a package is specified, take all the quants from the package
-    #            elif ops.package_id:
-    #                quants = quant_obj.browse(cr, uid, pack_obj.get_content(cr, uid, [ops.package_id.id], context=context))
-    #                quants = [x for x in quants if x.qty > 0] #Negative quants should not be moved
-    #                for quant in quants:
-    #                    # Match with moves
-    #                    move_ids = quant.product_id.id in products_moves and filter(lambda x: res2[x.id] > 0, products_moves[quant.product_id.id]) or []
-    #                    qty_to_do = quant.qty
-    #                    while qty_to_do > 0 and move_ids:
-    #                        move = move_ids.pop()
-    #                        if res2[move.id] > qty_to_do:
-    #                            qty = qty_to_do
-    #                            qty_to_do = 0.0
-    #                        else:
-    #                            qty = res2[move.id]
-    #                            qty_to_do -= res2[move.id]
-    #                        quant_obj.quants_reserve(cr, uid, [(quant, qty)], move, context=context)
-    #                        quant_obj.write(cr, uid, [quant.id], {'reservation_op_id': ops.id}, context=context)
-    #                        res2[move.id] -= qty
-    #                    res.setdefault(ops.id, {}).setdefault(quant.product_id.id, 0.0)
-    #                    res[ops.id][quant.product_id.id] += qty_to_do
-    #    return (res, res2, resneg)
+                elif op.package_id:
+                    for product_id, qty in package_obj._get_all_products_quantities(cr, uid, op.package_id.id, context=context).items():
+                        _create_link_for_product(product_id, qty)
 
     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
+        '''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')
@@ -1177,6 +1030,7 @@ class stock_picking(osv.osv):
         self.do_recompute_remaining_quantities(cr, uid, [picking.id], 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)
@@ -1193,13 +1047,12 @@ class stock_picking(osv.osv):
         if not context:
             context = {}
         stock_move_obj = self.pool.get('stock.move')
-        pack_obj = self.pool.get('stock.quant.package')
-        quant_obj = self.pool.get('stock.quant')
         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:
+                self.do_recompute_remaining_quantities(cr, uid, [picking.id], context=context)
                 #create extra moves in the picking (unexpected product moves coming from pack operations)
                 self._create_extra_moves(cr, uid, picking, context=context)
                 picking.refresh()
@@ -1207,29 +1060,25 @@ class stock_picking(osv.osv):
                 todo_move_ids = []
                 toassign_move_ids = []
                 for move in picking.move_lines:
-                    if move.state == 'draft':
+                    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 move.remaining_qty == 0:
                         if move.state in ('draft', 'assigned', 'confirmed'):
                             todo_move_ids.append(move.id)
                     elif move.remaining_qty > 0:
                         new_move = stock_move_obj.split(cr, uid, move, move.remaining_qty, context=context)
+                        todo_move_ids.append(move.id)
                         #Assign move as it was assigned before
                         toassign_move_ids.append(new_move)
-                    else:
+                    elif move.state:
                         #this should never happens
                         raise
                 self.rereserve_quants(cr, uid, picking, move_ids=todo_move_ids, 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)
-                    #Packing:
-                    for ops in picking.pack_operation_ids:
-                        if ops.result_package_id:
-                            if ops.package_id:
-                                pack_obj.write(cr, uid, [ops.package_id.id], {'parent_id': ops.result_package_id.id}, context=context)
-                            else:
-                                for record in ops.linked_move_operation_ids:
-                                    quant_obj.write(cr, uid, [q.id for q in record.move_id.quant_ids], {'package_id': ops.result_package_id.id}, context=context)
                 elif context.get('do_only_split'):
                     context.update({'split': todo_move_ids})
             picking.refresh()
@@ -1237,72 +1086,33 @@ class stock_picking(osv.osv):
             if toassign_move_ids:
                 stock_move_obj.action_assign(cr, uid, toassign_move_ids, context=context)
         return True
-                ## Rereserve quants
-                ## TODO: quants could have been created already in Supplier, so create parameter could disappear
-                #res = self.rereserve(cr, uid, [picking.id], create = True, context = context) #This time, quants need to be created 
-                #resneg = res[2]
-                #orig_moves = picking.move_lines
-                #orig_qtys = {}
-                #for orig in orig_moves:
-                #    orig_qtys[orig.id] = orig.product_qty
-                ##Add moves that operations need extra
-                #extra_moves = []
-                #for ops in res[0].keys():
-                #    for prod in res[0][ops].keys():
-                #        product = self.pool.get('product.product').browse(cr, uid, prod, context=context)
-                #        qty = res[0][ops][prod]
-                #        if qty > 0:
-                #            #Create moves for products too many on operation
-                #            move_id = stock_move_obj.create(cr, uid, {
-                #                            'name': product.name,
-                #                            'product_id': product.id,
-                #                            'product_uom_qty': qty,
-                #                            'product_uom': product.uom_id.id,
-                #                            'location_id': picking.location_id.id,
-                #                            'location_dest_id': picking.location_dest_id.id,
-                #                            'picking_id': picking.id,
-                #                            'picking_type_id': picking.picking_type_id.id,
-                #                            'group_id': picking.group_id.id,
-                #                        }, context=context)
-                #            stock_move_obj.action_confirm(cr, uid, [move_id], context=context)
-                #            move = stock_move_obj.browse(cr, uid, move_id, context=context)
-                #            ops_rec = self.pool.get("stock.pack.operation").browse(cr, uid, ops, context=context)
-                #            resneg[move_id] = {}
-                #            resneg[move_id][ops] = self._reserve_quants_ops_move(cr, uid, ops_rec, move, qty, create=True, context=context)
-                #            extra_moves.append(move_id)
-                #res2 = res[1]
-                ##Backorder
-                #for move in res2.keys():
-                #    if res2[move] > 0:
-                #        mov = stock_move_obj.browse(cr, uid, move, context=context)
-                #        new_move = stock_move_obj.split(cr, uid, mov, res2[move], context=context)
-                #        #Assign move as it was assigned before
-                #        stock_move_obj.action_assign(cr, uid, [new_move], context=context)
-                #todo = []
-                #orig_moves = [x for x in orig_moves if res[1][x.id] < orig_qtys[x.id]]
 
     def do_split(self, cr, uid, picking_ids, context=None):
-        """
-            just split the picking without making it 'done'
-        """
+        """ just split the picking (create a backorder) without making it 'done' """
         if context is None:
             context = {}
         ctx = context.copy()
         ctx['do_only_split'] = True
-        self.do_transfer(cr, uid, picking_ids, context=ctx)
-        return True
-
-    # Methods for the barcode UI
+        return self.do_transfer(cr, uid, picking_ids, context=ctx)
 
-    def get_picking_for_packing_ui(self, cr, uid, context=None):
-        return self.search(cr, uid, [('state', 'in', ('confirmed', 'assigned')), ('picking_type_id', '=', context.get('default_picking_type_id'))], context=context)
-
-    def action_done_from_packing_ui(self, cr, uid, picking_id, only_split_lines=False, context=None):
-        self.do_transfer(cr, uid, picking_id, only_split_lines, context=context)
+    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', ('confirmed', 'assigned'))]
+        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' in pused in the barcode scanner UI """
+        self.do_transfer(cr, uid, [picking_id], context=context)
         #return id of next picking to work on
-        return self.get_picking_for_packing_ui(cr, uid, context=context)
+        return self.get_next_picking_for_ui(cr, uid, context=context)
 
     def action_pack(self, cr, uid, picking_ids, 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. """
         stock_operation_obj = self.pool.get('stock.pack.operation')
         package_obj = self.pool.get('stock.quant.package')
         for picking_id in picking_ids:
@@ -1312,49 +1122,30 @@ class stock_picking(osv.osv):
                 stock_operation_obj.write(cr, uid, operation_ids, {'result_package_id': package_id}, context=context)
         return True
 
-    def _deal_with_quants(self, cr, uid, picking_id, quant_ids, context=None):
-        stock_operation_obj = self.pool.get('stock.pack.operation')
-        todo_on_moves = []
-        todo_on_operations = []
-        for quant in self.pool.get('stock.quant').browse(cr, uid, quant_ids, context=context):
-            tmp_moves, tmp_operations = stock_operation_obj._search_and_increment(cr, uid, picking_id, ('quant_id', '=', quant.id), context=context)
-            todo_on_moves += tmp_moves
-            todo_on_operations += tmp_operations
-        return todo_on_moves, todo_on_operations
-
-    def get_barcode_and_return_todo_stuff(self, cr, uid, picking_id, barcode_str, context=None):
+    def process_product_id_from_ui(self, cr, uid, picking_id, product_id, context=None):
+        return self.pool.get('stock.pack.operation')._search_and_increment(cr, uid, picking_id, [('product_id', '=', product_id)], context=context)
+
+    def process_barcode_from_ui(self, cr, uid, picking_id, barcode_str, context=None):
         '''This function is called each time there barcode scanner reads an input'''
-        #TODO: better error messages handling => why not real raised errors
-        quant_obj = self.pool.get('stock.quant')
+        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')
-        error_msg = ''
-        todo_on_moves = []
-        todo_on_operations = []
         #check if the barcode correspond to a product
         matching_product_ids = product_obj.search(cr, uid, [('ean13', '=', barcode_str)], context=context)
         if matching_product_ids:
-            todo_on_moves, todo_on_operations = stock_operation_obj._search_and_increment(cr, uid, picking_id, ('product_id', '=', matching_product_ids[0]), context=context)
+            self.process_product_id_from_ui(cr, uid, picking_id, matching_product_ids[0], context=context)
 
-        #check if the barcode correspond to a quant
-        matching_quant_ids = quant_obj.search(cr, uid, [('name', '=', barcode_str)], context=context)  # TODO need the location clause
-        if matching_quant_ids:
-            todo_on_moves, todo_on_operations = self._deal_with_quants(cr, uid, picking_id, [matching_quant_ids[0]], context=context)
+        #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)
+            stock_operation_obj._search_and_increment(cr, uid, picking_id, [('product_id', '=', lot.product_id.id), ('lot_id', '=', lot.id)], context=context)
 
         #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:
-            included_package_ids = package_obj.search(cr, uid, [('parent_id', 'child_of', matching_package_ids[0])], context=context)
-            included_quant_ids = quant_obj.search(cr, uid, [('package_id', 'in', included_package_ids)], context=context)
-            todo_on_moves, todo_on_operations = self._deal_with_quants(cr, uid, picking_id, included_quant_ids, context=context)
-        #write remaining qty on stock.move, to ease the treatment server side
-        for todo in todo_on_moves:
-            if todo[0] == 1:
-                self.pool.get('stock.move').write(cr, uid, todo[1], todo[2], context=context)
-            elif todo[0] == 0:
-                self.pool.get('stock.move').create(cr, uid, todo[2], context=context)
-        return {'warnings': error_msg, 'moves_to_update': todo_on_moves, 'operations_to_update': todo_on_operations}
+            stock_operation_obj._search_and_increment(cr, uid, picking_id, [('package_id', '=', matching_package_ids[0])], context=context)
 
 
 class stock_production_lot(osv.osv):
@@ -1376,6 +1167,34 @@ class stock_production_lot(osv.osv):
         ('name_ref_uniq', 'unique (name, ref)', 'The combination of Serial Number and internal reference must be unique !'),
     ]
 
+    def action_traceability(self, cr, uid, ids, context=None):
+        """ 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
+        @param ids: List of IDs selected
+        @param context: A standard dictionary
+        @return: A dictionary of values
+        """
+        quant_obj = self.pool.get("stock.quant")
+        move_obj = self.pool.get("stock.move")
+        quants = quant_obj.search(cr, uid, [('lot_id', 'in', ids)], context=context)
+        moves = set()
+        for quant in quant_obj.browse(cr, uid, quants, context=context):
+            moves |= {move.id for move in quant.history_ids}
+        if moves:
+            return { 
+                'domain': "[('id','in',["+','.join(map(str, list(moves)))+"])]",
+                'name': _('Traceability'),
+                'view_mode': 'tree,form',
+                'view_type': 'form',
+                'context': {'tree_view_ref': 'stock.view_move_tree'},
+                'res_model': 'stock.move',
+                'type': 'ir.actions.act_window',
+                    }
+        return False
+        
+
 
 # ----------------------------------------------------
 # Move
@@ -1402,6 +1221,12 @@ class stock_move(osv.osv):
             res.append((line.id, name))
         return res
 
+    def create(self, cr, uid, vals, context=None):
+        if vals.get('product_id') and not vals.get('price_unit'):
+            prod_obj = self.pool.get('product.product')
+            vals['price_unit'] = prod_obj.browse(cr, uid, vals['product_id'], context=context).standard_price
+        return super(stock_move, self).create(cr, uid, vals, context=context)
+
     def _quantity_normalize(self, cr, uid, ids, name, args, context=None):
         uom_obj = self.pool.get('product.uom')
         res = {}
@@ -1451,19 +1276,25 @@ class stock_move(osv.osv):
                 res.add(quant.reservation_id.id)
         return list(res)
 
+    def _get_move_ids(self, cr, uid, ids, context=None):
+        res = []
+        for picking in self.browse(cr, uid, ids, context=context):
+            res += [x.id for x in picking.move_lines]
+        return res
+
     _columns = {
         'name': fields.char('Description', required=True, select=True),
         'priority': fields.selection([('0', 'Not urgent'), ('1', 'Urgent')], 'Priority'),
         'create_date': fields.datetime('Creation Date', readonly=True, select=True),
         'date': fields.datetime('Date', required=True, select=True, help="Move date: scheduled date until move is done, then date of actual move processing", states={'done': [('readonly', True)]}),
-        'date_expected': fields.datetime('Scheduled Date', states={'done': [('readonly', True)]},required=True, select=True, help="Scheduled date for the processing of this move"),
-        'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type','<>','service')],states={'done': [('readonly', True)]}),
+        'date_expected': fields.datetime('Expected Date', states={'done': [('readonly', True)]}, required=True, select=True, help="Scheduled date for the processing of this move"),
+        'product_id': fields.many2one('product.product', 'Product', required=True, select=True, domain=[('type', '<>', 'service')], states={'done': [('readonly', True)]}),
         # TODO: improve store to add dependency on product UoM
         'product_qty': fields.function(_quantity_normalize, type='float', store=True, string='Quantity',
             digits_compute=dp.get_precision('Product Unit of Measure'),
             help='Quantity in the default UoM of the product'),
         'product_uom_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'),
-            required=True,states={'done': [('readonly', True)]},
+            required=True, states={'done': [('readonly', True)]},
             help="This is the quantity of products from an inventory "
                 "point of view. For moves in the state 'done', this is the "
                 "quantity of products that were actually moved. For other "
@@ -1472,14 +1303,14 @@ class stock_move(osv.osv):
                 "backorder. Changing this quantity on assigned moves affects "
                 "the product reservation, and should be done with care."
         ),
-        'product_uom': fields.many2one('product.uom', 'Unit of Measure', required=True,states={'done': [('readonly', True)]}),
+        'product_uom': fields.many2one('product.uom', 'Unit of Measure', required=True, states={'done': [('readonly', True)]}),
         'product_uos_qty': fields.float('Quantity (UOS)', digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]}),
         'product_uos': fields.many2one('product.uom', 'Product UOS', states={'done': [('readonly', True)]}),
 
         'product_packaging': fields.many2one('product.packaging', 'Prefered Packaging', help="It specifies attributes of packaging like type, quantity of packaging,etc."),
 
-        'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True,states={'done': [('readonly', True)]}, help="Sets a location if you produce at a fixed location. This can be a partner location if you subcontract the manufacturing operations."),
-        'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True,states={'done': [('readonly', True)]}, select=True, help="Location where the system will stock the finished products."),
+        'location_id': fields.many2one('stock.location', 'Source Location', required=True, select=True, states={'done': [('readonly', True)]}, help="Sets a location if you produce at a fixed location. This can be a partner location if you subcontract the manufacturing operations."),
+        'location_dest_id': fields.many2one('stock.location', 'Destination Location', required=True, states={'done': [('readonly', True)]}, select=True, help="Location where the system will stock the finished products."),
 
         # FP Note: should we remove this?
         'partner_id': fields.many2one('res.partner', 'Destination Address ', states={'done': [('readonly', True)]}, help="Optional address where goods are to be delivered, specifically used for allotment"),
@@ -1489,7 +1320,7 @@ class stock_move(osv.osv):
         'move_orig_ids': fields.one2many('stock.move', 'move_dest_id', 'Original Move', help="Optional: previous stock move when chaining them", select=True),
 
         'picking_id': fields.many2one('stock.picking', 'Reference', select=True, states={'done': [('readonly', True)]}),
-        'picking_priority': fields.related('picking_id','priority', type='selection', selection=[('0','Low'),('1','Normal'),('2','High')], string='Picking Priority'),
+        'picking_priority': fields.related('picking_id', 'priority', type='selection', selection=[('0', 'Low'), ('1', 'Normal'), ('2', 'High')], string='Picking Priority', store={'stock.picking': (_get_move_ids, ['priority'], 10)}),
         'note': fields.text('Notes'),
         'state': fields.selection([('draft', 'New'),
                                    ('cancel', 'Cancelled'),
@@ -1504,7 +1335,7 @@ class stock_move(osv.osv):
                        "* Available: When products are reserved, it is set to \'Available\'.\n"\
                        "* Done: When the shipment is processed, the state is \'Done\'."),
 
-        'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when average price costing method is used). Value given in company currency and in product uom."),  # as it's a technical field, we intentionally don't provide the digits attribute
+        'price_unit': fields.float('Unit Price', help="Technical field used to record the product cost set by the user during a picking confirmation (when costing method used is 'average price' or 'real'). Value given in company currency and in product uom."),  # as it's a technical field, we intentionally don't provide the digits attribute
 
         'company_id': fields.many2one('res.company', 'Company', required=True, select=True),
         'backorder_id': fields.related('picking_id', 'backorder_id', type='many2one', relation="stock.picking", string="Back Order of", select=True),
@@ -1512,18 +1343,17 @@ class stock_move(osv.osv):
         'procure_method': fields.selection([('make_to_stock', 'Make to Stock'), ('make_to_order', 'Make to Order')], 'Procurement Method', required=True, help="Make to Stock: When needed, the product is taken from the stock or we wait for replenishment. \nMake to Order: When needed, the product is purchased or produced."),
 
         # used for colors in tree views:
-        'scrapped': fields.related('location_dest_id', 'scrap_location', type='boolean', relation='stock.location',string='Scrapped', readonly=True),
+        'scrapped': fields.related('location_dest_id', 'scrap_location', type='boolean', relation='stock.location', string='Scrapped', readonly=True),
 
         'quant_ids': fields.many2many('stock.quant', 'stock_quant_move_rel', 'move_id', 'quant_id', 'Moved Quants'),
         'reserved_quant_ids': fields.one2many('stock.quant', 'reservation_id', 'Reserved quants'),
         'linked_move_operation_ids': fields.one2many('stock.move.operation.link', 'move_id', string='Linked Operations', readonly=True, help='Operations that impact this move for the computation of the remaining quantities'),
         'remaining_qty': fields.function(_get_remaining_qty, type='float', string='Remaining Quantity',
                                          digits_compute=dp.get_precision('Product Unit of Measure'), states={'done': [('readonly', True)]},),
-                                         #store = {'stock.move': (lambda self, cr, uid, ids, c={}: ids , ['product_uom_qty', 'product_uom', 'reserved_quant_ids'], 20),
-                                         #         'stock.quant': (_get_move, ['reservation_id'], 10)}),
         'procurement_id': fields.many2one('procurement.order', 'Procurement'),
         'group_id': fields.many2one('procurement.group', 'Procurement Group'),
         'rule_id': fields.many2one('procurement.rule', 'Procurement Rule', help='The pull rule that created this stock move'),
+        'push_rule_id': fields.many2one('stock.location.path', 'Push Rule', help='The push rule that created this stock move'),
         'propagate': fields.boolean('Propagate cancel and split', help='If checked, when this move is cancelled, cancel the linked move too'),
         'picking_type_id': fields.many2one('stock.picking.type', 'Picking Type'),
         'inventory_id': fields.many2one('stock.inventory', 'Inventory'),
@@ -1565,9 +1395,9 @@ class stock_move(osv.osv):
         'product_qty': 1.0,
         'product_uom_qty': 1.0,
         'scrapped': False,
-        'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+        'date': fields.datetime.now,
         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.move', context=c),
-        'date_expected': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+        'date_expected': fields.datetime.now,
         'procure_method': 'make_to_stock',
         'propagate': True,
     }
@@ -1597,12 +1427,13 @@ class stock_move(osv.osv):
         return super(stock_move, self).copy_data(cr, uid, id, default, context)
 
     def do_unreserve(self, cr, uid, move_ids, context=None):
-        ids_to_free = []
         quant_obj = self.pool.get("stock.quant")
         for move in self.browse(cr, uid, move_ids, context=context):
-            ids_to_free += [quant.id for quant in move.reserved_quant_ids]
-        if ids_to_free:
-            quant_obj.write(cr, SUPERUSER_ID, ids_to_free, {'reservation_id': False, 'reservation_op_id': False}, context=context)
+            quant_obj.quants_unreserve(cr, uid, move, context=context)
+            putaway_values = []
+            for putaway_rec in move.putaway_ids:
+                putaway_values.append((2, putaway_rec.id))
+            self.write(cr, uid, [move.id], {'state': 'confirmed', 'putaway_ids': putaway_values}, context=context)
 
     def _prepare_procurement_from_move(self, cr, uid, move, context=None):
         origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or "/")
@@ -1629,58 +1460,97 @@ class stock_move(osv.osv):
             'warehouse_id': move.warehouse_id and move.warehouse_id.id or False,
         }
 
-    def _push_apply(self, cr, uid, moves, context):
+    def _push_apply(self, cr, uid, moves, context=None):
         push_obj = self.pool.get("stock.location.path")
         for move in moves:
             if not move.move_dest_id:
-                routes = [x.id for x in move.product_id.route_ids + move.product_id.categ_id.total_route_ids]
-                routes = routes or [x.id for x in move.route_ids]  
-                if routes:
-                    domain = [('route_id', 'in', routes), ('location_from_id', '=', move.location_dest_id.id)]
-                    if move.warehouse_id:
-                        domain += [('warehouse_id', '=', move.warehouse_id.id)]
-                    rules = push_obj.search(cr, uid, domain, context=context)
-                    if rules:
-                        rule = push_obj.browse(cr, uid, rules[0], context=context)
-                        push_obj._apply(cr, uid, rule, move, context=context)
+                domain = [('location_from_id', '=', move.location_dest_id.id)]
+                if move.warehouse_id:
+                    domain += ['|', ('warehouse_id', '=', move.warehouse_id.id), ('warehouse_id', '=', False)]
+                #priority goes to the route defined on the product and product category
+                route_ids = [x.id for x in move.product_id.route_ids + move.product_id.categ_id.total_route_ids]
+                rules = push_obj.search(cr, uid, domain + [('route_id', 'in', route_ids)], order='route_sequence, sequence', context=context)
+                if not rules:
+                    #but if there's no rule matching, we try without filtering on routes
+                    rules = push_obj.search(cr, uid, domain, order='route_sequence, sequence', context=context)
+                if rules:
+                    rule = push_obj.browse(cr, uid, rules[0], context=context)
+                    push_obj._apply(cr, uid, rule, move, context=context)
         return True
 
     # Create the stock.move.putaway records
-    def _putaway_apply(self,cr, uid, ids, context=None):
+    def _putaway_apply(self, cr, uid, move, putaway, context=None):
+        # Should call different methods here in later versions
         moveputaway_obj = self.pool.get('stock.move.putaway')
+        quant_obj = self.pool.get('stock.quant')
+        if putaway.method == 'fixed' and putaway.location_spec_id:
+            for row in quant_obj.read_group(cr, uid, [('reservation_id', '=', move.id)], ['qty', 'lot_id'], ['lot_id'], context=context):
+                vals = {
+                    'move_id': move.id,
+                    'location_id': putaway.location_spec_id.id,
+                    'quantity': row['qty'],
+                    'lot_id': row.get('lot_id') and row['lot_id'][0] or False,
+                }
+                moveputaway_obj.create(cr, SUPERUSER_ID, vals, context=context)
+
+    def _putaway_check(self, cr, uid, ids, context=None):
         for move in self.browse(cr, uid, ids, context=context):
             putaway = self.pool.get('stock.location').get_putaway_strategy(cr, uid, move.location_dest_id, move.product_id, context=context)
             if putaway:
-                # Should call different methods here in later versions
-                # TODO: take care of lots
-                if putaway.method == 'fixed' and putaway.location_spec_id:
-                    moveputaway_obj.create(cr, SUPERUSER_ID, {'move_id': move.id,
-                                                     'location_id': putaway.location_spec_id.id,
-                                                     'quantity': move.product_uom_qty}, context=context)
-        return True
-    
+                self._putaway_apply(cr, uid, move, putaway, context=context)
+
     def _create_procurement(self, cr, uid, move, context=None):
-        """
-            This will create a procurement order
-        """
-        proc_obj = self.pool.get("procurement.order")
-        return proc_obj.create(cr, uid, self._prepare_procurement_from_move(cr, uid, move, context=context))
+        """ This will create a procurement order """
+        return self.pool.get("procurement.order").create(cr, uid, self._prepare_procurement_from_move(cr, uid, move, context=context))
 
-    # Check that we do not modify a stock.move which is done
     def write(self, cr, uid, ids, vals, context=None):
+        if context is None:
+            context = {}
         if isinstance(ids, (int, long)):
             ids = [ids]
+        # Check that we do not modify a stock.move which is done
         frozen_fields = set(['product_qty', 'product_uom', 'product_uos_qty', 'product_uos', 'location_id', 'location_dest_id', 'product_id'])
         for move in self.browse(cr, uid, ids, context=context):
             if move.state == 'done':
                 if frozen_fields.intersection(vals):
                     raise osv.except_osv(_('Operation Forbidden!'),
                         _('Quantities, Units of Measure, Products and Locations cannot be modified on stock moves that have already been processed (except by the Administrator).'))
-        result = super(stock_move, self).write(cr, uid, ids, vals, context=context)
-        return result
-
-    def onchange_quantity(self, cr, uid, ids, product_id, product_qty,
-                          product_uom, product_uos):
+        propagated_changes_dict = {}
+        #propagation of quantity change
+        if vals.get('product_uom_qty'):
+            propagated_changes_dict['product_uom_qty'] = vals['product_uom_qty']
+        if vals.get('product_uom_id'):
+            propagated_changes_dict['product_uom_id'] = vals['product_uom_id']
+        #propagation of expected date:
+        propagated_date_field = False
+        if vals.get('date_expected'):
+            #propagate any manual change of the expected date
+            propagated_date_field = 'date_expected'
+        elif (vals.get('state', '') == 'done' and vals.get('date')):
+            #propagate also any delta observed when setting the move as done
+            propagated_date_field = 'date'
+
+        if not context.get('do_not_propagate', False) and (propagated_date_field or propagated_changes_dict):
+            #any propagation is (maybe) needed
+            for move in self.browse(cr, uid, ids, context=context):
+                if move.move_dest_id and move.propagate:
+                    if 'date_expected' in propagated_changes_dict:
+                        propagated_changes_dict.pop('date_expected')
+                    if propagated_date_field:
+                        current_date = datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
+                        new_date = datetime.strptime(vals.get(propagated_date_field), DEFAULT_SERVER_DATETIME_FORMAT)
+                        delta = new_date - current_date
+                        if abs(delta.days) >= move.company_id.propagation_minimum_delta:
+                            old_move_date = datetime.strptime(move.move_dest_id.date_expected, DEFAULT_SERVER_DATETIME_FORMAT)
+                            new_move_date = (old_move_date + relativedelta.relativedelta(days=delta.days or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+                            propagated_changes_dict['date_expected'] = new_move_date
+                    #For pushed moves as well as for pulled moves, propagate by recursive call of write().
+                    #Note that, for pulled moves we intentionally don't propagate on the procurement.
+                    if propagated_changes_dict:
+                        self.write(cr, uid, [move.move_dest_id.id], propagated_changes_dict, context=context)
+        return super(stock_move, self).write(cr, uid, ids, vals, context=context)
+
+    def onchange_quantity(self, cr, uid, ids, product_id, product_qty, product_uom, product_uos):
         """ On change of product quantity finds UoM and UoS quantities
         @param product_id: Product id
         @param product_qty: Changed Quantity of product
@@ -1693,7 +1563,7 @@ class stock_move(osv.osv):
         }
         warning = {}
 
-        if (not product_id) or (product_qty <=0.0):
+        if (not product_id) or (product_qty <= 0.0):
             result['product_qty'] = 0.0
             return {'value': result}
 
@@ -1705,10 +1575,10 @@ class stock_move(osv.osv):
             for move in self.read(cr, uid, ids, ['product_qty']):
                 if product_qty < move['product_qty']:
                     warning.update({
-                       'title': _('Information'),
-                       'message': _("By changing this quantity here, you accept the "
+                        'title': _('Information'),
+                        'message': _("By changing this quantity here, you accept the "
                                 "new quantity as complete: OpenERP will not "
-                                "automatically generate a back order.") })
+                                "automatically generate a back order.")})
                 break
 
         if product_uos and product_uom and (product_uom != product_uos):
@@ -1732,7 +1602,7 @@ class stock_move(osv.osv):
         }
         warning = {}
 
-        if (not product_id) or (product_uos_qty <=0.0):
+        if (not product_id) or (product_uos_qty <= 0.0):
             result['product_uos_qty'] = 0.0
             return {'value': result}
 
@@ -1743,10 +1613,10 @@ class stock_move(osv.osv):
         for move in self.read(cr, uid, ids, ['product_uos_qty']):
             if product_uos_qty < move['product_uos_qty']:
                 warning.update({
-                   'title': _('Warning: No Back Order'),
-                   'message': _("By changing the quantity here, you accept the "
+                    'title': _('Warning: No Back Order'),
+                    'message': _("By changing the quantity here, you accept the "
                                 "new quantity as complete: OpenERP will not "
-                                "automatically generate a Back Order.") })
+                                "automatically generate a Back Order.")})
                 break
 
         if product_uos and product_uom and (product_uom != product_uos):
@@ -1755,8 +1625,7 @@ class stock_move(osv.osv):
             result['product_uom_qty'] = product_uos_qty
         return {'value': result, 'warning': warning}
 
-    def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False,
-                            loc_dest_id=False, partner_id=False):
+    def onchange_product_id(self, cr, uid, ids, prod_id=False, loc_id=False, loc_dest_id=False, partner_id=False):
         """ On change of product id, if finds UoM, UoS, quantity and UoS quantity.
         @param prod_id: Changed Product id
         @param loc_id: Source location id
@@ -1775,12 +1644,12 @@ class stock_move(osv.osv):
         ctx = {'lang': lang}
 
         product = self.pool.get('product.product').browse(cr, uid, [prod_id], context=ctx)[0]
-        uos_id  = product.uos_id and product.uos_id.id or False
+        uos_id = product.uos_id and product.uos_id.id or False
         result = {
             'product_uom': product.uom_id.id,
             'product_uos': uos_id,
             'product_uom_qty': 1.00,
-            'product_uos_qty' : self.pool.get('stock.move').onchange_quantity(cr, uid, ids, prod_id, 1.00, product.uom_id.id, uos_id)['value']['product_uos_qty'],
+            'product_uos_qty': self.pool.get('stock.move').onchange_quantity(cr, uid, ids, prod_id, 1.00, product.uom_id.id, uos_id)['value']['product_uos_qty'],
         }
         if not ids:
             result['name'] = product.partner_ref
@@ -1810,7 +1679,6 @@ class stock_move(osv.osv):
                 'company_id': move.company_id and move.company_id.id or False,
                 'move_type': move.group_id and move.group_id.move_type or 'one',
                 'partner_id': move.group_id and move.group_id.partner_id and move.group_id.partner_id.id or False,
-                'date_done': move.date_expected,
                 'picking_type_id': move.picking_type_id and move.picking_type_id.id or False,
             }
             pick = pick_obj.create(cr, uid, values, context=context)
@@ -1824,8 +1692,8 @@ class stock_move(osv.osv):
         @return: Move Date
         """
         if not date_expected:
-            date_expected = time.strftime('%Y-%m-%d %H:%M:%S')
-        return {'value':{'date': date_expected}}
+            date_expected = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+        return {'value': {'date': date_expected}}
 
     def action_confirm(self, cr, uid, ids, context=None):
         """ Confirms stock move or put it in waiting if it's linked to another move.
@@ -1858,10 +1726,7 @@ class stock_move(osv.osv):
         """ Changes the state to assigned.
         @return: True
         """
-        done = self.action_assign(cr, uid, ids, context=context)
-        self.write(cr, uid, list(set(ids) - set(done)), {'state': 'assigned'})
-        return True
-
+        return self.write(cr, uid, ids, {'state': 'assigned'})
 
     def cancel_assign(self, cr, uid, ids, context=None):
         """ Changes the state to confirmed.
@@ -1869,24 +1734,32 @@ class stock_move(osv.osv):
         """
         return self.write(cr, uid, ids, {'state': 'confirmed'})
 
+    def check_tracking(self, cr, uid, move, lot_id, context=None):
+        """ Checks if serial number is assigned to stock move or not and raise an error if it had to.
+        """
+        check = False
+        if move.product_id.track_all and not move.location_dest_id.usage == 'inventory':
+            check = True
+        elif move.product_id.track_incoming and move.location_id.usage in ('supplier', 'transit', 'inventory') and move.location_dest_id.usage == 'internal':
+            check = True
+        elif move.product_id.track_outgoing and move.location_dest_id.usage in ('customer', 'transit') and move.location_id.usage == 'internal':
+            check = True
+        if check and not lot_id:
+            raise osv.except_osv(_('Warning!'), _('You must assign a serial number for the product %s') % (move.product_id.name))
+
     def action_assign(self, cr, uid, ids, context=None):
         """ Checks the product type and accordingly writes the state.
-        @return: No. of moves done
         """
         context = context or {}
         quant_obj = self.pool.get("stock.quant")
-        done = []
+        to_assign_moves = []
         for move in self.browse(cr, uid, ids, context=context):
             if move.state not in ('confirmed', 'waiting', 'assigned'):
                 continue
             if move.product_id.type == 'consu':
-                done.append(move.id)
+                to_assign_moves.append(move.id)
                 continue
             else:
-                qty_already_assigned = sum([q.qty for q in move.reserved_quant_ids])
-                qty = move.product_qty - qty_already_assigned
-                #we keep the quants already assigned and try to find the remaining quantity on quants not assigned only
-                domain = [('reservation_id', '=', False), ('qty', '>', 0)]
                 #build the prefered domain based on quants that moved in previous linked done move
                 prev_quant_ids = []
                 for m2 in move.move_orig_ids:
@@ -1894,19 +1767,28 @@ class stock_move(osv.osv):
                         prev_quant_ids.append(q.id)
                 prefered_domain = prev_quant_ids and [('id', 'in', prev_quant_ids)] or []
                 fallback_domain = prev_quant_ids and [('id', 'not in', prev_quant_ids)] or []
-                quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
-                #Will only reserve physical quants, no negative
-                quant_obj.quants_reserve(cr, uid, quants, move, context=context)
-                # the total quantity is provided by existing quants
-                if all(map(lambda x: x[0], quants)):
-                    done.append(move.id)
-        self.write(cr, uid, done, {'state': 'assigned'}, context=context)
-        self._putaway_apply(cr, uid, ids, context=context)
-        return done
+                #we always keep the quants already assigned and try to find the remaining quantity on quants not assigned only
+                main_domain = [('reservation_id', '=', False), ('qty', '>', 0)]
+                #first try to find quants based on specific domains given by linked operations
+                for record in move.linked_move_operation_ids:
+                    domain = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
+                    qty_already_assigned = sum([q.qty for q in record.reserved_quant_ids])
+                    qty = record.qty - qty_already_assigned
+                    quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
+                    quant_obj.quants_reserve(cr, uid, quants, move, record, context=context)
+                #then if the move isn't totally assigned, try to find quants without any specific domain
+                if move.state != 'assigned':
+                    qty_already_assigned = sum([q.qty for q in move.reserved_quant_ids])
+                    qty = move.product_qty - qty_already_assigned
+                    quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
+                    quant_obj.quants_reserve(cr, uid, quants, move, context=context)
+
+        #force assignation of consumable products
+        if to_assign_moves:
+            self.force_assign(cr, uid, to_assign_moves, context=context)
+        #check if a putaway rule is likely to be processed and store result on the move
+        self._putaway_check(cr, uid, ids, context=context)
 
-    #
-    # Cancel move => cancel others move and pickings
-    #
     def action_cancel(self, cr, uid, ids, context=None):
         """ Cancels the moves and if all moves are cancelled it cancels the picking.
         @return: True
@@ -1938,9 +1820,9 @@ class stock_move(osv.osv):
         @return:
         """
         context = context or {}
+        picking_obj = self.pool.get("stock.picking")
         quant_obj = self.pool.get("stock.quant")
-        ops_obj = self.pool.get("stock.pack.operation")
-        pack_obj = self.pool.get("stock.quant.package")
+        pack_op_obj = self.pool.get("stock.pack.operation")
         todo = [move.id for move in self.browse(cr, uid, ids, context=context) if move.state == "draft"]
         if todo:
             self.action_confirm(cr, uid, todo, context=context)
@@ -1951,17 +1833,29 @@ class stock_move(osv.osv):
             if move.picking_id:
                 pickings.add(move.picking_id.id)
             qty = move.product_qty
-
-            # for qty, location_id in move_id.prefered_location_ids:
-            #    quants = quant_obj.quants_get(cr, uid, move.location_id, move.product_id, qty, context=context)
-            #    quant_obj.quants_move(cr, uid, quants, move, location_dest_id, context=context)
-            # should replace the above 2 lines
-            dom = [('qty', '>', 0)]
+            main_domain = [('qty', '>', 0)]
             prefered_domain = [('reservation_id', '=', move.id)]
             fallback_domain = [('reservation_id', '=', False)]
-            quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, context=context)
-            #Will move all quants_get and as such create negative quants
-            quant_obj.quants_move(cr, uid, quants, move, context=context)
+            #first, process the move per linked operation first because it may imply some specific domains to consider
+            for record in move.linked_move_operation_ids:
+                self.check_tracking(cr, uid, move, record.operation_id.lot_id.id, context=context)
+                dom = main_domain + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
+                quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, record.qty, domain=dom, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
+                package_id = False
+                if not record.operation_id.package_id:
+                    #if a package and a result_package is given, we don't enter here because it will be processed by process_packaging() later
+                    #but for operations having only result_package_id, we will create new quants in the final package directly
+                    package_id = record.operation_id.result_package_id.id or False
+                quant_obj.quants_move(cr, uid, quants, move, lot_id=record.operation_id.lot_id.id, owner_id=record.operation_id.owner_id.id, src_package_id=record.operation_id.package_id.id, dest_package_id=package_id, context=context)
+                #packaging process
+                pack_op_obj.process_packaging(cr, uid, record.operation_id, quants, context=context)
+                qty -= record.qty
+            #then if the total quantity processed this way isn't enough, process the remaining quantity without any specific domain
+            if qty > 0:
+                self.check_tracking(cr, uid, move, move.restrict_lot_id.id, context=context)
+                quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, qty, domain=main_domain, prefered_domain=prefered_domain, fallback_domain=fallback_domain, restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
+                quant_obj.quants_move(cr, uid, quants, move, lot_id=move.restrict_lot_id.id, owner_id=move.restrict_partner_id.id, context=context)
+            #unreserve the quants and make them available for other operations/moves
             quant_obj.quants_unreserve(cr, uid, move, context=context)
 
             #Check moves that were pushed
@@ -1975,6 +1869,13 @@ class stock_move(osv.osv):
                 procurement_ids.append(move.procurement_id.id)
         self.write(cr, uid, ids, {'state': 'done', 'date': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
         self.pool.get('procurement.order').check(cr, uid, procurement_ids, context=context)
+        #check picking state to set the date_done is needed
+        done_picking = []
+        for picking in picking_obj.browse(cr, uid, list(pickings), context=context):
+            if picking.state == 'done' and not picking.date_done:
+                done_picking.append(picking.id)
+        if done_picking:
+            picking_obj.write(cr, uid, done_picking, {'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
         return True
 
     def unlink(self, cr, uid, ids, context=None):
@@ -1984,7 +1885,7 @@ class stock_move(osv.osv):
                 raise osv.except_osv(_('User Error!'), _('You can only delete draft moves.'))
         return super(stock_move, self).unlink(cr, uid, ids, context=context)
 
-    def action_scrap(self, cr, uid, ids, quantity, location_id, context=None):
+    def action_scrap(self, cr, uid, ids, quantity, location_id, restrict_lot_id=False, restrict_partner_id=False, context=None):
         """ Move the scrap/damaged product into scrap location
         @param cr: the database cursor
         @param uid: the user id
@@ -1994,7 +1895,7 @@ class stock_move(osv.osv):
         @param context: context arguments
         @return: Scraped lines
         """
-        #quantity should in MOVE UOM
+        #quantity should be given in MOVE UOM
         if quantity <= 0:
             raise osv.except_osv(_('Warning!'), _('Please provide a positive quantity to scrap.'))
         res = []
@@ -2015,8 +1916,8 @@ class stock_move(osv.osv):
                 'state': move.state,
                 'scrapped': True,
                 'location_dest_id': location_id,
-                #TODO lot_id is now on quant and not on move, need to do something for this
-                #'lot_id': move.lot_id.id,
+                'restrict_lot_id': restrict_lot_id,
+                'restrict_partner_id': restrict_partner_id,
             }
             new_move = self.copy(cr, uid, move.id, default_val)
 
@@ -2031,17 +1932,14 @@ class stock_move(osv.osv):
         self.action_done(cr, uid, res, context=context)
         return res
 
-    def action_consume(self, cr, uid, ids, quantity, location_id=False, context=None):
-        """ Consumed product with specific quatity from specific source location
-        @param cr: the database cursor
-        @param uid: the user id
+    def action_consume(self, cr, uid, ids, quantity, location_id=False, restrict_lot_id=False, restrict_partner_id=False, context=None):
+        """ Consumed product with specific quantity from specific source location. This correspond to a split of the move (or write if the quantity to consume is >= than the quantity of the move) followed by an action_done
         @param ids: ids of stock move object to be consumed
-        @param quantity : specify consume quantity
+        @param quantity : specify consume quantity (given in move UoM)
         @param location_id : specify source location
-        @param context: context arguments
         @return: Consumed lines
         """
-        #quantity should in MOVE UOM
+        uom_obj = self.pool.get('product.uom')
         if context is None:
             context = {}
         if quantity <= 0:
@@ -2049,55 +1947,33 @@ class stock_move(osv.osv):
         res = []
         for move in self.browse(cr, uid, ids, context=context):
             move_qty = move.product_qty
+            uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, quantity, move.product_uom.id)
             if move_qty <= 0:
                 raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
-            quantity_rest = move.product_qty
-            quantity_rest -= quantity
-            uos_qty_rest = quantity_rest / move_qty * move.product_uos_qty
-            if quantity_rest <= 0:
-                quantity_rest = 0
-                uos_qty_rest = 0
-                quantity = move.product_qty
-
-            uos_qty = quantity / move_qty * move.product_uos_qty
+            quantity_rest = move.product_qty - uom_qty
             if quantity_rest > 0:
-                default_val = {
-                    'product_uom_qty': quantity,
-                    'product_uos_qty': uos_qty,
-                    'state': move.state,
-                    'location_id': location_id or move.location_id.id,
-                }
-                current_move = self.copy(cr, uid, move.id, default_val)
-                res += [current_move]
-                update_val = {}
-                update_val['product_uom_qty'] = quantity_rest
-                update_val['product_uos_qty'] = uos_qty_rest
-                self.write(cr, uid, [move.id], update_val)
-
+                ctx = context.copy()
+                if location_id:
+                    ctx['source_location_id'] = location_id
+                res.append(self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id, 
+                                      restrict_partner_id=restrict_partner_id, context=ctx))
             else:
-                quantity_rest = quantity
-                uos_qty_rest =  uos_qty
-                res += [move.id]
-                update_val = {
-                        'product_uom_qty' : quantity_rest,
-                        'product_uos_qty' : uos_qty_rest,
-                        'location_id': location_id or move.location_id.id,
-                }
-                self.write(cr, uid, [move.id], update_val)
+                res.append(move.id)
+                if location_id:
+                    self.write(cr, uid, [move.id], {'location_id': location_id, 'restrict_lot_id': restrict_lot_id, 
+                                                    'restrict_partner_id': restrict_partner_id}, context=context)
 
         self.action_done(cr, uid, res, context=context)
         return res
 
-
-
-    def split(self, cr, uid, move, qty, context=None):
-        """ 
-            Splits qty from move move into a new move
+    def split(self, cr, uid, move, qty, restrict_lot_id=False, restrict_partner_id=False, context=None):
+        """ Splits qty from move move into a new move
+        :param move: browse record
+        :param qty: float. quantity to split (given in product UoM)
+        :param context: dictionay. can contains the special key 'source_location_id' in order to force the source location when copying the move
         """
-        if move.product_qty==qty:
+        if move.product_qty <= qty or qty == 0:
             return move.id
-        if (move.product_qty < qty) or (qty==0):
-            return False
 
         uom_obj = self.pool.get('product.uom')
         context = context or {}
@@ -2112,17 +1988,24 @@ class stock_move(osv.osv):
             'product_uom_qty': uom_qty,
             'product_uos_qty': uos_qty,
             'state': move.state,
+            'procure_method': 'make_to_stock',
             'move_dest_id': False,
-            'reserved_quant_ids': []
+            'reserved_quant_ids': [],
+            'restrict_lot_id': restrict_lot_id,
+            'restrict_partner_id': restrict_partner_id
         }
+        if context.get('source_location_id'):
+            defaults['location_id'] = context['source_location_id']
         new_move = self.copy(cr, uid, move.id, defaults)
 
+        ctx = context.copy()
+        ctx['do_not_propagate'] = True
         self.write(cr, uid, [move.id], {
             'product_uom_qty': move.product_uom_qty - uom_qty,
             'product_uos_qty': move.product_uos_qty - uos_qty,
-#             'reserved_quant_ids': [(6,0,[])]  SHOULD NOT CHANGE as it has been reserved already
-        }, context=context)
-        
+            #'reserved_quant_ids': [(6,0,[])]  SHOULD NOT CHANGE as it has been reserved already
+        }, context=ctx)
+
         if move.move_dest_id and move.propagate:
             new_move_prop = self.split(cr, uid, move.move_dest_id, qty, context=context)
             self.write(cr, uid, [new_move], {'move_dest_id': new_move_prop}, context=context)
@@ -2150,7 +2033,7 @@ class stock_inventory(osv.osv):
            :rtype: list of tuple
         """
         #default available choices
-        res_filter = [('none', ' All products of a whole location'), ('product', 'One product only')]
+        res_filter = [('none', _('All products of a whole location')), ('product', _('One product only'))]
         settings_obj = self.pool.get('stock.config.settings')
         config_ids = settings_obj.search(cr, uid, [], limit=1, order='id DESC', context=context)
         #If we don't have updated config until now, all fields are by default false and so should be not dipslayed
@@ -2159,12 +2042,12 @@ class stock_inventory(osv.osv):
 
         stock_settings = settings_obj.browse(cr, uid, config_ids[0], context=context)
         if stock_settings.group_stock_tracking_owner:
-            res_filter.append(('owner', 'One owner only'))
-            res_filter.append(('product_owner', 'One product for a specific owner'))
-        if stock_settings.group_stock_production_lot:
-            res_filter.append(('lot', 'One Lot/Serial Number'))
+            res_filter.append(('owner', _('One owner only')))
+            res_filter.append(('product_owner', _('One product for a specific owner')))
         if stock_settings.group_stock_tracking_lot:
-            res_filter.append(('pack', 'A Pack'))
+            res_filter.append(('lot', _('One Lot/Serial Number')))
+        if stock_settings.group_stock_packaging:
+            res_filter.append(('pack', _('A Pack')))
         return res_filter
 
     _columns = {
@@ -2175,7 +2058,7 @@ class stock_inventory(osv.osv):
         'move_ids': fields.one2many('stock.move', 'inventory_id', 'Created Moves', help="Inventory Moves."),
         'state': fields.selection([('draft', 'Draft'), ('cancel', 'Cancelled'), ('confirm', 'In Progress'), ('done', 'Validated')], 'Status', readonly=True, select=True),
         'company_id': fields.many2one('res.company', 'Company', required=True, select=True, readonly=True, states={'draft': [('readonly', False)]}),
-        'location_id': fields.many2one('stock.location', 'Location', required=True),
+        'location_id': fields.many2one('stock.location', 'Location', required=True, readonly=True, states={'draft': [('readonly', False)]}),
         'product_id': fields.many2one('product.product', 'Product', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Product to focus your inventory on a particular Product."),
         'package_id': fields.many2one('stock.quant.package', 'Pack', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Pack to focus your inventory on a particular Pack."),
         'partner_id': fields.many2one('res.partner', 'Owner', readonly=True, states={'draft': [('readonly', False)]}, help="Specify Owner to focus your inventory on a particular Owner."),
@@ -2192,7 +2075,7 @@ class stock_inventory(osv.osv):
             return False
 
     _defaults = {
-        'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
+        'date': fields.datetime.now,
         'state': 'draft',
         'company_id': lambda self, cr, uid, c: self.pool.get('res.company')._company_default_get(cr, uid, 'stock.inventory', context=c),
         'location_id': _default_stock_location,
@@ -2235,7 +2118,7 @@ class stock_inventory(osv.osv):
             move_obj.action_done(cr, uid, [x.id for x in inv.move_ids if x.location_id.usage == 'internal'], context=context)
             #then, we move from inventory loss. This 2 steps process is needed because some moved quant may need to be put again in stock
             move_obj.action_done(cr, uid, [x.id for x in inv.move_ids if x.location_id.usage != 'internal'], context=context)
-            self.write(cr, uid, [inv.id], {'state': 'done', 'date_done': time.strftime('%Y-%m-%d %H:%M:%S')}, context=context)
+            self.write(cr, uid, [inv.id], {'state': 'done', 'date_done': time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
         return True
 
     def _create_stock_move(self, cr, uid, inventory, todo_line, context=None):
@@ -2296,7 +2179,6 @@ class stock_inventory(osv.osv):
         return True
 
     def action_cancel_inventory(self, cr, uid, ids, context=None):
-        #TODO test
         self.action_cancel_draft(cr, uid, ids, context=context)
 
     def prepare_inventory(self, cr, uid, ids, context=None):
@@ -2352,6 +2234,14 @@ class stock_inventory_line(osv.osv):
     _name = "stock.inventory.line"
     _description = "Inventory Line"
     _rec_name = "inventory_id"
+    _order = "inventory_id, location_name, product_code, product_name, prod_lot_id"
+
+    def _get_product_name_change(self, cr, uid, ids, context=None):
+        return self.pool.get('stock.inventory.line').search(cr, uid, [('product_id', 'in', ids)], context=context)
+
+    def _get_location_change(self, cr, uid, ids, context=None):
+        return self.pool.get('stock.inventory.line').search(cr, uid, [('location_id', 'in', ids)], context=context)
+
     _columns = {
         'inventory_id': fields.many2one('stock.inventory', 'Inventory', ondelete='cascade', select=True),
         'location_id': fields.many2one('stock.location', 'Location', required=True, select=True),
@@ -2364,6 +2254,15 @@ class stock_inventory_line(osv.osv):
         'state': fields.related('inventory_id', 'state', type='char', string='Status', readonly=True),
         'th_qty': fields.float('Theoretical Quantity', readonly=True),
         'partner_id': fields.many2one('res.partner', 'Owner'),
+        'product_name': fields.related('product_id', 'name', type='char', string='Product name', store={
+                                                                                            'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
+                                                                                            'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['product_id'], 20),}),
+        'product_code': fields.related('product_id', 'default_code', type='char', string='Product code', store={
+                                                                                            'product.product': (_get_product_name_change, ['name', 'default_code'], 20),
+                                                                                            'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['product_id'], 20),}),
+        'location_name': fields.related('location_id', 'complete_name', type='char', string='Location name', store={
+                                                                                            'stock.location': (_get_location_change, ['name', 'location_id', 'active'], 20),
+                                                                                            'stock.inventory.line': (lambda self, cr, uid, ids, c={}: ids, ['location_id'], 20),}),
     }
 
     _defaults = {
@@ -2640,10 +2539,10 @@ class stock_warehouse(osv.osv):
         try:
             mto_route_id = data_obj.get_object_reference(cr, uid, 'stock', 'route_warehouse0_mto')[1]
         except:
-            mto_route_id = route_obj.search(cr, uid, [('name', 'like', _('MTO'))], context=context)
+            mto_route_id = route_obj.search(cr, uid, [('name', 'like', _('Make To Order'))], context=context)
             mto_route_id = mto_route_id and mto_route_id[0] or False
         if not mto_route_id:
-            raise osv.except_osv(_('Error!'), _('Can\'t find any generic MTO route.'))
+            raise osv.except_osv(_('Error!'), _('Can\'t find any generic Make To Order route.'))
 
         from_loc, dest_loc, pick_type_id = values[0]
         return {
@@ -2688,7 +2587,7 @@ class stock_warehouse(osv.osv):
             pull_rule['procure_method'] = 'make_to_order'
             pull_obj.create(cr, uid, vals=pull_rule, context=context)
 
-        #create MTS route and pull rules for delivery a specific route MTO to be set on the product
+        #create MTS route and pull rules for delivery and a specific route MTO to be set on the product
         route_name, values = routes_dict[warehouse.delivery_steps]
         route_vals = self._get_reception_delivery_route(cr, uid, warehouse, route_name, context=context)
         #create the route and its pull rules
@@ -2739,7 +2638,6 @@ class stock_warehouse(osv.osv):
             output_loc = warehouse.lot_stock_id
         picking_type_obj.write(cr, uid, warehouse.in_type_id.id, {'default_location_dest_id': input_loc.id}, context=context)
         picking_type_obj.write(cr, uid, warehouse.out_type_id.id, {'default_location_src_id': output_loc.id}, context=context)
-        picking_type_obj.write(cr, uid, warehouse.int_type_id.id, {'active': new_reception_step != 'one_step'}, context=context)
         picking_type_obj.write(cr, uid, warehouse.pick_type_id.id, {'active': new_delivery_step != 'ship_only'}, context=context)
         picking_type_obj.write(cr, uid, warehouse.pack_type_id.id, {'active': new_delivery_step == 'pick_pack_ship'}, context=context)
 
@@ -2815,11 +2713,11 @@ class stock_warehouse(osv.osv):
             vals[values['field']] = location_id
 
         #create new sequences
-        in_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence in'), 'prefix': vals.get('code', '') + '\IN\\', 'padding': 5}, context=context)
-        out_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence out'), 'prefix': vals.get('code', '') + '\OUT\\', 'padding': 5}, context=context)
-        pack_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence packing'), 'prefix': vals.get('code', '') + '\PACK\\', 'padding': 5}, context=context)
-        pick_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence picking'), 'prefix': vals.get('code', '') + '\PICK\\', 'padding': 5}, context=context)
-        int_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence internal'), 'prefix': vals.get('code', '') + '\INT\\', 'padding': 5}, context=context)
+        in_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence in'), 'prefix': vals.get('code', '') + '/IN/', 'padding': 5}, context=context)
+        out_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence out'), 'prefix': vals.get('code', '') + '/OUT/', 'padding': 5}, context=context)
+        pack_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence packing'), 'prefix': vals.get('code', '') + '/PACK/', 'padding': 5}, context=context)
+        pick_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence picking'), 'prefix': vals.get('code', '') + '/PICK/', 'padding': 5}, context=context)
+        int_seq_id = seq_obj.create(cr, SUPERUSER_ID, values={'name': vals.get('name', '') + _(' Sequence internal'), 'prefix': vals.get('code', '') + '/INT/', 'padding': 5}, context=context)
 
         #create WH
         new_id = super(stock_warehouse, self).create(cr, uid, vals=vals, context=context)
@@ -2842,59 +2740,70 @@ class stock_warehouse(osv.osv):
             output_loc = wh_stock_loc
 
         #choose the next available color for the picking types of this warehouse
-        all_used_colors = self.pool.get('stock.picking.type').search_read(cr, uid, [('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')
-        not_used_colors = list(set(range(0, 9)) - set([x['color'] for x in all_used_colors]))
         color = 0
-        if not_used_colors:
-            color = not_used_colors[0]
+        available_colors = [c%9 for c in range(3, 12)]  # put flashy colors first
+        all_used_colors = self.pool.get('stock.picking.type').search_read(cr, uid, [('warehouse_id', '!=', False), ('color', '!=', False)], ['color'], order='color')
+        #don't use sets to preserve the list order
+        for x in all_used_colors:
+            if x['color'] in available_colors:
+                available_colors.remove(x['color'])
+        if available_colors:
+            color = available_colors[0]
+
+        #order the picking types with a sequence allowing to have the following suit for each warehouse: reception, internal, pick, pack, ship. 
+        max_sequence = self.pool.get('stock.picking.type').search_read(cr, uid, [], ['sequence'], order='sequence desc')
+        max_sequence = max_sequence and max_sequence[0]['sequence'] or 0
 
         in_type_id = picking_type_obj.create(cr, uid, vals={
             'name': _('Receptions'),
             'warehouse_id': new_id,
-            'code_id': 'incoming',
+            'code': 'incoming',
             'auto_force_assign': True,
             'sequence_id': in_seq_id,
             'default_location_src_id': supplier_loc.id,
             'default_location_dest_id': input_loc.id,
+            'sequence': max_sequence + 1,
             'color': color}, context=context)
         out_type_id = picking_type_obj.create(cr, uid, vals={
             'name': _('Delivery Orders'),
             'warehouse_id': new_id,
-            'code_id': 'outgoing',
+            'code': 'outgoing',
             'sequence_id': out_seq_id,
-            'delivery': True,
+            'return_picking_type_id': in_type_id,
             'default_location_src_id': output_loc.id,
             'default_location_dest_id': customer_loc.id,
+            'sequence': max_sequence + 4,
             'color': color}, context=context)
+        picking_type_obj.write(cr, uid, [in_type_id], {'return_picking_type_id': out_type_id}, context=context)
         int_type_id = picking_type_obj.create(cr, uid, vals={
             'name': _('Internal Transfers'),
             'warehouse_id': new_id,
-            'code_id': 'internal',
+            'code': 'internal',
             'sequence_id': int_seq_id,
             'default_location_src_id': wh_stock_loc.id,
             'default_location_dest_id': wh_stock_loc.id,
-            'active': reception_steps != 'one_step',
-            'pack': False,
+            'active': True,
+            'sequence': max_sequence + 2,
             'color': color}, context=context)
         pack_type_id = picking_type_obj.create(cr, uid, vals={
             'name': _('Pack'),
             'warehouse_id': new_id,
-            'code_id': 'internal',
+            'code': 'internal',
             'sequence_id': pack_seq_id,
             'default_location_src_id': wh_pack_stock_loc.id,
             'default_location_dest_id': output_loc.id,
             'active': delivery_steps == 'pick_pack_ship',
-            'pack': True,
+            'sequence': max_sequence + 3,
             'color': color}, context=context)
         pick_type_id = picking_type_obj.create(cr, uid, vals={
             'name': _('Pick'),
             'warehouse_id': new_id,
-            'code_id': 'internal',
+            'code': 'internal',
             'sequence_id': pick_seq_id,
             'default_location_src_id': wh_stock_loc.id,
             'default_location_dest_id': wh_pack_stock_loc.id,
             'active': delivery_steps != 'ship_only',
-            'pack': False,
+            'sequence': max_sequence + 2,
             'color': color}, context=context)
 
         #write picking types on WH
@@ -2930,7 +2839,7 @@ class stock_warehouse(osv.osv):
             'crossdock': (_('Cross-Dock'), [(warehouse.wh_input_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
             'ship_only': (_('Ship Only'), [(warehouse.lot_stock_id, customer_loc, warehouse.out_type_id.id)]),
             'pick_ship': (_('Pick + Ship'), [(warehouse.lot_stock_id, warehouse.wh_output_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
-            'pick_pack_ship': (_('Pick + Pack + Ship'), [(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.int_type_id.id), (warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
+            'pick_pack_ship': (_('Pick + Pack + Ship'), [(warehouse.lot_stock_id, warehouse.wh_pack_stock_loc_id, warehouse.pick_type_id.id), (warehouse.wh_pack_stock_loc_id, warehouse.wh_output_stock_loc_id, warehouse.pack_type_id.id), (warehouse.wh_output_stock_loc_id, customer_loc, warehouse.out_type_id.id)]),
         }
 
     def _handle_renaming(self, cr, uid, warehouse, name, context=None):
@@ -2958,7 +2867,6 @@ class stock_warehouse(osv.osv):
             ids = [ids]
         seq_obj = self.pool.get('ir.sequence')
         route_obj = self.pool.get('stock.location.route')
-        warehouse_obj = self.pool.get('stock.warehouse')
 
         context_with_inactive = context.copy()
         context_with_inactive['active_test'] = False
@@ -2987,7 +2895,7 @@ class stock_warehouse(osv.osv):
                     old_ids = set([wh.id for wh in warehouse.resupply_wh_ids])
                     to_add_wh_ids = new_ids - old_ids
                     if to_add_wh_ids:
-                        supplier_warehouses = warehouse_obj.browse(cr, uid, list(to_add_wh_ids), context=context)
+                        supplier_warehouses = self.browse(cr, uid, list(to_add_wh_ids), context=context)
                         self._create_resupply_routes(cr, uid, warehouse, supplier_warehouses, warehouse.default_resupply_wh_id, context=context)
                     to_remove_wh_ids = old_ids - new_ids
                     if to_remove_wh_ids:
@@ -3056,6 +2964,12 @@ class stock_location_path(osv.osv):
                 result[push_rule.id] = True
         return result.keys()
 
+    def _get_rules(self, cr, uid, ids, context=None):
+        res = []
+        for route in self.browse(cr, uid, ids, context=context):
+            res += [x.id for x in route.push_ids]
+        return res
+
     _columns = {
         'name': fields.char('Operation Name', size=64, required=True),
         'company_id': fields.many2one('res.company', 'Company'),
@@ -3084,6 +2998,12 @@ class stock_location_path(osv.osv):
                     'stock.location.path': (lambda self, cr, uid, ids, c={}: ids, ['route_id'], 20),},
                 help="If the active field is set to False, it will allow you to hide the rule without removing it." ),
         'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse'),
+        'route_sequence': fields.related('route_id', 'sequence', string='Route Sequence',
+            store={
+                'stock.location.route': (_get_rules, ['sequence'], 10),
+                'stock.location.path': (lambda self, cr, uid, ids, c={}: ids, ['route_id'], 10),
+        }),
+        'sequence': fields.integer('Sequence'),
     }
     _defaults = {
         'auto': 'auto',
@@ -3095,26 +3015,31 @@ class stock_location_path(osv.osv):
     }
     def _apply(self, cr, uid, rule, move, context=None):
         move_obj = self.pool.get('stock.move')
-        newdate = (datetime.strptime(move.date, '%Y-%m-%d %H:%M:%S') + relativedelta.relativedelta(days=rule.delay or 0)).strftime('%Y-%m-%d')
-        if rule.auto=='transparent':
+        newdate = (datetime.strptime(move.date_expected, DEFAULT_SERVER_DATETIME_FORMAT) + relativedelta.relativedelta(days=rule.delay or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
+        if rule.auto == 'transparent':
+            old_dest_location = move.location_dest_id.id
             move_obj.write(cr, uid, [move.id], {
                 'date': newdate,
+                'date_expected': newdate,
                 'location_dest_id': rule.location_dest_id.id
             })
-            if rule.location_dest_id.id<>move.location_dest_id.id:
-                move_obj._push_apply(self, cr, uid, move.id, context)
+            move.refresh()
+            #avoid looping if a push rule is not well configured
+            if rule.location_dest_id.id != old_dest_location:
+                #call again push_apply to see if a next step is defined
+                move_obj._push_apply(cr, uid, [move], context=context)
             return move.id
         else:
             move_id = move_obj.copy(cr, uid, move.id, {
                 'location_id': move.location_dest_id.id,
                 'location_dest_id': rule.location_dest_id.id,
-                'date': datetime.now().strftime('%Y-%m-%d'),
+                'date': newdate,
                 'company_id': rule.company_id and rule.company_id.id or False,
                 'date_expected': newdate,
                 'picking_id': False,
                 'picking_type_id': rule.picking_type_id and rule.picking_type_id.id or False,
-                'rule_id': rule.id,
-                'propagate': rule.propagate, 
+                'propagate': rule.propagate,
+                'push_rule_id': rule.id,
                 'warehouse_id': rule.warehouse_id and rule.warehouse_id.id or False,
             })
             move_obj.write(cr, uid, [move.id], {
@@ -3140,7 +3065,7 @@ class stock_move_putaway(osv.osv):
 # -------------------------
 
 from openerp.report import report_sxw
-report_sxw.report_sxw('report.stock.quant.package.barcode', 'stock.quant.package', 'addons/stock/report/picking_barcode.rml')
+report_sxw.report_sxw('report.stock.quant.package.barcode', 'stock.quant.package', 'addons/stock/report/package_barcode.rml')
 
 class stock_package(osv.osv):
     """
@@ -3311,7 +3236,7 @@ class stock_pack_operation(osv.osv):
     def _get_remaining_prod_quantities(self, cr, uid, operation, context=None):
         '''Get the remaining quantities per product on an operation with a package. This function returns a dictionary'''
         #if the operation doesn't concern a package, it's not relevant to call this function
-        if not operation.package_id:
+        if not operation.package_id or operation.product_id:
             return {operation.product_id.id: operation.remaining_qty}
         #get the total of products the package contains
         res = self.pool.get('stock.quant.package')._get_all_products_quantities(cr, uid, operation.package_id.id, context=context)
@@ -3326,17 +3251,20 @@ class stock_pack_operation(osv.osv):
         uom_obj = self.pool.get('product.uom')
         res = {}
         for ops in self.browse(cr, uid, ids, context=context):
+            res[ops.id] = 0
             if ops.package_id:
                 #dont try to compute the remaining quantity for packages because it's not relevant (a package could include different products).
                 #should use _get_remaining_prod_quantities instead
-                res[ops.id] = 0
                 continue
-            elif ops.product_id and ops.product_uom_id:
-                qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id)
+            elif ops.product_id:
+                qty = ops.product_qty
+                if ops.product_uom_id:
+                    qty = uom_obj._compute_qty(cr, uid, ops.product_uom_id.id, ops.product_qty, ops.product_id.uom_id.id)
                 for record in ops.linked_move_operation_ids:
                     qty -= record.qty
                 #converting the remaining quantity in the pack operation UoM
-                qty = uom_obj._compute_qty(cr, uid, ops.product_id.uom_id.id, qty, ops.product_uom_id.id)
+                if ops.product_uom_id:
+                    qty = uom_obj._compute_qty(cr, uid, ops.product_id.uom_id.id, qty, ops.product_uom_id.id)
                 res[ops.id] = qty
         return res
 
@@ -3374,7 +3302,6 @@ class stock_pack_operation(osv.osv):
         'product_uom_id': fields.many2one('product.uom', 'Product Unit of Measure'),
         'product_qty': fields.float('Quantity', digits_compute=dp.get_precision('Product Unit of Measure'), required=True),
         'package_id': fields.many2one('stock.quant.package', 'Package'),  # 2
-        'quant_id': fields.many2one('stock.quant', 'Quant'),  # 3
         'lot_id': fields.many2one('stock.production.lot', 'Lot/Serial Number'),
         'result_package_id': fields.many2one('stock.quant.package', 'Container Package', help="If set, the operations are packed into this package", required=False, ondelete='cascade'),
         'date': fields.datetime('Date', required=True),
@@ -3390,26 +3317,28 @@ class stock_pack_operation(osv.osv):
         'date': fields.date.context_today,
     }
 
-    def _get_domain(self, cr, uid, ops, context=None):
-        '''
-            Gives domain for different
-        '''
-        res = []
-        if ops.package_id:
-            res.append(('package_id', '=', ops.package_id.id), )
-        if ops.lot_id:
-            res.append(('lot_id', '=', ops.lot_id.id), )
-        if ops.owner_id: 
-            res.append(('owner_id', '=', ops.owner_id.id), )
-        else:
-            res.append(('owner_id', '=', False), )
-        return res
+    def process_packaging(self, cr, uid, operation, quants, context=None):
+        ''' Process the packaging of a given operation, after the quants have been moved. If there was not enough quants found
+        a quant already has been with the good package information so we don't consider that case in this method'''
+        quant_obj = self.pool.get("stock.quant")
+        pack_obj = self.pool.get("stock.quant.package")
+        for quant, qty in quants:
+            if quant:
+                if operation.product_id:
+                    #if a product + a package information is given, we consider that we took a part of an existing package (unpacking)
+                    quant_obj.write(cr, SUPERUSER_ID, quant.id, {'package_id': operation.result_package_id.id}, context=context)
+                elif operation.package_id and operation.result_package_id:
+                    #move the whole pack into the final package if any
+                    pack_obj.write(cr, uid, [operation.package_id.id], {'parent_id': operation.result_package_id.id}, context=context)
+
+
+
 
     #TODO: this function can be refactored
-    def _search_and_increment(self, cr, uid, picking_id, key, context=None):
-        '''Search for an operation on an existing key in a picking, if it exists increment the qty (+1) otherwise create it
+    def _search_and_increment(self, cr, uid, picking_id, domain, context=None):
+        '''Search for an operation with given 'domain' in a picking, if it exists increment the qty (+1) otherwise create it
 
-        :param key: tuple directly reusable in a domain
+        :param domain: list of tuple directly reusable as a domain
         context can receive a key 'current_package_id' with the package to consider for this operation
         returns True
 
@@ -3418,35 +3347,33 @@ class stock_pack_operation(osv.osv):
                  (1, ID, { values })    update the linked record with id = ID (write *values* on it)
                  (2, ID)                remove and delete the linked record with id = ID (calls unlink on ID, that will delete the object completely, and the link to it as well)
         '''
-        quant_obj = self.pool.get('stock.quant')
         if context is None:
             context = {}
 
         #if current_package_id is given in the context, we increase the number of items in this package
         package_clause = [('result_package_id', '=', context.get('current_package_id', False))]
-        existing_operation_ids = self.search(cr, uid, [('picking_id', '=', picking_id), key] + package_clause, context=context)
+        existing_operation_ids = self.search(cr, uid, [('picking_id', '=', picking_id)] + domain + package_clause, context=context)
         if existing_operation_ids:
-            #existing operation found for the given key and picking => increment its quantity
+            #existing operation found for the given domain and picking => increment its quantity
             operation_id = existing_operation_ids[0]
             qty = self.browse(cr, uid, operation_id, context=context).product_qty + 1
             self.write(cr, uid, operation_id, {'product_qty': qty}, context=context)
         else:
-            #no existing operation found for the given key and picking => create a new one
-            var_name, dummy, value = key
-            uom_id = False
-            if var_name == 'product_id':
-                uom_id = self.pool.get('product.product').browse(cr, uid, value, context=context).uom_id.id
-            elif var_name == 'quant_id':
-                quant = quant_obj.browse(cr, uid, value, context=context)
-                uom_id = quant.product_id.uom_id.id
+            #no existing operation found for the given domain and picking => create a new one
             values = {
                 'picking_id': picking_id,
-                var_name: value,
                 'product_qty': 1,
-                'product_uom_id': uom_id,
             }
+            for key in domain:
+                var_name, dummy, value = key
+                uom_id = False
+                if var_name == 'product_id':
+                    uom_id = self.pool.get('product.product').browse(cr, uid, value, context=context).uom_id.id
+                update_dict = {var_name: value}
+                if uom_id:
+                    update_dict['product_uom_id'] = uom_id
+                values.update(update_dict)
             operation_id = self.create(cr, uid, values, context=context)
-            values.update({'id': operation_id})
         return True
 
 
@@ -3461,8 +3388,26 @@ class stock_move_operation_link(osv.osv):
         'qty': fields.float('Quantity', help="Quantity of products to consider when talking about the contribution of this pack operation towards the remaining quantity of the move (and inverse). Given in the product main uom."),
         'operation_id': fields.many2one('stock.pack.operation', 'Operation', required=True, ondelete="cascade"),
         'move_id': fields.many2one('stock.move', 'Move', required=True, ondelete="cascade"),
+        'reserved_quant_ids': fields.one2many('stock.quant', 'link_move_operation_id', 'Reserved quants'),
     }
 
+    def get_specific_domain(self, cr, uid, record, context=None):
+        '''Returns the specific domain to consider for quant selection in action_assign() or action_done() of stock.move,
+        having the record given as parameter making the link between the stock move and a pack operation'''
+        package_obj = self.pool.get('stock.quant.package')
+
+        op = record.operation_id
+        domain = []
+        if op.package_id:
+            domain.append(('id', 'in', package_obj.get_content(cr, uid, [op.package_id.id], context=context)))
+        if op.lot_id:
+            domain.append(('lot_id', '=', op.lot_id.id))
+        if op.owner_id:
+            domain.append(('owner_id', '=', op.owner_id.id))
+        else:
+            domain.append(('owner_id', '=', False))
+        return domain
+
 class stock_warehouse_orderpoint(osv.osv):
     """
     Defines Minimum stock rules.
@@ -3473,7 +3418,6 @@ class stock_warehouse_orderpoint(osv.osv):
     def get_draft_procurements(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
-        result = {}
         if not isinstance(ids, list):
             ids = [ids]
         procurement_obj = self.pool.get('procurement.order')
@@ -3586,6 +3530,7 @@ class stock_warehouse_orderpoint(osv.osv):
 class stock_picking_type(osv.osv):
     _name = "stock.picking.type"
     _description = "The picking type determines the picking view"
+    _order = 'sequence'
 
     def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
         """ Generic method to generate data for bar chart values using SparklineBarWidget.
@@ -3605,41 +3550,65 @@ class stock_picking_type(osv.osv):
         """
         month_begin = date.today().replace(day=1)
         section_result = [{
-                            'value': 0,
-                            'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
-                            } for i in range(10, -1, -1)]
+            'value': 0,
+            'tooltip': (month_begin + relativedelta.relativedelta(months=i)).strftime('%B'),
+        } for i in range(-2, 2, 1)]
         group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
         for group in group_obj:
-            group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
+            group_begin_date = datetime.strptime(group['__domain'][0][2], DEFAULT_SERVER_DATE_FORMAT)
             month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
-            section_result[10 - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
+            section_result[-month_delta.months + 2] = {'value': group.get(value_field, 0), 'tooltip': group_begin_date.strftime('%B')}
+            inner_groupby = (group.get('__context', {})).get('group_by',[])
+            if inner_groupby:
+                groupby_picking = obj.read_group(cr, uid, group.get('__domain'), read_fields, inner_groupby, context=context)
+                for groupby in groupby_picking:
+                    section_result[-month_delta.months + 2]['value'] = groupby.get(value_field, 0)
         return section_result
 
-    def _get_picking_data(self, cr, uid, ids, field_name, arg, context=None):
+    def _get_tristate_values(self, cr, uid, ids, field_name, arg, context=None):
+        picking_obj = self.pool.get('stock.picking')
+        res = dict.fromkeys(ids, [])
+        for picking_type_id in ids:
+            #get last 10 pickings of this type
+            picking_ids = picking_obj.search(cr, uid, [('picking_type_id', '=', picking_type_id), ('state', '=', 'done')], order='date_done desc', limit=10, context=context)
+            tristates = []
+            for picking in picking_obj.browse(cr, uid, picking_ids, context=context):
+                if picking.date_done > picking.date:
+                    tristates.insert(0, {'tooltip': picking.name + _(': Late'), 'value': -1})
+                elif picking.backorder_id:
+                    tristates.insert(0, {'tooltip': picking.name + _(': Backorder exists'), 'value': 0})
+                else:
+                    tristates.insert(0, {'tooltip': picking.name + _(': OK'), 'value': 1})
+            res[picking_type_id] = tristates
+        return res
+
+
+
+    def _get_monthly_pickings(self, cr, uid, ids, field_name, arg, context=None):
         obj = self.pool.get('stock.picking')
         res = dict.fromkeys(ids, False)
         month_begin = date.today().replace(day=1)
-        groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
-        groupby_end = (month_begin + relativedelta.relativedelta(months=3)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+        groupby_begin = (month_begin + relativedelta.relativedelta(months=-2)).strftime(DEFAULT_SERVER_DATE_FORMAT)
+        groupby_end = (month_begin + relativedelta.relativedelta(months=2)).strftime(DEFAULT_SERVER_DATE_FORMAT)
         for id in ids:
             created_domain = [
                 ('picking_type_id', '=', id),
-                ('state', 'not in', ['done', 'cancel']),
+                ('state', '=', 'done'),
                 ('date', '>=', groupby_begin),
                 ('date', '<', groupby_end),
             ]
-            res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['date'], 'picking_type_id_count', 'date', context=context)
+            res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['date','picking_type_id'], 'picking_type_id_count', ['date','picking_type_id'], context=context)
         return res
 
     def _get_picking_count(self, cr, uid, ids, field_names, arg, context=None):
         obj = self.pool.get('stock.picking')
         domains = {
             'count_picking_draft': [('state', '=', 'draft')],
-            'count_picking_waiting': [('state','=','confirmed')],
+            'count_picking_waiting': [('state','=', 'confirmed')],
             'count_picking_ready': [('state','=','assigned')],
             'count_picking': [('state','in',('assigned','waiting','confirmed'))],
-            'count_picking_late': [('min_date','<', time.strftime('%Y-%m-%d %H:%M:%S')), ('state','in',('assigned','waiting','confirmed'))],
-            'count_picking_backorders': [('backorder_id','<>', False), ('state','!=','done')],
+            'count_picking_late': [('min_date','<', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ('state','in',('assigned','waiting','confirmed'))],
+            'count_picking_backorders': [('backorder_id','!=', False), ('state','in',('confirmed', 'assigned', 'waiting'))],
         }
         result = {}
         for field in domains:
@@ -3652,7 +3621,7 @@ class stock_picking_type(osv.osv):
         for tid in ids:
             if result[tid]['count_picking']:
                 result[tid]['rate_picking_late'] = result[tid]['count_picking_late'] *100 / result[tid]['count_picking']
-                result[tid]['rate_picking_backorders'] = result[tid]['count_picking_backorders'] *100 / (result[tid]['count_picking'] + result[tid]['count_picking_draft'])
+                result[tid]['rate_picking_backorders'] = result[tid]['count_picking_backorders'] *100 / result[tid]['count_picking']
             else:
                 result[tid]['rate_picking_late'] = 0
                 result[tid]['rate_picking_backorders'] = 0
@@ -3728,23 +3697,24 @@ class stock_picking_type(osv.osv):
     _columns = {
         'name': fields.char('Name', translate=True, required=True),
         'complete_name': fields.function(_get_name, type='char', string='Name'),
-        'pack': fields.boolean('Prefill Pack Operations', help='This picking type needs packing interface'),
         'auto_force_assign': fields.boolean('Automatic Availability', help='This picking type does\'t need to check for the availability in source location.'),
         'color': fields.integer('Color'),
-        'delivery': fields.boolean('Print delivery'),
+        'sequence': fields.integer('Sequence', help="Used to order the 'All Operations' kanban view"),
         'sequence_id': fields.many2one('ir.sequence', 'Reference Sequence', required=True),
         'default_location_src_id': fields.many2one('stock.location', 'Default Source Location'),
         'default_location_dest_id': fields.many2one('stock.location', 'Default Destination Location'),
-        #TODO: change field name to "code" as it's not a many2one anymore
-        'code_id': fields.selection([('incoming', 'Suppliers'), ('outgoing', 'Customers'), ('internal', 'Internal')], 'Picking type code', required=True),
+        'code': fields.selection([('incoming', 'Suppliers'), ('outgoing', 'Customers'), ('internal', 'Internal')], 'Type of Operation', required=True),
         'return_picking_type_id': fields.many2one('stock.picking.type', 'Picking Type for Returns'),
         'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse', ondelete='cascade'),
         'active': fields.boolean('Active'),
 
         # Statistics for the kanban view
-        'weekly_picking': fields.function(_get_picking_data,
+        'monthly_picking': fields.function(_get_monthly_pickings,
+            type='string',
+            string='Done Pickings per Month'),
+        'last_done_picking': fields.function(_get_tristate_values,
             type='string',
-            string='Scheduled pickings per week'),
+            string='Last 10 Done Pickings'),
 
         'count_picking_draft': fields.function(_get_picking_count,
             type='integer', multi='_get_picking_count'),