[FIX]move domain check in _quants_get_order
[odoo/odoo.git] / addons / stock / stock.py
index 2d8df49..d954042 100644 (file)
@@ -171,12 +171,14 @@ 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),
     }
 
 
@@ -526,17 +528,6 @@ class stock_quant(osv.osv):
     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, 'link_move_operation_id': False}, context=context)
@@ -547,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:
@@ -583,27 +577,8 @@ class stock_quant(osv.osv):
                 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']),
     ]
 
 
@@ -785,6 +760,7 @@ 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):
@@ -847,11 +823,11 @@ class stock_picking(osv.osv):
         return True
 
     def cancel_assign(self, cr, uid, ids, context=None):
-        """ Cancels picking and moves.
+        """ Cancels picking for the moves that are in the assigned state
         @return: True
         """
         for pick in self.browse(cr, uid, ids, context=context):
-            move_ids = [x.id for x in pick.move_lines]
+            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
 
@@ -918,7 +894,7 @@ class stock_picking(osv.osv):
             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
@@ -1007,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
 
@@ -1083,7 +1060,10 @@ 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'):
@@ -1093,7 +1073,7 @@ class stock_picking(osv.osv):
                         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)
@@ -1187,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
@@ -1268,6 +1276,12 @@ 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'),
@@ -1306,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'),
@@ -1416,6 +1430,10 @@ class stock_move(osv.osv):
         quant_obj = self.pool.get("stock.quant")
         for move in self.browse(cr, uid, move_ids, 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 "/")
@@ -1461,18 +1479,25 @@ class stock_move(osv.osv):
         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_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 """
@@ -1654,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)
@@ -1702,9 +1726,7 @@ class stock_move(osv.osv):
         """ Changes the state to assigned.
         @return: True
         """
-        self.action_assign(cr, uid, ids, context=context)
-        self.write(cr, uid, ids, {'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.
@@ -1712,18 +1734,30 @@ 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:
                 #build the prefered domain based on quants that moved in previous linked done move
@@ -1749,7 +1783,11 @@ class stock_move(osv.osv):
                     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)
 
-        self._putaway_apply(cr, uid, ids, 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)
 
     def action_cancel(self, cr, uid, ids, context=None):
         """ Cancels the moves and if all moves are cancelled it cancels the picking.
@@ -1782,6 +1820,7 @@ class stock_move(osv.osv):
         @return:
         """
         context = context or {}
+        picking_obj = self.pool.get("stock.picking")
         quant_obj = self.pool.get("stock.quant")
         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"]
@@ -1799,8 +1838,9 @@ class stock_move(osv.osv):
             fallback_domain = [('reservation_id', '=', False)]
             #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, 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
@@ -1812,8 +1852,9 @@ class stock_move(osv.osv):
                 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:
-                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, context=context)
-                quant_obj.quants_move(cr, uid, quants, move, context=context)
+                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)
 
@@ -1828,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):
@@ -1837,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
@@ -1868,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)
 
@@ -1884,7 +1932,7 @@ 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):
+    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 (given in move UoM)
@@ -1907,16 +1955,18 @@ class stock_move(osv.osv):
                 ctx = context.copy()
                 if location_id:
                     ctx['source_location_id'] = location_id
-                res.append(self.split(cr, uid, move, move_qty - quantity_rest, ctx))
+                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:
                 res.append(move.id)
                 if location_id:
-                    self.write(cr, uid, [move.id], {'location_id': location_id}, context=context)
+                    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):
+    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)
@@ -1940,7 +1990,9 @@ class stock_move(osv.osv):
             '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']
@@ -1981,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
@@ -1992,9 +2044,9 @@ class stock_inventory(osv.osv):
         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')))
         if stock_settings.group_stock_tracking_lot:
+            res_filter.append(('lot', _('One Lot/Serial Number')))
+        if stock_settings.group_stock_packaging:
             res_filter.append(('pack', _('A Pack')))
         return res_filter
 
@@ -2127,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):
@@ -2183,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),
@@ -2195,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 = {
@@ -2645,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)
@@ -2672,11 +2740,19 @@ 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'),
@@ -2686,15 +2762,19 @@ class stock_warehouse(osv.osv):
             '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': 'outgoing',
             'sequence_id': out_seq_id,
+            '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,
@@ -2703,6 +2783,7 @@ class stock_warehouse(osv.osv):
             'default_location_src_id': wh_stock_loc.id,
             'default_location_dest_id': wh_stock_loc.id,
             'active': True,
+            'sequence': max_sequence + 2,
             'color': color}, context=context)
         pack_type_id = picking_type_obj.create(cr, uid, vals={
             'name': _('Pack'),
@@ -2712,6 +2793,7 @@ class stock_warehouse(osv.osv):
             'default_location_src_id': wh_pack_stock_loc.id,
             'default_location_dest_id': output_loc.id,
             'active': delivery_steps == 'pick_pack_ship',
+            'sequence': max_sequence + 3,
             'color': color}, context=context)
         pick_type_id = picking_type_obj.create(cr, uid, vals={
             'name': _('Pick'),
@@ -2721,6 +2803,7 @@ class stock_warehouse(osv.osv):
             'default_location_src_id': wh_stock_loc.id,
             'default_location_dest_id': wh_pack_stock_loc.id,
             'active': delivery_steps != 'ship_only',
+            'sequence': max_sequence + 2,
             'color': color}, context=context)
 
         #write picking types on WH
@@ -2883,7 +2966,7 @@ class stock_location_path(osv.osv):
 
     def _get_rules(self, cr, uid, ids, context=None):
         res = []
-        for route in self.browse(cr, uid, ids):
+        for route in self.browse(cr, uid, ids, context=context):
             res += [x.id for x in route.push_ids]
         return res
 
@@ -3243,7 +3326,7 @@ class stock_pack_operation(osv.osv):
             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, uid, quant.id, {'package_id': operation.result_package_id.id}, context=context)
+                    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)
@@ -3447,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.
@@ -3466,31 +3550,50 @@ 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], 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[10 - (month_delta.months + 1)]['value'] = groupby.get(value_field, 0)
+                    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(DEFAULT_SERVER_DATE_FORMAT)
-        groupby_end = (month_begin + relativedelta.relativedelta(months=3)).strftime(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),
             ]
@@ -3501,7 +3604,7 @@ class stock_picking_type(osv.osv):
         obj = self.pool.get('stock.picking')
         domains = {
             'count_picking_draft': [('state', '=', 'draft')],
-            'count_picking_waiting': [('state','in', ('confirmed', 'waiting'))],
+            'count_picking_waiting': [('state','=', 'confirmed')],
             'count_picking_ready': [('state','=','assigned')],
             'count_picking': [('state','in',('assigned','waiting','confirmed'))],
             'count_picking_late': [('min_date','<', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ('state','in',('assigned','waiting','confirmed'))],
@@ -3596,6 +3699,7 @@ class stock_picking_type(osv.osv):
         'complete_name': fields.function(_get_name, type='char', string='Name'),
         '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'),
+        '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'),
@@ -3605,9 +3709,12 @@ class stock_picking_type(osv.osv):
         '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'),