2012-01-16 14:18:24 +00:00
# -*- coding: utf-8 -*-
2006-12-07 13:41:40 +00:00
##############################################################################
2009-11-13 05:41:16 +00:00
#
2008-11-04 05:29:09 +00:00
# OpenERP, Open Source Management Solution
2010-01-12 09:18:39 +00:00
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
2006-12-07 13:41:40 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is free software: you can redistribute it and/or modify
2009-10-14 11:15:34 +00:00
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
2006-12-07 13:41:40 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2009-10-14 11:15:34 +00:00
# GNU Affero General Public License for more details.
2006-12-07 13:41:40 +00:00
#
2009-10-14 11:15:34 +00:00
# You should have received a copy of the GNU Affero General Public License
2009-11-13 05:41:16 +00:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2006-12-07 13:41:40 +00:00
#
##############################################################################
2013-07-28 12:42:01 +00:00
from datetime import date , datetime
from dateutil import relativedelta
2010-07-06 08:59:22 +00:00
import time
2010-03-01 05:05:14 +00:00
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
from openerp . tools . translate import _
from openerp import tools
2013-08-05 23:23:48 +00:00
from openerp . tools import DEFAULT_SERVER_DATETIME_FORMAT
2013-07-17 13:26:27 +00:00
from openerp import SUPERUSER_ID
2012-12-17 15:23:03 +00:00
import openerp . addons . decimal_precision as dp
2010-09-03 10:01:28 +00:00
import logging
2012-06-22 06:48:54 +00:00
_logger = logging . getLogger ( __name__ )
2006-12-07 13:41:40 +00:00
#----------------------------------------------------------
# Incoterms
#----------------------------------------------------------
class stock_incoterms ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " stock.incoterms "
_description = " Incoterms "
_columns = {
2013-07-29 21:44:43 +00:00
' name ' : fields . char ( ' Name ' , size = 64 , required = True , help = " Incoterms are series of sales terms. They are used to divide transaction costs and responsibilities between buyer and seller and reflect state-of-the-art transportation practices. " ) ,
' code ' : fields . char ( ' Code ' , size = 3 , required = True , help = " Incoterm Standard Code " ) ,
' active ' : fields . boolean ( ' Active ' , help = " By unchecking the active field, you may hide an INCOTERM you will not use. " ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
2010-07-06 11:21:27 +00:00
' active ' : True ,
2008-07-22 15:11:28 +00:00
}
2009-08-28 09:40:49 +00:00
2006-12-07 13:41:40 +00:00
#----------------------------------------------------------
# Stock Location
#----------------------------------------------------------
2013-06-29 22:17:03 +00:00
2006-12-07 13:41:40 +00:00
class stock_location ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " stock.location "
2013-06-29 22:17:03 +00:00
_description = " Inventory Locations "
2008-07-22 15:11:28 +00:00
_parent_name = " location_id "
2008-09-17 07:49:51 +00:00
_parent_store = True
2013-06-29 22:17:03 +00:00
_parent_order = ' name '
2008-09-17 07:49:51 +00:00
_order = ' parent_left '
2010-07-06 11:21:27 +00:00
def name_get ( self , cr , uid , ids , context = None ) :
2012-01-13 15:15:48 +00:00
res = self . _complete_name ( cr , uid , ids , ' complete_name ' , None , context = context )
2013-08-16 15:02:26 +00:00
return res . items ( )
2009-09-24 10:46:21 +00:00
2010-07-06 11:21:27 +00:00
def _complete_name ( self , cr , uid , ids , name , args , context = None ) :
2010-05-26 12:59:30 +00:00
""" Forms complete name of location from parent location to child location.
@return : Dictionary of values
"""
2008-09-17 07:49:51 +00:00
res = { }
for m in self . browse ( cr , uid , ids , context = context ) :
2013-06-29 22:17:03 +00:00
res [ m . id ] = m . name
2012-01-13 15:15:48 +00:00
parent = m . location_id
while parent :
2013-06-29 22:17:03 +00:00
res [ m . id ] = parent . name + ' / ' + res [ m . id ]
2012-01-13 15:15:48 +00:00
parent = parent . location_id
2008-09-17 07:49:51 +00:00
return res
2012-01-27 13:55:37 +00:00
def _get_sublocations ( self , cr , uid , ids , context = None ) :
""" return all sublocations of the given stock locations (included) """
return self . search ( cr , uid , [ ( ' id ' , ' child_of ' , ids ) ] , context = context )
2009-02-11 13:32:54 +00:00
2008-07-22 15:11:28 +00:00
_columns = {
2008-08-26 22:18:32 +00:00
' name ' : fields . char ( ' Location Name ' , size = 64 , required = True , translate = True ) ,
2010-09-06 13:51:23 +00:00
' active ' : fields . boolean ( ' Active ' , help = " By unchecking the active field, you may hide a location without deleting it. " ) ,
2010-08-30 12:03:58 +00:00
' usage ' : fields . selection ( [ ( ' supplier ' , ' Supplier Location ' ) , ( ' view ' , ' View ' ) , ( ' internal ' , ' Internal Location ' ) , ( ' customer ' , ' Customer Location ' ) , ( ' inventory ' , ' Inventory ' ) , ( ' procurement ' , ' Procurement ' ) , ( ' production ' , ' Production ' ) , ( ' transit ' , ' Transit Location for Inter-Companies Transfers ' ) ] , ' Location Type ' , required = True ,
2010-09-06 13:51:23 +00:00
help = """ * Supplier Location: Virtual location representing the source location for products coming from your suppliers
\n * View : Virtual location used to create a hierarchical structures for your warehouse , aggregating its child locations ; can ' t directly contain products
\n * Internal Location : Physical locations inside your own warehouses ,
\n * Customer Location : Virtual location representing the destination location for products sent to your customers
\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),
2013-06-29 22:17:03 +00:00
2013-06-30 14:29:27 +00:00
' complete_name ' : fields . function ( _complete_name , type = ' char ' , string = " Location Name " ,
2012-01-27 13:55:37 +00:00
store = { ' stock.location ' : ( _get_sublocations , [ ' name ' , ' location_id ' ] , 10 ) } ) ,
2008-09-17 08:59:36 +00:00
' location_id ' : fields . many2one ( ' stock.location ' , ' Parent Location ' , select = True , ondelete = ' cascade ' ) ,
2008-07-22 15:11:28 +00:00
' child_ids ' : fields . one2many ( ' stock.location ' , ' location_id ' , ' Contains ' ) ,
2013-07-24 15:16:11 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' , help = " Owner of the location if not internal " ) ,
2008-07-22 15:11:28 +00:00
' comment ' : fields . text ( ' Additional Information ' ) ,
2013-07-11 13:05:28 +00:00
' posx ' : fields . integer ( ' Corridor (X) ' , help = " Optional localization details, for information purpose only " ) ,
2010-08-31 11:29:56 +00:00
' posy ' : fields . integer ( ' Shelves (Y) ' , help = " Optional localization details, for information purpose only " ) ,
' posz ' : fields . integer ( ' Height (Z) ' , help = " Optional localization details, for information purpose only " ) ,
2008-09-17 08:59:36 +00:00
' parent_left ' : fields . integer ( ' Left Parent ' , select = 1 ) ,
' parent_right ' : fields . integer ( ' Right Parent ' , select = 1 ) ,
2013-06-29 22:17:03 +00:00
2010-09-06 13:51:23 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , select = 1 , help = ' Let this field empty if this location is shared between all companies ' ) ,
2010-08-30 11:33:00 +00:00
' scrap_location ' : fields . boolean ( ' Scrap Location ' , help = ' Check this box to allow using this location to put scrapped/damaged goods. ' ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
2010-07-06 11:44:51 +00:00
' active ' : True ,
2010-07-06 11:21:27 +00:00
' usage ' : ' internal ' ,
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.location ' , context = c ) ,
' posx ' : 0 ,
' posy ' : 0 ,
' posz ' : 0 ,
' scrap_location ' : False ,
2008-07-22 15:11:28 +00:00
}
2013-06-30 10:20:28 +00:00
def get_removal_strategy ( self , cr , uid , location , product , context = None ) :
2013-07-01 19:13:08 +00:00
return None
2009-08-28 09:40:49 +00:00
2013-06-30 14:29:27 +00:00
#----------------------------------------------------------
# Quants
#----------------------------------------------------------
2013-06-19 08:35:48 +00:00
class stock_quant ( osv . osv ) :
"""
2013-06-29 22:17:03 +00:00
Quants are the smallest unit of stock physical instances
2013-06-19 08:35:48 +00:00
"""
_name = " stock.quant "
_description = " Quants "
2013-08-06 15:36:01 +00:00
def _get_quant_name ( self , cr , uid , ids , name , args , context = None ) :
""" Forms complete name of location from parent location to child location.
@return : Dictionary of values
"""
res = { }
for q in self . browse ( cr , uid , ids , context = context ) :
2013-08-08 13:21:02 +00:00
res [ q . id ] = q . product_id . code or ' '
2013-08-06 15:36:01 +00:00
if q . lot_id :
res [ q . id ] = q . lot_id . name
res [ q . id ] + = ' : ' + str ( q . qty ) + q . product_id . uom_id . name
return res
2013-06-19 08:35:48 +00:00
_columns = {
2013-08-06 15:36:01 +00:00
' name ' : fields . function ( _get_quant_name , type = ' char ' , string = ' Identifier ' ) ,
2013-06-24 09:41:50 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True ) ,
2013-06-20 16:41:06 +00:00
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True ) ,
2013-06-29 22:17:03 +00:00
' qty ' : fields . float ( ' Quantity ' , required = True , help = " Quantity of products in this quant, in the default unit of measure of the product " ) ,
2013-07-24 15:16:11 +00:00
' package_id ' : fields . many2one ( ' stock.quant.package ' , string = ' Package ' , help = " The package containing this quant " ) ,
2013-08-07 10:22:57 +00:00
' packaging_type_id ' : fields . related ( ' package_id ' , ' packaging_id ' , type = ' many2one ' , relation = ' product.packaging ' , string = ' Type of packaging ' , store = True ) ,
2013-07-24 15:16:11 +00:00
' reservation_id ' : fields . many2one ( ' stock.move ' , ' Reserved for Move ' , help = " Is this quant reserved for a stock.move? " ) ,
' lot_id ' : fields . many2one ( ' stock.production.lot ' , ' Lot ' ) ,
' cost ' : fields . float ( ' Unit Cost ' ) ,
2013-08-30 13:25:07 +00:00
' owner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' , help = " This is the owner of the quant " ) ,
2013-06-19 08:35:48 +00:00
2013-07-24 15:16:11 +00:00
' create_date ' : fields . datetime ( ' Creation Date ' ) ,
' in_date ' : fields . datetime ( ' Incoming Date ' ) ,
2013-06-19 14:54:06 +00:00
2013-06-29 22:17:03 +00:00
' history_ids ' : fields . many2many ( ' stock.move ' , ' stock_quant_move_rel ' , ' quant_id ' , ' move_id ' , ' Moves ' , help = ' Moves that operate(d) on this quant ' ) ,
2013-08-02 09:04:46 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , help = " The company to which the quants belong " , required = True ) ,
2013-06-19 14:54:06 +00:00
2013-06-29 22:17:03 +00:00
# Used for negative quants to reconcile after compensated by a new positive one
2013-07-24 15:16:11 +00:00
' propagated_from_id ' : fields . many2one ( ' stock.quant ' , ' Linked Quant ' , help = ' The negative quant this is coming from ' ) ,
2013-06-29 22:17:03 +00:00
}
2013-07-24 15:16:11 +00:00
2013-08-02 09:04:46 +00:00
_defaults = {
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.quant ' , context = c ) ,
}
2013-07-16 16:11:07 +00:00
def _check_qorder ( self , word ) :
"""
Needs to pass True to allow " expression order " in search
"""
return True
2013-06-19 14:54:06 +00:00
2013-06-30 08:55:03 +00:00
def quants_reserve ( self , cr , uid , quants , move , context = None ) :
2013-06-29 22:17:03 +00:00
toreserve = [ ]
for quant , qty in quants :
if not quant : continue
self . _quant_split ( cr , uid , quant , qty , context = context )
toreserve . append ( quant . id )
return self . write ( cr , uid , toreserve , { ' reservation_id ' : move . id } , context = context )
2013-07-19 11:55:33 +00:00
# add location_dest_id in parameters (False=use the destination of the move)
2013-06-30 08:55:03 +00:00
def quants_move ( self , cr , uid , quants , move , context = None ) :
2013-07-11 13:05:28 +00:00
for quant , qty in quants :
self . move_single_quant ( cr , uid , quant , qty , move , context = context )
2013-06-30 08:55:03 +00:00
2013-07-19 11:55:33 +00:00
def check_preferred_location ( self , cr , uid , move , context = None ) :
return move . location_dest_id
2013-07-11 13:05:28 +00:00
def move_single_quant ( self , cr , uid , quant , qty , move , context = None ) :
if not quant :
quant = self . _quant_create ( cr , uid , qty , move , context = context )
else :
self . _quant_split ( cr , uid , quant , qty , context = context )
# FP Note: improve this using preferred locations
2013-07-19 11:55:33 +00:00
location_to = self . check_preferred_location ( cr , uid , move , context = context )
2013-07-11 13:05:28 +00:00
self . write ( cr , uid , [ quant . id ] , {
' location_id ' : location_to . id ,
2013-07-17 13:26:27 +00:00
' reservation_id ' : move . move_dest_id and move . move_dest_id . id or False ,
2013-07-11 13:05:28 +00:00
' history_ids ' : [ ( 4 , move . id ) ]
} )
2013-07-17 13:26:27 +00:00
quant . refresh ( )
self . _quant_reconcile_negative ( cr , uid , quant , context = context )
2013-07-15 09:30:53 +00:00
return quant
2013-06-27 23:30:58 +00:00
2013-07-16 10:15:40 +00:00
def quants_get ( self , cr , uid , location , product , qty , domain = None , prefered_order = False , reservedcontext = None , context = None ) :
2013-06-27 23:30:58 +00:00
"""
2013-06-29 22:17:03 +00:00
Use the removal strategies of product to search for the correct quants
2013-07-01 09:09:23 +00:00
If you inherit , put the super at the end of your method .
2013-06-29 22:17:03 +00:00
2013-07-23 15:34:24 +00:00
: location : browse record of the parent location in which the quants have to be found
: product : browse record of the product to find
2013-06-29 22:17:03 +00:00
: qty in UoM of product
2013-06-30 08:55:03 +00:00
: lot_id NOT USED YET !
2013-06-27 23:30:58 +00:00
"""
2013-07-17 13:26:27 +00:00
result = [ ]
2013-06-30 20:13:23 +00:00
domain = domain or [ ( ' qty ' , ' > ' , 0.0 ) ]
2013-07-17 13:26:27 +00:00
if location :
2013-07-11 13:02:37 +00:00
removal_strategy = self . pool . get ( ' stock.location ' ) . get_removal_strategy ( cr , uid , location , product , context = context ) or ' fifo '
if removal_strategy == ' fifo ' :
2013-07-16 10:15:40 +00:00
result + = self . _quants_get_fifo ( cr , uid , location , product , qty , domain , prefered_order = prefered_order , context = context )
2013-07-11 13:02:37 +00:00
elif removal_strategy == ' lifo ' :
2013-07-16 10:15:40 +00:00
result + = self . _quants_get_lifo ( cr , uid , location , product , qty , domain , prefered_order = prefered_order , context = context )
2013-07-11 13:02:37 +00:00
else :
2013-07-17 13:26:27 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' Removal strategy %s not implemented. ' % ( removal_strategy , ) ) )
2013-09-03 16:27:57 +00:00
print ' Quant get result ' , result
2013-06-29 22:17:03 +00:00
return result
2013-06-19 14:54:06 +00:00
2013-06-29 22:17:03 +00:00
#
# Create a quant in the destination location
# Create a negative quant in the source location if it's an internal location
# Reconcile a positive quant with a negative is possible
2013-07-29 21:44:43 +00:00
#
2013-08-22 09:48:13 +00:00
def _quant_create ( self , cr , uid , qty , move , lot_id = False , context = None ) :
2013-06-30 10:20:28 +00:00
# FP Note: TODO: compute the right price according to the move, with currency convert
# QTY is normally already converted to main product's UoM
2013-09-03 16:27:57 +00:00
price_unit = self . pool . get ( ' stock.move ' ) . get_price_unit ( cr , uid , move , context = context )
2013-07-17 13:26:27 +00:00
vals = {
' product_id ' : move . product_id . id ,
' location_id ' : move . location_dest_id . id ,
' qty ' : qty ,
' cost ' : price_unit ,
' history_ids ' : [ ( 4 , move . id ) ] ,
' in_date ' : datetime . now ( ) . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' company_id ' : move . company_id . id ,
2013-09-03 14:28:47 +00:00
' lot_id ' : lot_id ,
2013-07-17 13:26:27 +00:00
}
2013-06-30 10:20:28 +00:00
2013-07-17 13:26:27 +00:00
if move . location_id . usage == ' internal ' :
#if we were trying to move something from an internal location and reach here (quant creation),
#it means that a negative quant has to be created as well.
negative_vals = vals . copy ( )
negative_vals [ ' location_id ' ] = move . location_id . id
negative_vals [ ' qty ' ] = - qty
negative_vals [ ' cost ' ] = price_unit
negative_quant_id = self . create ( cr , uid , negative_vals , context = context )
2013-09-03 16:27:57 +00:00
vals . update ( { ' propagated_from_id ' : negative_quant_id } )
2013-07-17 13:26:27 +00:00
#create the quant
quant_id = self . create ( cr , uid , vals , context = context )
return self . browse ( cr , uid , quant_id , context = context )
2013-06-29 22:17:03 +00:00
2013-06-30 20:13:23 +00:00
def _quant_split ( self , cr , uid , quant , qty , context = None ) :
2013-07-17 13:26:27 +00:00
context = context or { }
if ( quant . qty > 0 and quant . qty < = qty ) or ( quant . qty < = 0 and quant . qty > = qty ) :
2013-06-29 22:17:03 +00:00
return False
2013-07-17 13:26:27 +00:00
new_quant = self . copy ( cr , uid , quant . id , default = { ' qty ' : quant . qty - qty } , context = context )
2013-06-29 22:17:03 +00:00
self . write ( cr , uid , quant . id , { ' qty ' : qty } , context = context )
quant . refresh ( )
2013-07-17 13:26:27 +00:00
return self . browse ( cr , uid , new_quant , context = context )
def _get_latest_move ( self , cr , uid , quant , context = None ) :
move = False
for m in quant . history_ids :
if not move or m . date > move . date :
move = m
return move
2013-06-21 16:23:48 +00:00
2013-06-29 22:17:03 +00:00
def _quant_reconcile_negative ( self , cr , uid , quant , context = None ) :
2013-07-17 13:26:27 +00:00
"""
When new quant arrive in a location , try to reconcile it with
negative quants . If it ' s possible, apply the cost of the new
quant to the conter - part of the negative quant .
"""
if quant . location_id . usage != ' internal ' :
return False
quants = self . quants_get ( cr , uid , quant . location_id , quant . product_id , quant . qty , [ ( ' qty ' , ' < ' , ' 0 ' ) ] , context = context )
2013-06-30 20:13:23 +00:00
result = False
for quant_neg , qty in quants :
2013-07-17 13:26:27 +00:00
if not quant_neg :
continue
result = True
2013-07-23 15:34:24 +00:00
to_solve_quant = self . search ( cr , uid , [ ( ' propagated_from_id ' , ' = ' , quant_neg . id ) , ( ' id ' , ' != ' , quant . id ) ] , context = context )
2013-07-17 13:26:27 +00:00
if not to_solve_quant :
continue
to_solve_quant = self . browse ( cr , uid , to_solve_quant [ 0 ] , context = context )
move = self . _get_latest_move ( cr , uid , to_solve_quant , context = context )
2013-06-30 20:13:23 +00:00
self . _quant_split ( cr , uid , quant , qty , context = context )
2013-07-17 13:26:27 +00:00
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
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 )
2013-06-30 20:13:23 +00:00
return result
2013-06-21 16:23:48 +00:00
2013-06-30 10:20:28 +00:00
def _price_update ( self , cr , uid , quant , newprice , context = None ) :
self . write ( cr , uid , [ quant . id ] , { ' cost ' : newprice } , context = context )
2013-06-21 16:23:48 +00:00
2013-08-26 10:32:24 +00:00
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 , uid , ids , vals , context = context )
2013-07-16 10:15:40 +00:00
def quants_unreserve ( self , cr , uid , move , context = None ) :
2013-08-26 10:32:24 +00:00
#cr.execute('update stock_quant set reservation_id=NULL where reservation_id=%s', (move.id,))
#need write for related store of remaining qty
2013-08-29 13:02:07 +00:00
related_quants = [ x . id for x in move . reserved_quant_ids ]
2013-08-26 10:32:24 +00:00
self . write ( cr , uid , related_quants , { ' reservation_id ' : False } , context = context )
2013-07-16 10:15:40 +00:00
return True
2013-06-29 22:17:03 +00:00
#
# Implementation of removal strategies
2013-07-16 10:15:40 +00:00
# If it can not reserve, it will return a tuple (None, qty)
2013-06-29 22:17:03 +00:00
#
2013-06-30 10:20:28 +00:00
def _quants_get_order ( self , cr , uid , location , product , quantity , domain = [ ] , orderby = ' in_date ' , context = None ) :
2013-06-30 20:13:23 +00:00
domain + = location and [ ( ' location_id ' , ' child_of ' , location . id ) ] or [ ]
2013-07-16 10:15:40 +00:00
domain + = [ ( ' product_id ' , ' = ' , product . id ) ] + domain
2013-06-19 08:35:48 +00:00
res = [ ]
2013-06-29 22:17:03 +00:00
offset = 0
while quantity > 0 :
quants = self . search ( cr , uid , domain , order = orderby , limit = 10 , offset = offset , context = context )
if not quants :
res . append ( ( None , quantity ) )
2013-06-19 08:35:48 +00:00
break
2013-06-29 22:17:03 +00:00
for quant in self . browse ( cr , uid , quants , context = context ) :
if quantity > = abs ( quant . qty ) :
res + = [ ( quant , abs ( quant . qty ) ) ]
quantity - = abs ( quant . qty )
2013-07-26 12:55:32 +00:00
elif quantity != 0 :
2013-06-29 22:17:03 +00:00
res + = [ ( quant , quantity ) ]
quantity = 0
2013-06-30 20:13:23 +00:00
break
2013-06-29 22:17:03 +00:00
offset + = 10
2013-06-19 08:35:48 +00:00
return res
2013-07-16 10:15:40 +00:00
def _quants_get_fifo ( self , cr , uid , location , product , quantity , domain = [ ] , prefered_order = False , context = None ) :
order = ' in_date '
if prefered_order :
order = prefered_order + ' , in_date '
2013-07-17 13:26:27 +00:00
return self . _quants_get_order ( cr , uid , location , product , quantity , domain , order , context = context )
2013-06-19 08:35:48 +00:00
2013-07-16 10:15:40 +00:00
def _quants_get_lifo ( self , cr , uid , location , product , quantity , domain = [ ] , prefered_order = False , context = None ) :
order = ' in_date desc '
if prefered_order :
order = prefered_order + ' , in_date desc '
2013-07-17 13:26:27 +00:00
return self . _quants_get_order ( cr , uid , location , product , quantity , domain , order , context = context )
2012-01-11 15:09:42 +00:00
2013-06-30 10:20:28 +00:00
# Return the company owning the location if any
def _location_owner ( self , cr , uid , quant , location , context = None ) :
return location and ( location . usage == ' internal ' ) and location . company_id or False
2013-07-29 21:44:43 +00:00
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 ) )
return True
_constraints = [
( _check_location , ' You cannot move products to a location of the type view. ' , [ ' location_id ' ] )
]
2006-12-07 13:41:40 +00:00
#----------------------------------------------------------
# Stock Picking
#----------------------------------------------------------
2013-06-30 14:29:27 +00:00
2006-12-07 13:41:40 +00:00
class stock_picking ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " stock.picking "
2012-04-03 12:10:37 +00:00
_inherit = [ ' mail.thread ' ]
2009-11-13 05:41:16 +00:00
_description = " Picking List "
2013-01-16 10:55:05 +00:00
_order = " id desc "
2010-07-06 11:21:27 +00:00
def get_min_max_date ( self , cr , uid , ids , field_name , arg , context = None ) :
2010-05-26 12:59:30 +00:00
""" Finds minimum and maximum dates for picking.
@return : Dictionary of values
"""
2008-09-05 07:12:28 +00:00
res = { }
2008-09-16 10:37:53 +00:00
for id in ids :
2009-08-28 09:40:49 +00:00
res [ id ] = { ' min_date ' : False , ' max_date ' : False }
2008-09-16 10:37:53 +00:00
if not ids :
return res
cr . execute ( """ select
picking_id ,
2010-11-15 08:36:42 +00:00
min ( date_expected ) ,
max ( date_expected )
2008-09-16 10:37:53 +00:00
from
2008-11-03 14:40:49 +00:00
stock_move
2008-09-16 10:37:53 +00:00
where
2010-06-16 11:51:39 +00:00
picking_id IN % s
2008-09-16 10:37:53 +00:00
group by
2010-06-16 11:51:39 +00:00
picking_id """ ,(tuple(ids),))
2009-08-28 09:40:49 +00:00
for pick , dt1 , dt2 in cr . fetchall ( ) :
2010-11-24 17:21:57 +00:00
res [ pick ] [ ' min_date ' ] = dt1
res [ pick ] [ ' max_date ' ] = dt2
2008-09-05 07:12:28 +00:00
return res
2012-08-07 11:06:16 +00:00
2009-02-27 07:04:59 +00:00
def create ( self , cr , user , vals , context = None ) :
2013-07-29 21:44:43 +00:00
context = context or { }
2013-06-29 22:17:03 +00:00
if ( ' name ' not in vals ) or ( vals . get ( ' name ' ) in ( ' / ' , False ) ) :
2013-08-02 14:52:39 +00:00
ptype_id = vals . get ( ' picking_type_id ' , context . get ( ' default_picking_type_id ' , False ) )
2013-07-29 21:44:43 +00:00
sequence_id = self . pool . get ( ' stock.picking.type ' ) . browse ( cr , user , ptype_id , context = context ) . sequence_id . id
2013-07-24 17:00:17 +00:00
vals [ ' name ' ] = self . pool . get ( ' ir.sequence ' ) . get_id ( cr , user , sequence_id , ' id ' , context = context )
2013-06-29 22:17:03 +00:00
return super ( stock_picking , self ) . create ( cr , user , vals , context )
2009-01-09 13:48:17 +00:00
2013-07-28 17:27:17 +00:00
# The state of a picking depends on the state of its related stock.move
2013-07-28 18:09:34 +00:00
# draft: the picking has no line or any one of the lines is draft
2013-07-28 17:27:17 +00:00
# done, draft, cancel: all lines are done / draft / cancel
# confirmed, auto, assigned depends on move_type (all at once or direct)
2013-07-28 18:09:34 +00:00
def _state_get ( self , cr , uid , ids , field_name , arg , context = None ) :
2013-07-17 15:29:03 +00:00
res = { }
for pick in self . browse ( cr , uid , ids , context = context ) :
2013-07-28 18:09:34 +00:00
if ( not pick . move_lines ) or any ( [ x . state == ' draft ' for x in pick . move_lines ] ) :
res [ pick . id ] = ' draft '
continue
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 ] ) :
res [ pick . id ] = ' done '
continue
2013-08-16 15:02:26 +00:00
order = { ' confirmed ' : 0 , ' waiting ' : 1 , ' assigned ' : 2 }
2013-07-28 17:27:17 +00:00
order_inv = dict ( zip ( order . values ( ) , order . keys ( ) ) )
2013-07-30 08:47:16 +00:00
lst = [ order [ x . state ] for x in pick . move_lines if x . state not in ( ' cancel ' , ' done ' ) ]
2013-07-28 17:27:17 +00:00
if pick . move_lines == ' one ' :
2013-07-28 18:09:34 +00:00
res [ pick . id ] = order_inv [ min ( lst ) ]
2013-07-28 17:27:17 +00:00
else :
2013-07-28 18:09:34 +00:00
res [ pick . id ] = order_inv [ max ( lst ) ]
2013-07-17 15:29:03 +00:00
return res
def _get_pickings ( self , cr , uid , ids , context = None ) :
res = set ( )
for move in self . browse ( cr , uid , ids , context = context ) :
2013-07-29 21:44:43 +00:00
if move . picking_id :
2013-07-17 15:29:03 +00:00
res . add ( move . picking_id . id )
return list ( res )
2013-07-26 18:09:42 +00:00
2013-08-02 13:49:00 +00:00
def _get_pack_operation_exist ( self , cr , uid , ids , field_name , arg , context = None ) :
res = { }
for pick in self . browse ( cr , uid , ids , context = context ) :
res [ pick . id ] = False
if pick . pack_operation_ids :
res [ pick . id ] = True
return res
2008-07-22 15:11:28 +00:00
_columns = {
2012-03-20 09:47:17 +00:00
' name ' : fields . char ( ' Reference ' , size = 64 , select = True , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
2012-10-08 10:41:51 +00:00
' origin ' : fields . char ( ' Source Document ' , size = 64 , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , help = " Reference of the document " , select = True ) ,
2012-03-21 11:14:45 +00:00
' 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 ) ,
2012-03-20 09:47:17 +00:00
' note ' : fields . text ( ' Notes ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
2012-03-21 11:43:30 +00:00
' 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 " ) ,
2013-07-30 08:58:59 +00:00
' 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 = [
2012-05-04 15:36:13 +00:00
( ' draft ' , ' Draft ' ) ,
2012-05-01 12:34:46 +00:00
( ' cancel ' , ' Cancelled ' ) ,
2013-08-16 15:02:26 +00:00
( ' waiting ' , ' Waiting Another Operation ' ) ,
2012-05-03 10:29:10 +00:00
( ' confirmed ' , ' Waiting Availability ' ) ,
2012-05-04 15:36:13 +00:00
( ' assigned ' , ' Ready to Transfer ' ) ,
( ' done ' , ' Transferred ' ) ,
2013-07-17 15:29:03 +00:00
] , string = ' Status ' , readonly = True , select = True , track_visibility = ' onchange ' , help = """
2012-05-04 15:36:13 +00:00
* 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 " " "
) ,
2013-06-29 22:17:03 +00:00
' min_date ' : fields . function ( get_min_max_date , multi = " min_max_date " ,
2013-08-04 00:07:18 +00:00
store = { ' stock.move ' : ( _get_pickings , [ ' state ' , ' date_expected ' ] , 20 ) } , type = ' datetime ' , string = ' Scheduled Time ' , select = 1 , help = " Scheduled time for the first part of the shipment to be processed " ) ,
2013-06-29 22:17:03 +00:00
' max_date ' : fields . function ( get_min_max_date , multi = " min_max_date " ,
2013-08-04 00:07:18 +00:00
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 " ) ,
2013-08-04 00:22:25 +00:00
' 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 ) ] } ) ,
2013-07-29 21:44:43 +00:00
' date_done ' : fields . datetime ( ' Date of Transfer ' , help = " Date of Completion " , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
2010-05-10 05:46:20 +00:00
' move_lines ' : fields . one2many ( ' stock.move ' , ' picking_id ' , ' Internal Moves ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
2012-04-02 10:24:12 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Partner ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
2012-03-20 09:47:17 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True , select = True , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
2013-07-24 17:30:39 +00:00
' pack_operation_ids ' : fields . one2many ( ' stock.pack.operation ' , ' picking_id ' , string = ' Related Packing Operations ' ) ,
2013-08-02 13:49:00 +00:00
' pack_operation_exist ' : fields . function ( _get_pack_operation_exist , type = ' boolean ' , string = ' Pack Operation Exists? ' , help = ' technical field for attrs in view ' ) ,
2013-07-29 21:44:43 +00:00
' picking_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Picking Type ' , required = True ) ,
# Used to search on pickings
2013-07-24 15:32:35 +00:00
' 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 ) ,
2008-07-22 15:11:28 +00:00
}
_defaults = {
2012-09-27 09:03:52 +00:00
' name ' : lambda self , cr , uid , context : ' / ' ,
2010-07-06 11:21:27 +00:00
' state ' : ' draft ' ,
2013-07-29 21:44:43 +00:00
' move_type ' : ' one ' ,
2011-02-23 06:15:04 +00:00
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2010-07-06 11:21:27 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.picking ' , context = c )
2008-07-22 15:11:28 +00:00
}
2011-11-09 06:30:48 +00:00
_sql_constraints = [
2013-07-29 21:44:43 +00:00
( ' name_uniq ' , ' unique(name, company_id) ' , ' Reference must be unique per company! ' ) ,
2011-11-09 06:30:48 +00:00
]
2009-08-28 09:40:49 +00:00
2010-07-06 11:21:27 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
2009-02-05 16:41:33 +00:00
if default is None :
default = { }
default = default . copy ( )
2010-11-23 11:31:52 +00:00
picking_obj = self . browse ( cr , uid , id , context = context )
2013-03-19 12:52:44 +00:00
if ( ' name ' not in default ) or ( picking_obj . name == ' / ' ) :
2013-06-29 22:17:03 +00:00
default [ ' name ' ] = ' / '
2013-08-29 09:04:31 +00:00
if not default . get ( ' backorder_id ' ) :
default [ ' backorder_id ' ] = False
2013-06-29 22:17:03 +00:00
return super ( stock_picking , self ) . copy ( cr , uid , id , default , context )
2013-03-19 12:52:44 +00:00
2010-07-06 11:21:27 +00:00
def action_confirm ( self , cr , uid , ids , context = None ) :
2008-07-22 15:11:28 +00:00
todo = [ ]
2013-08-03 23:47:59 +00:00
todo_force_assign = [ ]
2013-07-29 21:44:43 +00:00
for picking in self . browse ( cr , uid , ids , context = context ) :
2013-08-03 23:47:59 +00:00
if picking . picking_type_id . auto_force_assign :
todo_force_assign . append ( picking . id )
2008-07-22 15:11:28 +00:00
for r in picking . move_lines :
2009-08-28 09:40:49 +00:00
if r . state == ' draft ' :
2008-11-05 09:25:04 +00:00
todo . append ( r . id )
2008-07-22 15:11:28 +00:00
if len ( todo ) :
2010-07-06 11:21:27 +00:00
self . pool . get ( ' stock.move ' ) . action_confirm ( cr , uid , todo , context = context )
2013-08-03 23:47:59 +00:00
if todo_force_assign :
self . force_assign ( cr , uid , todo_force_assign , context = context )
2008-07-22 15:11:28 +00:00
return True
2013-06-28 15:48:21 +00:00
def action_assign ( self , cr , uid , ids , * args ) :
2010-05-26 12:59:30 +00:00
""" Changes state of picking to available if all moves are confirmed.
@return : True
"""
2008-07-22 15:11:28 +00:00
for pick in self . browse ( cr , uid , ids ) :
2012-02-29 12:06:38 +00:00
if pick . state == ' draft ' :
2013-07-17 16:00:38 +00:00
self . action_confirm ( cr , uid , [ pick . id ] )
2009-08-28 09:40:49 +00:00
move_ids = [ x . id for x in pick . move_lines if x . state == ' confirmed ' ]
2010-03-18 07:12:23 +00:00
if not move_ids :
2013-08-03 23:47:59 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' No product available. ' ) )
2008-07-22 15:11:28 +00:00
self . pool . get ( ' stock.move ' ) . action_assign ( cr , uid , move_ids )
return True
2013-08-03 23:47:59 +00:00
def force_assign ( self , cr , uid , ids , context = None ) :
2010-05-26 12:59:30 +00:00
""" Changes state of picking to available if moves are confirmed or waiting.
@return : True
"""
2013-08-03 23:47:59 +00:00
for pick in self . browse ( cr , uid , ids , context = context ) :
move_ids = [ x . id for x in pick . move_lines if x . state in [ ' confirmed ' , ' waiting ' ] ]
self . pool . get ( ' stock.move ' ) . force_assign ( cr , uid , move_ids , context = context )
2008-07-22 15:11:28 +00:00
return True
2008-11-03 14:40:49 +00:00
2008-07-22 15:11:28 +00:00
def cancel_assign ( self , cr , uid , ids , * args ) :
2010-06-16 08:40:57 +00:00
""" Cancels picking and moves.
2010-05-26 12:59:30 +00:00
@return : True
"""
2008-07-22 15:11:28 +00:00
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 )
return True
2010-07-06 11:44:51 +00:00
def action_cancel ( self , cr , uid , ids , context = None ) :
2010-11-22 10:37:53 +00:00
for pick in self . browse ( cr , uid , ids , context = context ) :
2008-07-22 15:11:28 +00:00
ids2 = [ move . id for move in pick . move_lines ]
self . pool . get ( ' stock.move ' ) . action_cancel ( cr , uid , ids2 , context )
return True
def action_done ( self , cr , uid , ids , context = None ) :
2013-07-26 18:09:42 +00:00
""" Changes picking state to done by processing the Stock Moves of the Picking
2013-07-29 21:44:43 +00:00
2013-07-26 18:09:42 +00:00
Normally that happens when the button " Done " is pressed on a Picking view .
2010-05-26 12:59:30 +00:00
@return : True
"""
2010-12-13 06:43:09 +00:00
for pick in self . browse ( cr , uid , ids , context = context ) :
2008-07-22 15:11:28 +00:00
todo = [ ]
for move in pick . move_lines :
2011-09-25 23:20:56 +00:00
if move . state == ' draft ' :
self . pool . get ( ' stock.move ' ) . action_confirm ( cr , uid , [ move . id ] ,
context = context )
todo . append ( move . id )
elif move . state in ( ' assigned ' , ' confirmed ' ) :
2008-07-22 15:11:28 +00:00
todo . append ( move . id )
if len ( todo ) :
2013-06-29 22:17:03 +00:00
self . pool . get ( ' stock.move ' ) . action_done ( cr , uid , todo , context = context )
2008-07-22 15:11:28 +00:00
return True
2009-12-02 07:15:24 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
2010-05-11 09:17:05 +00:00
move_obj = self . pool . get ( ' stock.move ' )
2013-07-29 21:44:43 +00:00
context = context or { }
2009-12-02 07:15:24 +00:00
for pick in self . browse ( cr , uid , ids , context = context ) :
2013-07-29 21:44:43 +00:00
ids2 = [ move . id for move in pick . move_lines ]
2013-07-30 14:05:05 +00:00
move_obj . action_cancel ( cr , uid , ids2 , context = context )
move_obj . unlink ( cr , uid , ids2 , context = context )
2009-12-02 07:15:24 +00:00
return super ( stock_picking , self ) . unlink ( cr , uid , ids , context = context )
2009-08-28 09:40:49 +00:00
2013-07-30 07:46:15 +00:00
# Methods for partial pickings
2013-08-03 23:28:48 +00:00
def _create_backorder ( self , cr , uid , picking , backorder_moves = [ ] , context = None ) :
2013-07-30 08:58:59 +00:00
"""
Move all non - done lines into a new backorder picking
"""
2013-08-03 23:28:48 +00:00
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 ' ) ]
2013-08-26 15:36:48 +00:00
if backorder_move_ids :
backorder_id = self . copy ( cr , uid , picking . id , {
' name ' : ' / ' ,
' move_lines ' : [ ] ,
' pack_operation_ids ' : [ ] ,
' backorder_id ' : picking . id ,
} )
back_order_name = self . browse ( cr , uid , backorder_id , context = context ) . name
self . message_post ( cr , uid , picking . id , body = _ ( " Back order <em> %s </em> <b>created</b>. " ) % ( back_order_name ) , context = context )
2013-09-02 17:07:40 +00:00
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 )
2013-08-26 15:36:48 +00:00
return backorder_id
return False
2013-06-29 22:17:03 +00:00
2013-07-30 06:26:41 +00:00
def do_prepare_partial ( self , cr , uid , picking_ids , context = None ) :
context = context or { }
pack_operation_obj = self . pool . get ( ' stock.pack.operation ' )
for picking in self . browse ( cr , uid , picking_ids , context = context ) :
for move in picking . move_lines :
2013-08-03 23:28:48 +00:00
if move . state != ' assigned ' : continue
2013-07-30 06:26:41 +00:00
remaining_qty = move . product_qty
for quant in move . reserved_quant_ids :
qty = min ( quant . qty , move . product_qty )
remaining_qty - = qty
pack_operation_obj . create ( cr , uid , {
' picking_id ' : picking . id ,
' product_qty ' : qty ,
' quant_id ' : quant . id ,
' product_id ' : quant . product_id . id ,
2013-08-30 13:25:07 +00:00
' lot_id ' : quant . lot_id and quant . lot_id . id or False ,
2013-07-30 06:26:41 +00:00
' product_uom_id ' : quant . product_id . uom_id . id ,
2013-08-30 13:25:07 +00:00
' owner_id ' : quant . owner_id and quant . owner_id . id or False ,
2013-07-30 06:26:41 +00:00
' cost ' : quant . cost ,
2013-09-02 17:07:40 +00:00
' package_id ' : quant . package_id and quant . package_id . id or False ,
2013-07-30 06:26:41 +00:00
} , context = context )
if remaining_qty > 0 :
pack_operation_obj . create ( cr , uid , {
' picking_id ' : picking . id ,
' product_qty ' : remaining_qty ,
' product_id ' : move . product_id . id ,
' product_uom_id ' : move . product_id . uom_id . id ,
' cost ' : move . product_id . standard_price ,
} , context = context )
2013-07-30 06:24:10 +00:00
2013-08-22 09:48:13 +00:00
def _do_partial_product_move ( self , cr , uid , picking , product , qty , pack_id = False , lot_id = False , quant = False , context = None ) :
2013-07-30 07:46:15 +00:00
moves = [ ]
2013-07-23 15:34:24 +00:00
stock_move_obj = self . pool . get ( ' stock.move ' )
2013-07-30 07:46:15 +00:00
for move in picking . move_lines :
if move . state in ( ' cancel ' , ' done ' ) : continue
if move . product_id . id == product . id :
todo = min ( move . product_qty , qty )
2013-07-30 08:47:16 +00:00
newmove_id = stock_move_obj . split ( cr , uid , move , todo , context = context )
2013-08-03 23:28:48 +00:00
if not context . get ( ' do_only_split ' ) :
stock_move_obj . action_done ( cr , uid , [ newmove_id ] , context = context )
2013-07-30 07:46:15 +00:00
# TODO: To be removed after new API implementation
move . refresh ( )
moves . append ( move )
qty - = todo
2013-08-03 23:28:48 +00:00
if qty < = 0 : break
if qty > 0 :
2013-07-30 07:46:15 +00:00
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 ,
2013-08-03 23:28:48 +00:00
' reserved_quant_ids ' : quant and [ ( 4 , quant . id ) ] or [ ] ,
2013-07-30 07:46:15 +00:00
' picking_type_id ' : picking . picking_type_id . id
} , context = context )
2013-08-03 23:28:48 +00:00
if not context . get ( ' do_only_split ' ) :
stock_move_obj . action_done ( cr , uid , [ move_id ] , context = context )
2013-07-30 07:46:15 +00:00
move = stock_move_obj . browse ( cr , uid , move_id , context = context )
moves . append ( move )
return moves
2013-07-23 15:34:24 +00:00
2013-08-26 10:32:24 +00:00
def do_rereserve ( self , cr , uid , picking_ids , context = None ) :
'''
Needed for parameter create
'''
self . rereserve ( cr , uid , picking_ids , context = context )
2013-09-04 17:04:13 +00:00
#
# TODO:rereserve should be improved for giving negative quants when a certain lot is not there
# (Suppose you have a pack op for 20 lot B and lot B does not have any quants in the source location
# and could be used also instead of do_split
#
2013-08-22 09:48:13 +00:00
def rereserve ( self , cr , uid , picking_ids , create = False , context = None ) :
"""
This will unreserve all products and reserve the quants from the operations
2013-08-22 17:22:48 +00:00
: return : 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
2013-08-22 09:48:13 +00:00
"""
quant_obj = self . pool . get ( " stock.quant " )
move_obj = self . pool . get ( " stock.move " )
op_obj = self . pool . get ( " stock.pack.operation " )
2013-08-22 14:49:44 +00:00
res = { }
2013-08-22 17:22:48 +00:00
res2 = { } #what is left from moves
2013-08-22 09:48:13 +00:00
for picking in self . browse ( cr , uid , picking_ids , context = context ) :
2013-08-22 17:22:48 +00:00
# unreserve everything and initialize res2
for move in picking . move_lines :
2013-08-22 09:48:13 +00:00
quant_obj . quants_unreserve ( cr , uid , move , context = context )
2013-08-22 17:22:48 +00:00
res2 [ move . id ] = move . product_qty
2013-09-02 07:30:12 +00:00
# Resort pack_operation_ids
2013-09-02 17:07:40 +00:00
orderedpackops = picking . pack_operation_ids
#Sort packing operations such that packing operations with most specific information
orderedpackops . sort ( key = lambda x : ( x . package_id and - 1 or 0 ) + ( x . lot_id and - 1 or 0 ) )
for ops in orderedpackops :
2013-08-22 09:48:13 +00:00
#Find moves that correspond
2013-08-22 12:21:03 +00:00
if ops . product_id :
2013-08-26 10:32:24 +00:00
#TODO: Should have order such that things with lots and packings are searched first
2013-08-22 14:49:44 +00:00
move_ids = move_obj . search ( cr , uid , [ ( ' picking_id ' , ' = ' , picking . id ) , ( ' product_id ' , ' = ' , ops . product_id . id ) , ( ' remaining_qty ' , ' > ' , 0.0 ) ] , context = context )
qty_to_do = ops . product_qty
2013-08-22 12:21:03 +00:00
while qty_to_do > 0 and move_ids :
move = move_obj . browse ( cr , uid , move_ids . pop ( ) , context = context )
if move . remaining_qty > qty_to_do :
qty = qty_to_do
qty_to_do = 0
2013-08-22 09:48:13 +00:00
else :
qty = move . remaining_qty
2013-08-22 12:21:03 +00:00
qty_to_do - = move . remaining_qty
2013-08-22 09:48:13 +00:00
2013-09-03 09:17:26 +00:00
if create and move . location_id . usage != ' internal ' :
2013-08-22 09:48:13 +00:00
# Create quants
vals = {
' product_id ' : move . product_id . id ,
' location_id ' : move . location_id . id ,
' qty ' : qty ,
#'cost': price_unit,
' history_ids ' : [ ( 4 , move . id ) ] ,
' in_date ' : datetime . now ( ) . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' company_id ' : move . company_id . id ,
2013-08-30 13:25:07 +00:00
' lot_id ' : ops . lot_id and ops . lot_id . id or False ,
' owner_id ' : ops . owner_id and ops . owner_id . id or False ,
2013-08-22 09:48:13 +00:00
' reservation_id ' : move . id , #Reserve at once
2013-08-22 14:49:44 +00:00
' package_id ' : ops . result_package_id and ops . result_package_id . id or False ,
2013-08-22 09:48:13 +00:00
}
quant_id = quant_obj . create ( cr , uid , vals , context = context )
else :
#Quants get
2013-09-02 17:07:40 +00:00
prefered_order = " reservation_id IS NOT NULL "
domain = op_obj . _get_domain ( cr , uid , ops , context = context )
2013-08-22 09:48:13 +00:00
quants = quant_obj . quants_get ( cr , uid , move . location_id , move . product_id , qty , domain = domain , prefered_order = prefered_order , context = context )
quant_obj . quants_reserve ( cr , uid , quants , move , context = context )
2013-09-02 17:07:40 +00:00
#In the end, move quants in correct package
2013-09-03 12:46:49 +00:00
if create :
2013-09-04 10:29:29 +00:00
quant_obj . write ( cr , uid , [ x [ 0 ] . id for x in quants if x [ 0 ] != None ] , { ' package_id ' : ops . result_package_id and ops . result_package_id . id or False } , context = context )
2013-08-22 17:22:48 +00:00
res2 [ move . id ] - = qty
2013-08-22 14:49:44 +00:00
res [ ops . id ] = { }
res [ ops . id ] [ ops . product_id . id ] = qty_to_do
elif ops . package_id :
quants = ops . package_id . quant_ids #_get_package_lines
for quant in quants :
move_ids = move_obj . search ( cr , uid , [ ( ' picking_id ' , ' = ' , picking . id ) , ( ' product_id ' , ' = ' , quant . product_id . id ) , ( ' remaining_qty ' , ' > ' , 0.0 ) ] )
qty_to_do = quant . qty
while qty_to_do > 0 and move_ids :
move = move_obj . browse ( cr , uid , move_ids . pop ( ) , context = context )
if move . remaining_qty > qty_to_do :
qty = qty_to_do
2013-08-22 17:22:48 +00:00
qty_to_do = 0.0
2013-08-22 14:49:44 +00:00
else :
qty = move . remaining_qty
qty_to_do - = move . remaining_qty
2013-09-03 14:28:47 +00:00
quant_obj . quants_reserve ( cr , uid , [ ( quant , qty ) ] , move , context = context )
2013-08-22 17:22:48 +00:00
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
2013-09-02 17:07:40 +00:00
#Add parent package
2013-09-03 12:46:49 +00:00
if create :
2013-09-03 14:28:47 +00:00
self . pool . get ( " stock.quant.package " ) . write ( cr , uid , [ ops . package_id . id ] , { ' parent_id ' : ops . result_package_id and ops . result_package_id . id or False } , context = context )
2013-08-22 17:22:48 +00:00
return ( res , res2 )
2013-08-22 09:48:13 +00:00
2013-07-23 15:34:24 +00:00
def do_partial ( self , cr , uid , picking_ids , context = None ) :
2013-07-30 08:47:16 +00:00
"""
If no pack operation , we close the whole move
Otherwise , do the pack operations
2013-07-23 15:34:24 +00:00
"""
2013-08-22 17:22:48 +00:00
stock_move_obj = self . pool . get ( ' stock.move ' )
2013-07-30 07:46:15 +00:00
for picking in self . browse ( cr , uid , picking_ids , context = context ) :
2013-07-30 08:47:16 +00:00
if not picking . pack_operation_ids :
self . action_done ( cr , uid , [ picking . id ] , context = context )
continue
2013-08-22 17:22:48 +00:00
else :
#First thing that needs to happen is rereserving the quants
res = self . rereserve ( cr , uid , [ picking . id ] , create = True , context = context ) #This time, quants need to be created
orig_moves = picking . move_lines
#Add moves that operations need extra
2013-08-26 13:03:22 +00:00
extra_moves = [ ]
2013-08-22 17:22:48 +00:00
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 :
#TODO: Maybe should try to reserve quants?
quant = False
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 ,
' reserved_quant_ids ' : quant and [ ( 4 , quant . id ) ] or [ ] ,
' picking_type_id ' : picking . picking_type_id . id
} , context = context )
2013-08-26 13:03:22 +00:00
extra_moves . append ( move_id )
2013-08-22 17:22:48 +00:00
res2 = res [ 1 ]
for move in res2 . keys ( ) :
if res2 [ move ] > 0 :
mov = stock_move_obj . browse ( cr , uid , move , context = context )
2013-09-03 12:18:30 +00:00
stock_move_obj . split ( cr , uid , mov , res2 [ move ] , context = context )
2013-08-26 13:03:22 +00:00
stock_move_obj . action_done ( cr , uid , extra_moves + [ x . id for x in orig_moves ] , context = context )
picking . refresh ( )
2013-07-30 07:46:15 +00:00
self . _create_backorder ( cr , uid , picking , context = context )
return True
2013-07-23 15:34:24 +00:00
2013-08-03 23:28:48 +00:00
def do_split ( self , cr , uid , picking_ids , context = None ) :
"""
2013-09-03 12:18:30 +00:00
just split the picking without making it ' done '
2013-08-03 23:28:48 +00:00
"""
if context is None :
context = { }
ctx = context . copy ( )
ctx [ ' do_only_split ' ] = True
quant_package_obj = self . pool . get ( ' stock.quant.package ' )
backorder_moves = [ ]
for picking in self . browse ( cr , uid , picking_ids , context = context ) :
if not picking . pack_operation_ids :
continue
for op in picking . pack_operation_ids :
if op . package_id :
for quant in quant_package_obj . quants_get ( cr , uid , op . package_id , context = context ) :
backorder_moves + = self . _do_partial_product_move ( cr , uid , picking , quant . product_id , quant . qty , quant , context = ctx )
elif op . product_id :
backorder_moves + = self . _do_partial_product_move ( cr , uid , picking , op . product_id , op . product_qty , op . quant_id , context = ctx )
self . _create_backorder ( cr , uid , picking , backorder_moves , context = context )
return True
2013-07-30 07:46:15 +00:00
# Methods for the barcode UI
2010-03-25 12:08:31 +00:00
2013-06-27 10:42:55 +00:00
def _get_picking_for_packing_ui ( self , cr , uid , context = None ) :
2013-06-28 15:58:56 +00:00
res = self . search ( cr , uid , [ ( ' state ' , ' = ' , ' assigned ' ) ] , limit = 1 , context = context )
return res and res [ 0 ] or False # TODO: what to do if nothing is left to do?
2013-06-27 10:42:55 +00:00
2013-07-23 15:34:24 +00:00
def action_done_from_packing_ui ( self , cr , uid , picking_id , only_split_lines = False , context = None ) :
self . do_partial ( cr , uid , picking_id , only_split_lines , context = context )
2013-06-26 13:10:04 +00:00
#return id of next picking to work on
2013-06-27 10:42:55 +00:00
return self . _get_picking_for_packing_ui ( cr , uid , context = context )
2013-06-26 13:10:04 +00:00
2013-07-24 17:30:39 +00:00
def action_pack ( self , cr , uid , picking_ids , context = None ) :
2013-06-26 13:10:04 +00:00
stock_operation_obj = self . pool . get ( ' stock.pack.operation ' )
package_obj = self . pool . get ( ' stock.quant.package ' )
2013-07-30 06:26:41 +00:00
for picking_id in picking_ids :
operation_ids = stock_operation_obj . search ( cr , uid , [ ( ' picking_id ' , ' = ' , picking_id ) , ( ' result_package_id ' , ' = ' , False ) ] , context = context )
if operation_ids :
package_id = package_obj . create ( cr , uid , { } , context = context )
stock_operation_obj . write ( cr , uid , operation_ids , { ' result_package_id ' : package_id } , context = context )
return True
2013-06-26 13:10:04 +00:00
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 ) :
''' 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 ' )
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 = ' '
2013-06-27 12:31:08 +00:00
todo_on_moves = [ ]
todo_on_operations = [ ]
2013-06-26 13:10:04 +00:00
#check if the barcode correspond to a product
2013-07-23 15:34:24 +00:00
matching_product_ids = product_obj . search ( cr , uid , [ ( ' ean13 ' , ' = ' , barcode_str ) ] , context = context )
2013-06-26 13:10:04 +00:00
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 )
#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 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 )
2013-06-27 14:42:12 +00:00
#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 )
2013-06-26 14:43:27 +00:00
return { ' warnings ' : error_msg , ' moves_to_update ' : todo_on_moves , ' operations_to_update ' : todo_on_operations }
2013-06-26 13:10:04 +00:00
2006-12-07 13:41:40 +00:00
class stock_production_lot ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = ' stock.production.lot '
2013-06-29 22:17:03 +00:00
_inherit = [ ' mail.thread ' ]
_description = ' Lot/Serial '
2008-07-22 15:11:28 +00:00
_columns = {
2013-06-29 22:17:03 +00:00
' name ' : fields . char ( ' Serial Number ' , size = 64 , required = True , help = " Unique Serial Number " ) ,
2010-09-06 13:51:23 +00:00
' ref ' : fields . char ( ' Internal Reference ' , size = 256 , help = " Internal reference number in case it differs from the manufacturer ' s serial number " ) ,
2010-11-10 12:13:19 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , domain = [ ( ' type ' , ' <> ' , ' service ' ) ] ) ,
2013-06-29 22:17:03 +00:00
' quant_ids ' : fields . one2many ( ' stock.quant ' , ' lot_id ' , ' Quants ' ) ,
2013-06-30 08:55:03 +00:00
' create_date ' : fields . datetime ( ' Creation Date ' ) ,
2013-08-30 13:25:07 +00:00
# 'partner_id': fields.many2one('res.partner', 'Owner'),
2008-07-22 15:11:28 +00:00
}
_defaults = {
2009-08-28 09:40:49 +00:00
' name ' : lambda x , y , z , c : x . pool . get ( ' ir.sequence ' ) . get ( y , z , ' stock.lot.serial ' ) ,
' product_id ' : lambda x , y , z , c : c . get ( ' product_id ' , False ) ,
2008-07-22 15:11:28 +00:00
}
_sql_constraints = [
2012-10-23 14:46:25 +00:00
( ' name_ref_uniq ' , ' unique (name, ref) ' , ' The combination of Serial Number and internal reference must be unique ! ' ) ,
2008-07-22 15:11:28 +00:00
]
2013-08-04 00:43:03 +00:00
def name_get ( self , cr , uid , ids , context = None ) :
res = [ ]
for lot in self . browse ( cr , uid , ids , context = context ) :
name = lot . name
2013-08-30 13:25:07 +00:00
# if lot.partner_id:
# name += ' (' + lot.partner_id.name + ')'
2013-08-04 00:43:03 +00:00
res . append ( ( lot . id , name ) )
return res
2012-11-27 10:20:02 +00:00
2006-12-07 13:41:40 +00:00
2010-04-09 08:22:51 +00:00
# ----------------------------------------------------
# Move
# ----------------------------------------------------
2006-12-07 13:41:40 +00:00
class stock_move ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " stock.move "
_description = " Stock Move "
2012-12-06 10:14:45 +00:00
_order = ' date_expected desc, id '
2010-05-19 20:02:36 +00:00
_log_create = False
2011-01-20 10:04:53 +00:00
2013-09-03 16:27:57 +00:00
def get_price_unit ( self , cr , uid , move , context = None ) :
""" Returns the unit price to store on the move """
return move . price_unit or move . product_id . standard_price
2010-07-06 11:21:27 +00:00
def name_get ( self , cr , uid , ids , context = None ) :
2008-10-10 08:59:17 +00:00
res = [ ]
2010-11-22 10:37:53 +00:00
for line in self . browse ( cr , uid , ids , context = context ) :
2012-09-11 16:14:35 +00:00
name = line . location_id . name + ' > ' + line . location_dest_id . name
if line . product_id . code :
name = line . product_id . code + ' : ' + name
if line . picking_id . origin :
name = line . picking_id . origin + ' / ' + name
res . append ( ( line . id , name ) )
2008-10-10 08:59:17 +00:00
return res
2013-06-29 22:17:03 +00:00
# FP Note: put this on quants, 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):
2013-06-30 08:55:03 +00:00
# if not move.lot_id and \
2013-06-29 22:17:03 +00:00
# (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
2008-11-03 14:40:49 +00:00
2013-06-30 14:10:14 +00:00
def _quantity_normalize ( self , cr , uid , ids , name , args , context = None ) :
uom_obj = self . pool . get ( ' product.uom ' )
res = { }
for m in self . browse ( cr , uid , ids , context = context ) :
2013-09-04 10:29:29 +00:00
res [ m . id ] = uom_obj . _compute_qty_obj ( cr , uid , m . product_uom , m . product_uom_qty , m . product_id . uom_id , round = False )
2013-06-30 14:10:14 +00:00
return res
2013-08-22 14:49:44 +00:00
# def _get_remaining_qty(self, cr, uid, ids, field_name, args, context=None):
# #TODO: this function assumes that there aren't several stock move in the same picking with the same product. what should we do in that case?
# #TODO take care of the quant on stock moves too
# res = dict.fromkeys(ids, False)
# for move in self.browse(cr, uid, ids, context=context):
# res[move.id] = move.product_qty
# if move.picking_id:
# for op in move.picking_id.pack_operation_ids:
# if op.product_id == move.product_id or (op.quant_id and op.quant_id.product_id == move.product_id):
# res[move.id] -= op.product_qty
# if op.package_id:
# #find the product qty recursively
# res[move.id] -= self.pool.get('stock.quant.package')._get_product_total_qty(cr, uid, op.package_id, move.product_id.id, context=context)
# return res
2013-06-30 13:41:05 +00:00
def _get_remaining_qty ( self , cr , uid , ids , field_name , args , context = None ) :
2013-08-22 14:49:44 +00:00
res = { }
2013-06-30 13:41:05 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2013-09-03 16:31:04 +00:00
res [ move . id ] = move . product_qty
2013-08-22 14:49:44 +00:00
for quant in move . reserved_quant_ids :
res [ move . id ] - = quant . qty
2013-06-30 13:41:05 +00:00
return res
2013-08-22 09:48:13 +00:00
2013-08-06 15:36:01 +00:00
def _get_lot_ids ( self , cr , uid , ids , field_name , args , context = None ) :
res = dict . fromkeys ( ids , False )
for move in self . browse ( cr , uid , ids , context = context ) :
if move . state == ' done ' :
res [ move . id ] = [ q . id for q in move . quant_ids ]
else :
res [ move . id ] = [ q . id for q in move . reserved_quant_ids ]
return res
2013-08-07 10:04:28 +00:00
def _get_product_availability ( self , cr , uid , ids , field_name , args , context = None ) :
quant_obj = self . pool . get ( ' stock.quant ' )
res = dict . fromkeys ( ids , False )
for move in self . browse ( cr , uid , ids , context = context ) :
if move . state == ' done ' :
res [ move . id ] = move . product_qty
else :
sublocation_ids = self . pool . get ( ' stock.location ' ) . search ( cr , uid , [ ( ' id ' , ' child_of ' , [ move . location_id . id ] ) ] , context = context )
quant_ids = quant_obj . search ( cr , uid , [ ( ' location_id ' , ' in ' , sublocation_ids ) , ( ' product_id ' , ' = ' , move . product_id . id ) , ( ' reservation_id ' , ' = ' , False ) ] , context = context )
availability = 0
for quant in quant_obj . browse ( cr , uid , quant_ids , context = context ) :
availability + = quant . qty
res [ move . id ] = min ( move . product_qty , availability )
return res
2013-08-22 12:21:03 +00:00
def _get_move ( self , cr , uid , ids , context = None ) :
res = set ( )
for quant in self . browse ( cr , uid , ids , context = context ) :
if quant . reservation_id :
res . add ( quant . reservation_id . id )
return list ( res )
2008-07-22 15:11:28 +00:00
_columns = {
2012-12-17 22:28:27 +00:00
' name ' : fields . char ( ' Description ' , required = True , select = True ) ,
2009-08-28 09:40:49 +00:00
' priority ' : fields . selection ( [ ( ' 0 ' , ' Not urgent ' ) , ( ' 1 ' , ' Urgent ' ) ] , ' Priority ' ) ,
2011-01-17 06:22:31 +00:00
' create_date ' : fields . datetime ( ' Creation Date ' , readonly = True , select = True ) ,
2011-05-03 07:25:13 +00:00
' 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 ) ] } ) ,
2011-01-17 06:22:31 +00:00
' date_expected ' : fields . datetime ( ' Scheduled Date ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } , required = True , select = True , help = " Scheduled date for the processing of this move " ) ,
2010-10-11 09:52:55 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , select = True , domain = [ ( ' type ' , ' <> ' , ' service ' ) ] , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2013-07-01 09:09:23 +00:00
# TODO: improve store to add dependency on product UoM
2013-06-30 14:10:14 +00:00
' product_qty ' : fields . function ( _quantity_normalize , type = ' float ' , store = True , string = ' Quantity ' ,
2013-06-30 14:29:27 +00:00
digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ,
help = ' Quantity in the default UoM of the product ' ) ,
2013-06-30 14:10:14 +00:00
' product_uom_qty ' : fields . float ( ' Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ,
2012-09-29 10:30:13 +00:00
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 "
" moves, this is the quantity of product that is planned to "
" be moved. Lowering this quantity does not generate a "
" backorder. Changing this quantity on assigned moves affects "
" the product reservation, and should be done with care. "
) ,
2010-10-11 09:52:55 +00:00
' product_uom ' : fields . many2one ( ' product.uom ' , ' Unit of Measure ' , required = True , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2012-04-25 12:09:08 +00:00
' product_uos_qty ' : fields . float ( ' Quantity (UOS) ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2011-07-15 12:00:59 +00:00
' product_uos ' : fields . many2one ( ' product.uom ' , ' Product UOS ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2013-06-29 22:17:03 +00:00
' product_packaging ' : fields . many2one ( ' product.packaging ' , ' Prefered Packaging ' , help = " It specifies attributes of packaging like type, quantity of packaging,etc. " ) ,
2008-07-22 15:11:28 +00:00
2010-10-11 09:52:55 +00:00
' 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. " ) ,
2008-07-22 15:11:28 +00:00
2013-06-29 22:17:03 +00:00
# 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 " ) ,
2013-07-29 21:44:43 +00:00
2008-07-22 15:11:28 +00:00
2010-09-06 13:51:23 +00:00
' move_dest_id ' : fields . many2one ( ' stock.move ' , ' Destination Move ' , help = " Optional: next stock move when chaining them " , select = True ) ,
2013-07-26 18:09:42 +00:00
' move_orig_ids ' : fields . one2many ( ' stock.move ' , ' move_dest_id ' , ' Original Move ' , help = " Optional: previous stock move when chaining them " , select = True ) ,
2013-06-29 22:17:03 +00:00
2010-11-10 05:37:49 +00:00
' picking_id ' : fields . many2one ( ' stock.picking ' , ' Reference ' , select = True , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2008-07-22 15:11:28 +00:00
' note ' : fields . text ( ' Notes ' ) ,
2012-05-07 15:43:56 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' New ' ) ,
2012-05-17 10:15:17 +00:00
( ' cancel ' , ' Cancelled ' ) ,
2012-05-07 15:43:56 +00:00
( ' waiting ' , ' Waiting Another Move ' ) ,
( ' confirmed ' , ' Waiting Availability ' ) ,
( ' assigned ' , ' Available ' ) ,
( ' done ' , ' Done ' ) ,
2012-05-22 16:10:55 +00:00
] , ' Status ' , readonly = True , select = True ,
2012-05-07 15:43:56 +00:00
help = " * New: When the stock move is created and not yet confirmed. \n " \
" * Waiting Another Move: This state can be seen when a move is waiting for another one, for example in a chained flow. \n " \
" * Waiting Availability: This state is reached when the procurement resolution is not straight forward. It may need the scheduler to run, a component to me manufactured... \n " \
" * Available: When products are reserved, it is set to \' Available \' . \n " \
" * Done: When the shipment is processed, the state is \' Done \' . " ) ,
2013-06-29 22:17:03 +00:00
2013-06-24 09:53:19 +00:00
' 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) " ) , # as it's a technical field, we intentionally don't provide the digits attribute
2010-09-15 18:01:00 +00:00
' price_currency_id ' : fields . many2one ( ' res.currency ' , ' Currency for average price ' , help = " Technical field used to record the currency chosen by the user during a picking confirmation (when average price costing method is used) " ) ,
2013-06-29 22:17:03 +00:00
2010-09-06 13:51:23 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True , select = True ) ,
2012-02-24 10:58:49 +00:00
' backorder_id ' : fields . related ( ' picking_id ' , ' backorder_id ' , type = ' many2one ' , relation = " stock.picking " , string = " Back Order of " , select = True ) ,
2013-08-04 14:17:50 +00:00
' origin ' : fields . char ( " Source " ) ,
2013-07-09 13:22:39 +00:00
' 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. \n Make to Order: When needed, the product is purchased or produced. " ) ,
2010-09-06 13:51:23 +00:00
# used for colors in tree views:
2010-12-17 10:24:47 +00:00
' scrapped ' : fields . related ( ' location_dest_id ' , ' scrap_location ' , type = ' boolean ' , relation = ' stock.location ' , string = ' Scrapped ' , readonly = True ) ,
2013-07-29 21:44:43 +00:00
2013-07-23 16:47:55 +00:00
' quant_ids ' : fields . many2many ( ' stock.quant ' , ' stock_quant_move_rel ' , ' move_id ' , ' quant_id ' , ' Quants ' ) ,
2013-06-29 22:17:03 +00:00
' reserved_quant_ids ' : fields . one2many ( ' stock.quant ' , ' reservation_id ' , ' Reserved quants ' ) ,
2013-08-22 12:21:03 +00:00
' 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 ) ] } ,
2013-09-04 10:29:29 +00:00
store = { ' stock.move ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' product_uom_qty ' , ' product_uom ' , ' reserved_quant_ids ' ] , 20 ) ,
2013-08-26 10:32:24 +00:00
' stock.quant ' : ( _get_move , [ ' reservation_id ' ] , 10 ) } ) ,
2013-08-04 13:40:14 +00:00
' procurement_id ' : fields . many2one ( ' procurement.order ' , ' Procurement ' ) ,
' group_id ' : fields . related ( ' procurement_id ' , ' group_id ' , type = ' many2one ' , relation = " procurement.group " , string = ' Procurement Group ' ) ,
2013-07-24 17:00:17 +00:00
' rule_id ' : fields . many2one ( ' procurement.rule ' , ' Procurement Rule ' ) ,
2013-07-25 15:38:23 +00:00
' propagate ' : fields . boolean ( ' Propagate cancel and split ' , help = ' If checked, when this move is cancelled, cancel the linked move too ' ) ,
2013-07-30 07:46:15 +00:00
' picking_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Picking Type ' ) ,
2013-08-05 23:23:48 +00:00
' inventory_id ' : fields . many2one ( ' stock.inventory ' , ' Inventory ' ) ,
2013-08-06 15:36:01 +00:00
' lot_ids ' : fields . function ( _get_lot_ids , type = ' many2many ' , relation = ' stock.quant ' , string = ' Lots ' ) ,
2013-08-06 15:37:20 +00:00
' origin_returned_move_id ' : fields . many2one ( ' stock.move ' , ' Origin return move ' , help = ' move that created the return move ' ) ,
' returned_move_ids ' : fields . one2many ( ' stock.move ' , ' origin_returned_move_id ' , ' All returned moves ' , help = ' Optional: all returned moves created from this move ' ) ,
2013-08-07 10:04:28 +00:00
' availability ' : fields . function ( _get_product_availability , type = ' float ' , string = ' Availability ' ) ,
2013-08-30 08:16:48 +00:00
}
2012-09-21 15:02:06 +00:00
2013-07-16 10:15:40 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
if default is None :
default = { }
default = default . copy ( )
2013-07-29 21:44:43 +00:00
default [ ' move_orig_ids ' ] = [ ]
default [ ' quant_ids ' ] = [ ]
default [ ' reserved_quant_ids ' ] = [ ]
2013-08-06 15:37:20 +00:00
default [ ' returned_move_ids ' ] = [ ]
default [ ' origin_returned_move_id ' ] = False
2013-07-29 21:44:43 +00:00
default [ ' state ' ] = ' draft '
2013-07-16 10:15:40 +00:00
return super ( stock_move , self ) . copy ( cr , uid , id , default , context )
2013-07-29 21:44:43 +00:00
2010-07-06 11:21:27 +00:00
def _default_location_destination ( self , cr , uid , context = None ) :
2013-07-26 11:53:32 +00:00
context = context or { }
2013-08-02 14:52:39 +00:00
if context . get ( ' default_picking_type_id ' , False ) :
pick_type = self . pool . get ( ' stock.picking.type ' ) . browse ( cr , uid , context [ ' default_picking_type_id ' ] , context = context )
return pick_type . default_location_dest_id and pick_type . default_location_dest_id . id or False
2013-07-29 12:57:57 +00:00
return False
2008-09-07 23:24:39 +00:00
2010-07-06 11:21:27 +00:00
def _default_location_source ( self , cr , uid , context = None ) :
2013-07-26 11:53:32 +00:00
context = context or { }
2013-08-02 14:52:39 +00:00
if context . get ( ' default_picking_type_id ' , False ) :
pick_type = self . pool . get ( ' stock.picking.type ' ) . browse ( cr , uid , context [ ' default_picking_type_id ' ] , context = context )
return pick_type . default_location_src_id and pick_type . default_location_src_id . id or False
2013-07-29 12:57:57 +00:00
return False
2008-09-07 23:24:39 +00:00
2012-06-29 12:23:47 +00:00
def _default_destination_address ( self , cr , uid , context = None ) :
2012-07-25 13:51:27 +00:00
user = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context )
return user . company_id . partner_id . id
2008-09-07 23:24:39 +00:00
2008-07-22 15:11:28 +00:00
_defaults = {
2008-09-07 23:24:39 +00:00
' location_id ' : _default_location_source ,
' location_dest_id ' : _default_location_destination ,
2012-06-29 12:23:47 +00:00
' partner_id ' : _default_destination_address ,
2010-07-06 11:21:27 +00:00
' state ' : ' draft ' ,
' priority ' : ' 1 ' ,
' product_qty ' : 1.0 ,
2013-08-06 13:09:39 +00:00
' product_uom_qty ' : 1.0 ,
2013-07-10 10:16:16 +00:00
' scrapped ' : False ,
2011-02-23 06:15:04 +00:00
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2013-07-10 10:16:16 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.move ' , context = c ) ,
2011-02-23 06:15:04 +00:00
' date_expected ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2013-07-10 10:16:16 +00:00
' procure_method ' : ' make_to_stock ' ,
2013-07-25 15:38:23 +00:00
' propagate ' : True ,
2008-07-22 15:11:28 +00:00
}
2010-02-01 12:50:00 +00:00
2013-07-09 13:41:21 +00:00
def _create_procurement ( self , cr , uid , move , context = None ) :
"""
This will create a procurement order
"""
proc_obj = self . pool . get ( " procurement.order " )
2013-08-02 21:11:07 +00:00
origin = ( move . group_id and ( move . group_id . name + " : " ) or " " ) + ( move . rule_id and move . rule_id . name or " / " )
2013-07-09 13:41:21 +00:00
return proc_obj . create ( cr , uid , {
2013-08-02 21:11:07 +00:00
' name ' : move . rule_id and move . rule_id . name or " / " ,
' origin ' : origin ,
' company_id ' : move . company_id and move . company_id . id or False ,
' date_planned ' : move . date ,
' product_id ' : move . product_id . id ,
' product_qty ' : move . product_qty ,
' product_uom ' : move . product_uom . id ,
' product_uos_qty ' : ( move . product_uos and move . product_uos_qty ) or move . product_qty ,
' product_uos ' : ( move . product_uos and move . product_uos . id ) or move . product_uom . id ,
' location_id ' : move . location_id . id ,
' move_dest_id ' : move . id ,
' group_id ' : move . group_id and move . group_id . id or False ,
} )
2013-07-09 13:41:21 +00:00
2013-06-29 22:17:03 +00:00
# Check that we do not modify a stock.move which is done
2010-08-31 06:39:28 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2011-03-29 12:27:12 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2013-06-29 22:17:03 +00:00
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
2010-09-01 08:21:28 +00:00
2010-06-16 08:40:57 +00:00
def onchange_quantity ( self , cr , uid , ids , product_id , product_qty ,
2010-05-26 12:59:30 +00:00
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
@param product_uom : Unit of measure of product
2010-06-16 08:40:57 +00:00
@param product_uos : Unit of sale of product
2010-05-26 12:59:30 +00:00
@return : Dictionary of values
"""
2009-11-20 11:20:04 +00:00
result = {
2013-07-29 21:44:43 +00:00
' product_uos_qty ' : 0.00
}
2012-02-29 11:08:20 +00:00
warning = { }
2009-12-08 12:58:25 +00:00
2009-11-20 11:20:04 +00:00
if ( not product_id ) or ( product_qty < = 0.0 ) :
2012-02-28 15:05:27 +00:00
result [ ' product_qty ' ] = 0.0
2009-11-20 11:20:04 +00:00
return { ' value ' : result }
2009-12-08 12:58:25 +00:00
2009-11-20 11:20:04 +00:00
product_obj = self . pool . get ( ' product.product ' )
uos_coeff = product_obj . read ( cr , uid , product_id , [ ' uos_coeff ' ] )
2013-07-29 21:44:43 +00:00
# Warn if the quantity was decreased
2012-09-29 10:30:13 +00:00
if ids :
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 "
2012-02-29 11:08:20 +00:00
" new quantity as complete: OpenERP will not "
2012-09-29 10:30:13 +00:00
" automatically generate a back order. " ) } )
2012-02-29 11:08:20 +00:00
break
2009-12-08 12:58:25 +00:00
2009-11-20 11:20:04 +00:00
if product_uos and product_uom and ( product_uom != product_uos ) :
result [ ' product_uos_qty ' ] = product_qty * uos_coeff [ ' uos_coeff ' ]
else :
result [ ' product_uos_qty ' ] = product_qty
2009-12-08 12:58:25 +00:00
2012-02-29 11:08:20 +00:00
return { ' value ' : result , ' warning ' : warning }
2009-12-08 12:58:25 +00:00
2010-11-25 07:00:10 +00:00
def onchange_uos_quantity ( self , cr , uid , ids , product_id , product_uos_qty ,
product_uos , product_uom ) :
""" On change of product quantity finds UoM and UoS quantities
@param product_id : Product id
@param product_uos_qty : Changed UoS Quantity of product
@param product_uom : Unit of measure of product
@param product_uos : Unit of sale of product
@return : Dictionary of values
"""
result = {
2013-06-30 20:13:23 +00:00
' product_uom_qty ' : 0.00
}
2012-02-29 11:08:20 +00:00
warning = { }
2010-11-25 07:00:10 +00:00
if ( not product_id ) or ( product_uos_qty < = 0.0 ) :
2012-02-28 15:05:27 +00:00
result [ ' product_uos_qty ' ] = 0.0
2010-11-25 07:00:10 +00:00
return { ' value ' : result }
product_obj = self . pool . get ( ' product.product ' )
uos_coeff = product_obj . read ( cr , uid , product_id , [ ' uos_coeff ' ] )
2013-07-29 21:44:43 +00:00
# Warn if the quantity was decreased
2012-02-29 11:08:20 +00:00
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 "
" new quantity as complete: OpenERP will not "
" automatically generate a Back Order. " ) } )
break
2010-11-25 07:00:10 +00:00
if product_uos and product_uom and ( product_uom != product_uos ) :
2013-06-30 20:13:23 +00:00
result [ ' product_uom_qty ' ] = product_uos_qty / uos_coeff [ ' uos_coeff ' ]
2010-11-25 07:00:10 +00:00
else :
2013-06-30 20:13:23 +00:00
result [ ' product_uom_qty ' ] = product_uos_qty
2012-02-29 11:08:20 +00:00
return { ' value ' : result , ' warning ' : warning }
2010-12-15 10:29:07 +00:00
2010-06-16 08:40:57 +00:00
def onchange_product_id ( self , cr , uid , ids , prod_id = False , loc_id = False ,
2012-03-30 07:49:01 +00:00
loc_dest_id = False , partner_id = False ) :
2010-05-26 12:59:30 +00:00
""" 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
2011-11-11 06:16:52 +00:00
@param loc_dest_id : Destination location id
2012-03-30 07:49:01 +00:00
@param partner_id : Address id of partner
2010-05-26 12:59:30 +00:00
@return : Dictionary of values
"""
2008-07-22 15:11:28 +00:00
if not prod_id :
return { }
2010-02-10 11:48:08 +00:00
lang = False
2012-03-30 07:49:01 +00:00
if partner_id :
addr_rec = self . pool . get ( ' res.partner ' ) . browse ( cr , uid , partner_id )
2010-02-10 11:48:08 +00:00
if addr_rec :
2012-03-06 09:06:45 +00:00
lang = addr_rec and addr_rec . lang or False
2010-02-10 11:48:08 +00:00
ctx = { ' lang ' : lang }
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , [ prod_id ] , context = ctx ) [ 0 ]
2009-11-20 11:20:04 +00:00
uos_id = product . uos_id and product . uos_id . id or False
2008-07-22 15:11:28 +00:00
result = {
' product_uom ' : product . uom_id . id ,
2009-11-20 11:20:04 +00:00
' product_uos ' : uos_id ,
2013-06-30 20:13:23 +00:00
' product_uom_qty ' : 1.00 ,
2012-04-07 09:03:58 +00: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 ' ] ,
2008-07-22 15:11:28 +00:00
}
2010-03-18 12:17:30 +00:00
if not ids :
result [ ' name ' ] = product . partner_ref
2008-07-22 15:11:28 +00:00
if loc_id :
result [ ' location_id ' ] = loc_id
if loc_dest_id :
result [ ' location_dest_id ' ] = loc_dest_id
2009-08-28 09:40:49 +00:00
return { ' value ' : result }
2008-07-22 15:11:28 +00:00
2013-07-29 21:44:43 +00:00
def _picking_assign ( self , cr , uid , move , context = None ) :
2013-07-30 07:54:38 +00:00
if move . picking_id or not move . picking_type_id :
2013-07-29 21:44:43 +00:00
return False
context = context or { }
2013-07-17 13:51:42 +00:00
pick_obj = self . pool . get ( " stock.picking " )
2013-07-30 14:05:05 +00:00
picks = [ ]
if move . group_id :
picks = pick_obj . search ( cr , uid , [
( ' group_id ' , ' = ' , move . group_id . id ) ,
( ' location_id ' , ' = ' , move . location_id . id ) ,
( ' location_dest_id ' , ' = ' , move . location_dest_id . id ) ,
2013-08-16 15:02:26 +00:00
( ' state ' , ' in ' , [ ' confirmed ' , ' waiting ' ] ) ] , context = context )
2013-07-17 13:51:42 +00:00
if picks :
pick = picks [ 0 ]
else :
2013-07-29 21:44:43 +00:00
values = {
' origin ' : move . origin ,
' 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 ' ,
2013-08-04 14:17:50 +00:00
' partner_id ' : move . group_id and move . group_id . partner_id and move . group_id . partner_id . id or False ,
2013-07-29 21:44:43 +00:00
' date_done ' : move . date_expected ,
' state ' : ' confirmed ' ,
' group_id ' : move . group_id and move . group_id . id or False ,
' picking_type_id ' : move . picking_type_id and move . picking_type_id . id or False ,
2013-07-26 12:55:32 +00:00
}
pick = pick_obj . create ( cr , uid , values , context = context )
2013-07-29 21:44:43 +00:00
move . write ( { ' picking_id ' : pick } )
return True
2012-08-30 06:52:55 +00:00
2011-05-03 07:25:13 +00:00
def onchange_date ( self , cr , uid , ids , date , date_expected , context = None ) :
""" On change of Scheduled Date gives a Move date.
2011-12-01 21:32:12 +00:00
@param date_expected : Scheduled Date
2011-05-03 07:25:13 +00:00
@param date : Move Date
@return : Move Date
"""
if not date_expected :
2011-05-04 05:11:34 +00:00
date_expected = time . strftime ( ' % Y- % m- %d % H: % M: % S ' )
2011-05-03 07:25:13 +00:00
return { ' value ' : { ' date ' : date_expected } }
2010-07-06 11:21:27 +00:00
def action_confirm ( self , cr , uid , ids , context = None ) :
2013-06-29 22:17:03 +00:00
""" Confirms stock move or put it in waiting if it ' s linked to another move.
2010-05-26 12:59:30 +00:00
@return : List of ids .
"""
2013-06-29 22:17:03 +00:00
states = {
' confirmed ' : [ ] ,
' waiting ' : [ ]
}
for move in self . browse ( cr , uid , ids , context = context ) :
state = ' confirmed '
for m in move . move_orig_ids :
2013-07-17 13:51:42 +00:00
if m . state not in ( ' done ' , ' cancel ' ) :
2013-06-29 22:17:03 +00:00
state = ' waiting '
states [ state ] . append ( move . id )
2013-07-29 21:44:43 +00:00
self . _picking_assign ( cr , uid , move , context = context )
2013-06-29 22:17:03 +00:00
for state , write_ids in states . items ( ) :
if len ( write_ids ) :
self . write ( cr , uid , write_ids , { ' state ' : state } )
2013-07-09 13:41:21 +00:00
if state == ' confirmed ' :
for move in self . browse ( cr , uid , write_ids , context = context ) :
if move . procure_method == ' make_to_order ' :
self . _create_procurement ( cr , uid , move , context = context )
2013-06-29 22:17:03 +00:00
return True
2008-07-22 15:11:28 +00:00
2010-11-22 10:37:53 +00:00
def force_assign ( self , cr , uid , ids , context = None ) :
2010-05-26 12:59:30 +00:00
""" Changes the state to assigned.
@return : True
"""
2013-07-16 10:15:40 +00:00
done = self . action_assign ( cr , uid , ids , context = context )
2013-07-29 21:44:43 +00:00
self . write ( cr , uid , list ( set ( ids ) - set ( done ) ) , { ' state ' : ' assigned ' } )
2013-07-16 10:15:40 +00:00
return True
2013-07-29 21:44:43 +00:00
2008-07-22 15:11:28 +00:00
2010-11-22 10:37:53 +00:00
def cancel_assign ( self , cr , uid , ids , context = None ) :
2010-05-26 12:59:30 +00:00
""" Changes the state to confirmed.
@return : True
"""
2013-06-29 22:17:03 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' confirmed ' } )
2008-07-22 15:11:28 +00:00
2013-06-29 22:17:03 +00:00
def action_assign ( self , cr , uid , ids , context = None ) :
2010-05-26 12:59:30 +00:00
""" Checks the product type and accordingly writes the state.
@return : No . of moves done
"""
2013-06-29 22:17:03 +00:00
context = context or { }
2013-06-19 14:54:06 +00:00
quant_obj = self . pool . get ( " stock.quant " )
uom_obj = self . pool . get ( " product.uom " )
2013-06-29 22:17:03 +00:00
done = [ ]
2010-06-21 18:40:58 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2013-06-29 22:17:03 +00:00
if move . state not in ( ' confirmed ' , ' waiting ' ) :
continue
if move . product_id . type == ' consu ' :
done . append ( move . id )
2008-07-22 15:11:28 +00:00
continue
2010-08-30 13:31:38 +00:00
else :
2013-06-29 22:17:03 +00:00
qty = uom_obj . _compute_qty ( cr , uid , move . product_uom . id , move . product_qty , move . product_id . uom_id . id )
dp = [ ]
2013-07-16 10:15:40 +00:00
for m2 in move . move_orig_ids :
for q in m2 . quant_ids :
dp . append ( str ( q . id ) )
2013-08-26 15:36:48 +00:00
qty - = q . qty
2013-07-16 10:15:40 +00:00
domain = [ ' | ' , ( ' reservation_id ' , ' = ' , False ) , ( ' reservation_id ' , ' = ' , move . id ) ]
quants = quant_obj . quants_get ( cr , uid , move . location_id , move . product_id , qty , domain = domain , prefered_order = dp and ( ' id not in ( ' + ' , ' . join ( dp ) + ' ) ' ) or False , context = context )
2013-07-29 21:44:43 +00:00
#Will only reserve physical quants, no negative
2013-06-30 08:55:03 +00:00
quant_obj . quants_reserve ( cr , uid , quants , move , context = context )
2013-07-16 10:15:40 +00:00
# the total quantity is provided by existing quants
if all ( map ( lambda x : x [ 0 ] , quants ) ) :
2013-07-01 19:13:08 +00:00
done . append ( move . id )
2013-06-29 22:17:03 +00:00
self . write ( cr , uid , done , { ' state ' : ' assigned ' } )
2013-07-16 10:15:40 +00:00
return done
2013-06-29 22:17:03 +00:00
2010-06-16 08:40:57 +00:00
2008-07-22 15:11:28 +00:00
#
# Cancel move => cancel others move and pickings
#
2010-07-06 11:21:27 +00:00
def action_cancel ( self , cr , uid , ids , context = None ) :
2010-05-26 12:59:30 +00:00
""" Cancels the moves and if all moves are cancelled it cancels the picking.
@return : True
"""
2013-06-29 22:17:03 +00:00
context = context or { }
2010-12-13 06:43:09 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2013-08-29 13:02:07 +00:00
if move . reserved_quant_ids :
self . pool . get ( " stock.quant " ) . quants_unreserve ( cr , uid , move , context = context )
2013-06-29 22:17:03 +00:00
if move . move_dest_id :
2013-07-25 15:38:23 +00:00
if move . propagate :
2013-06-29 22:17:03 +00:00
self . action_cancel ( cr , uid , [ move . move_dest_id . id ] , context = context )
elif move . move_dest_id . state == ' waiting ' :
self . write ( cr , uid , [ move . move_dest_id . id ] , { ' state ' : ' confirmed ' } )
return self . write ( cr , uid , ids , { ' state ' : ' cancel ' , ' move_dest_id ' : False } )
2013-07-26 12:55:32 +00:00
#def _get_quants_from_pack(self, cr, uid, ids, context=None):
# """
# Suppose for the moment we don't have any packaging
# """
# res = {}
# for move in self.browse(cr, uid, ids, context=context):
# #Split according to pack wizard if necessary
# res[move.id] = [x.id for x in move.reserved_quant_ids]
# return res
2013-06-19 08:35:48 +00:00
2010-07-06 11:21:27 +00:00
def action_done ( self , cr , uid , ids , context = None ) :
2010-05-26 12:59:30 +00:00
""" Makes the move done and if all moves are done, it will finish the picking.
2013-07-16 10:15:40 +00:00
If quants are not assigned yet , it should assign them
Putaway strategies should be applied
2010-06-16 08:40:57 +00:00
@return :
2010-05-26 12:59:30 +00:00
"""
2013-06-30 08:55:03 +00:00
context = context or { }
quant_obj = self . pool . get ( " stock.quant " )
2013-07-17 13:51:42 +00:00
todo = [ move . id for move in self . browse ( cr , uid , ids , context = context ) if move . state == " draft " ]
2010-10-10 17:32:39 +00:00
if todo :
self . action_confirm ( cr , uid , todo , context = context )
2013-07-26 12:55:32 +00:00
pickings = set ( )
2013-07-16 10:15:40 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2013-07-26 12:55:32 +00:00
if move . picking_id :
pickings . add ( move . picking_id . id )
2013-09-03 16:27:57 +00:00
qty = move . product_qty
2013-07-29 21:44:43 +00:00
2013-07-16 10:15:40 +00:00
# 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
domain = [ ' | ' , ( ' reservation_id ' , ' = ' , False ) , ( ' reservation_id ' , ' = ' , move . id ) ]
2013-09-03 09:17:26 +00:00
prefered_order = ' reservation_id '
2013-08-22 09:48:13 +00:00
# if lot_id:
# prefered_order = 'lot_id<>' + lot_id + ", " + prefered_order
# if pack_id:
# prefered_order = 'pack_id<>' + pack_id + ", " + prefered_order
quants = quant_obj . quants_get ( cr , uid , move . location_id , move . product_id , qty , domain = domain , prefered_order = prefered_order , context = context )
2013-07-16 10:15:40 +00:00
#Will move all quants_get and as such create negative quants
quant_obj . quants_move ( cr , uid , quants , move , context = context )
quant_obj . quants_unreserve ( cr , uid , move , context = context )
2013-07-29 21:44:43 +00:00
2013-07-16 10:15:40 +00:00
#
#Check moves that were pushed
if move . move_dest_id . state in ( ' waiting ' , ' confirmed ' ) :
other_upstream_move_ids = self . search ( cr , uid , [ ( ' id ' , ' != ' , move . id ) , ( ' state ' , ' not in ' , [ ' done ' , ' cancel ' ] ) ,
( ' move_dest_id ' , ' = ' , move . move_dest_id . id ) ] , context = context )
#If no other moves for the move that got pushed:
if not other_upstream_move_ids and move . move_dest_id . state in ( ' waiting ' , ' confirmed ' ) :
self . action_assign ( cr , uid , [ move . move_dest_id . id ] , context = context )
2013-06-30 10:20:28 +00:00
self . write ( cr , uid , ids , { ' state ' : ' done ' , ' date ' : time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) } , context = context )
2008-07-22 15:11:28 +00:00
return True
2010-06-16 08:40:57 +00:00
2008-07-22 15:11:28 +00:00
def unlink ( self , cr , uid , ids , context = None ) :
2013-07-29 21:44:43 +00:00
context = context or { }
2008-07-22 15:11:28 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2013-07-29 21:44:43 +00:00
if move . state not in ( ' draft ' , ' cancel ' ) :
2013-03-19 12:52:44 +00:00
raise osv . except_osv ( _ ( ' User Error! ' ) , _ ( ' You can only delete draft moves. ' ) )
2013-07-29 21:44:43 +00:00
return super ( stock_move , self ) . unlink ( cr , uid , ids , context = context )
2007-12-31 13:31:32 +00:00
2010-03-18 06:31:22 +00:00
def action_scrap ( self , cr , uid , ids , quantity , location_id , context = None ) :
2010-05-26 12:59:30 +00:00
""" Move the scrap/damaged product into scrap location
@param cr : the database cursor
@param uid : the user id
2010-08-31 06:54:27 +00:00
@param ids : ids of stock move object to be scrapped
2010-05-26 12:59:30 +00:00
@param quantity : specify scrap qty
@param location_id : specify scrap location
@param context : context arguments
@return : Scraped lines
"""
2011-12-21 11:38:22 +00:00
#quantity should in MOVE UOM
2010-03-18 06:31:22 +00:00
if quantity < = 0 :
2012-07-17 13:21:35 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' Please provide a positive quantity to scrap. ' ) )
2010-03-19 08:46:14 +00:00
res = [ ]
for move in self . browse ( cr , uid , ids , context = context ) :
2013-03-19 12:52:16 +00:00
source_location = move . location_id
if move . state == ' done ' :
source_location = move . location_dest_id
2013-09-04 08:31:18 +00:00
#Previously used to prevent scraping from virtual location but not necessary anymore
#if source_location.usage != 'internal':
2013-03-19 12:52:16 +00:00
#restrict to scrap from a virtual location because it's meaningless and it may introduce errors in stock ('creating' new products from nowhere)
2013-09-04 08:31:18 +00:00
#raise osv.except_osv(_('Error!'), _('Forbidden operation: it is not allowed to scrap products from a virtual location.'))
2010-03-18 06:31:22 +00:00
move_qty = move . product_qty
uos_qty = quantity / move_qty * move . product_uos_qty
default_val = {
2013-03-19 12:52:16 +00:00
' location_id ' : source_location . id ,
2013-08-07 11:13:58 +00:00
' product_uom_qty ' : quantity ,
2010-08-13 12:20:05 +00:00
' product_uos_qty ' : uos_qty ,
' state ' : move . state ,
2013-03-19 12:52:16 +00:00
' scrapped ' : True ,
2010-08-30 07:37:18 +00:00
' location_dest_id ' : location_id ,
2013-08-07 11:02:19 +00:00
#TODO lot_id is now on quant and not on move, need to do something for this
#'lot_id': move.lot_id.id,
2010-08-13 12:20:05 +00:00
}
2010-03-18 06:31:22 +00:00
new_move = self . copy ( cr , uid , move . id , default_val )
2010-09-02 12:00:21 +00:00
2011-04-07 11:32:08 +00:00
res + = [ new_move ]
2010-07-23 10:11:19 +00:00
product_obj = self . pool . get ( ' product.product ' )
2012-04-03 09:46:42 +00:00
for product in product_obj . browse ( cr , uid , [ move . product_id . id ] , context = context ) :
2012-04-03 13:52:37 +00:00
if move . picking_id :
uom = product . uom_id . name if product . uom_id else ' '
2012-12-19 20:55:13 +00:00
message = _ ( " %s %s %s has been <b>moved to</b> scrap. " ) % ( quantity , uom , product . name )
move . picking_id . message_post ( body = message )
2010-07-23 10:11:19 +00:00
2011-12-09 08:32:35 +00:00
self . action_done ( cr , uid , res , context = context )
2010-03-18 06:31:22 +00:00
return res
2010-12-31 14:12:42 +00:00
def action_consume ( self , cr , uid , ids , quantity , location_id = False , context = None ) :
2010-05-26 12:59:30 +00:00
""" Consumed product with specific quatity from specific source location
@param cr : the database cursor
@param uid : the user id
@param ids : ids of stock move object to be consumed
@param quantity : specify consume quantity
@param location_id : specify source location
@param context : context arguments
@return : Consumed lines
"""
2011-12-21 11:38:22 +00:00
#quantity should in MOVE UOM
2010-05-18 14:26:51 +00:00
if context is None :
2010-02-24 09:57:17 +00:00
context = { }
2010-02-23 05:34:00 +00:00
if quantity < = 0 :
2012-07-17 13:21:35 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' Please provide proper quantity. ' ) )
2010-03-18 11:25:02 +00:00
res = [ ]
for move in self . browse ( cr , uid , ids , context = context ) :
2010-02-23 05:34:00 +00:00
move_qty = move . product_qty
2010-12-21 07:16:53 +00:00
if move_qty < = 0 :
2012-07-17 13:21:35 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' Cannot consume a move with negative or zero quantity. ' ) )
2010-02-23 05:34:00 +00:00
quantity_rest = move . product_qty
2010-03-18 11:25:02 +00:00
quantity_rest - = quantity
2010-02-19 13:44:55 +00:00
uos_qty_rest = quantity_rest / move_qty * move . product_uos_qty
2010-02-22 07:23:42 +00:00
if quantity_rest < = 0 :
2010-03-18 11:25:02 +00:00
quantity_rest = 0
2010-02-24 08:17:06 +00:00
uos_qty_rest = 0
quantity = move . product_qty
2010-03-18 11:25:02 +00:00
2010-02-24 08:17:06 +00:00
uos_qty = quantity / move_qty * move . product_uos_qty
2010-03-18 11:25:02 +00:00
if quantity_rest > 0 :
2010-02-24 08:17:06 +00:00
default_val = {
2010-03-18 11:25:02 +00:00
' product_qty ' : quantity ,
' product_uos_qty ' : uos_qty ,
2010-12-31 14:12:42 +00:00
' state ' : move . state ,
2010-10-20 10:58:16 +00:00
' location_id ' : location_id or move . location_id . id ,
2010-02-24 08:17:06 +00:00
}
2011-03-31 10:07:25 +00:00
current_move = self . copy ( cr , uid , move . id , default_val )
res + = [ current_move ]
2010-03-18 11:25:02 +00:00
update_val = { }
2010-02-23 05:34:00 +00:00
update_val [ ' product_qty ' ] = quantity_rest
2010-03-18 11:25:02 +00:00
update_val [ ' product_uos_qty ' ] = uos_qty_rest
self . write ( cr , uid , [ move . id ] , update_val )
2010-02-24 08:17:06 +00:00
2010-03-18 11:25:02 +00:00
else :
quantity_rest = quantity
2010-02-24 10:55:03 +00:00
uos_qty_rest = uos_qty
2011-03-31 10:07:25 +00:00
res + = [ move . id ]
update_val = {
2010-03-18 06:31:22 +00:00
' product_qty ' : quantity_rest ,
' product_uos_qty ' : uos_qty_rest ,
2012-02-06 13:26:12 +00:00
' location_id ' : location_id or move . location_id . id ,
2011-03-31 10:07:25 +00:00
}
self . write ( cr , uid , [ move . id ] , update_val )
2012-04-03 12:50:53 +00:00
2011-12-09 08:32:35 +00:00
self . action_done ( cr , uid , res , context = context )
2013-06-03 08:58:18 +00:00
return res
2013-08-30 15:12:01 +00:00
2013-07-30 07:46:15 +00:00
def split ( self , cr , uid , move , qty , context = None ) :
2013-08-29 13:02:07 +00:00
"""
Splits qty from move move into a new move
2010-03-26 14:52:01 +00:00
"""
2013-07-30 07:46:15 +00:00
if move . product_qty == qty :
2013-08-06 07:25:51 +00:00
return move . id
2013-07-30 07:46:15 +00:00
if ( move . product_qty < qty ) or ( qty == 0 ) :
return False
2010-03-26 14:52:01 +00:00
uom_obj = self . pool . get ( ' product.uom ' )
2013-07-30 07:46:15 +00:00
context = context or { }
2010-03-26 14:52:01 +00:00
2013-07-30 08:47:16 +00:00
uom_qty = uom_obj . _compute_qty ( cr , uid , move . product_id . uom_id . id , qty , move . product_uom . id )
2013-07-30 07:46:15 +00:00
uos_qty = uom_qty * move . product_uos_qty / move . product_uom_qty
2010-07-06 11:21:27 +00:00
2013-07-30 08:47:16 +00:00
if move . state in ( ' done ' , ' cancel ' ) :
2013-07-30 07:46:15 +00:00
raise osv . except_osv ( _ ( ' Error ' ) , _ ( ' You cannot split a move done ' ) )
defaults = {
' product_uom_qty ' : uom_qty ,
' product_uos_qty ' : uos_qty ,
' state ' : move . state ,
' reserved_quant_ids ' : [ ]
}
new_move = self . copy ( cr , uid , move . id , defaults )
2006-12-07 13:41:40 +00:00
2013-07-30 07:46:15 +00:00
self . write ( cr , uid , [ move . id ] , {
' product_uom_qty ' : move . product_uom_qty - uom_qty ,
' product_uos_qty ' : move . product_uos_qty - uos_qty ,
2013-08-22 17:22:48 +00:00
# 'reserved_quant_ids': [(6,0,[])] SHOULD NOT CHANGE as it has been reserved already
2013-07-30 07:46:15 +00:00
} , context = context )
2013-08-29 13:02:07 +00:00
if move . move_dest_id and move . propagate :
2013-08-30 13:25:07 +00:00
self . split ( cr , uid , move . move_dest_id , qty , context = context )
2013-07-30 07:46:15 +00:00
return new_move
2013-07-12 16:54:49 +00:00
2006-12-07 13:41:40 +00:00
class stock_inventory ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " stock.inventory "
_description = " Inventory "
2013-08-06 12:30:08 +00:00
def _get_move_ids_exist ( self , cr , uid , ids , field_name , arg , context = None ) :
res = { }
for inv in self . browse ( cr , uid , ids , context = context ) :
res [ inv . id ] = False
if inv . move_ids :
res [ inv . id ] = True
return res
2008-07-22 15:11:28 +00:00
_columns = {
2013-08-27 08:34:59 +00:00
' name ' : fields . char ( ' Inventory Reference ' , size = 64 , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , help = " Inventory Name. " ) ,
' date ' : fields . datetime ( ' Creation Date ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , help = " Inventory Create Date. " ) ,
2013-08-27 09:49:00 +00:00
' date_done ' : fields . datetime ( ' Date done ' , help = " Inventory Validation Date. " ) ,
' line_ids ' : fields . one2many ( ' stock.inventory.line ' , ' inventory_id ' , ' Inventories ' , readonly = False , states = { ' done ' : [ ( ' readonly ' , True ) ] } , help = " Inventory Lines. " ) ,
' move_ids ' : fields . one2many ( ' stock.move ' , ' inventory_id ' , ' Created Moves ' , help = " Inventory Moves. " ) ,
2013-08-27 08:34:59 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' Draft ' ) , ( ' cancel ' , ' Cancelled ' ) , ( ' confirm ' , ' In Progress ' ) , ( ' done ' , ' Validated ' ) ] , ' Status ' , readonly = True , select = True ) ,
2013-08-27 09:49:00 +00:00
' 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 ) ,
2013-09-03 06:32:11 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , help = " Select Product to Specify filters to focus your inventory a on particular product " ) ,
' package_id ' : fields . many2one ( ' stock.quant.package ' , ' Pack ' , help = " Select Pack to Specify filters to focus your inventory a on particular Pack " ) ,
' partner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' , help = " Select Owner to Specify filters to focus your inventory a on particular Owner " ) ,
' lot_id ' : fields . many2one ( ' stock.production.lot ' , ' Lot/Serial Number ' , help = " Select Lot/Serial Number to Specify filters to focus your inventory a on particular Lot/Serial Number " ) ,
2013-08-06 12:30:08 +00:00
' move_ids_exist ' : fields . function ( _get_move_ids_exist , type = ' boolean ' , string = ' Stock Move Exists? ' , help = ' technical field for attrs in view ' ) ,
2008-07-22 15:11:28 +00:00
}
2013-08-05 23:23:48 +00:00
def _default_stock_location ( self , cr , uid , context = None ) :
try :
stock_location = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , ' stock ' , ' stock_location_stock ' )
return stock_location . id
except :
return False
2008-07-22 15:11:28 +00:00
_defaults = {
2011-02-23 06:15:04 +00:00
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2010-07-06 11:44:51 +00:00
' state ' : ' draft ' ,
2013-08-05 23:23:48 +00:00
' 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 ,
2008-07-22 15:11:28 +00:00
}
2010-10-26 13:56:22 +00:00
2013-08-27 10:06:38 +00:00
def set_checked_qty ( self , cr , uid , ids , context = None ) :
inventory = self . browse ( cr , uid , ids [ 0 ] , context = context )
line_ids = [ line . id for line in inventory . line_ids ]
self . pool . get ( ' stock.inventory.line ' ) . write ( cr , uid , line_ids , { ' product_qty ' : 0 } )
return True
2012-01-06 12:59:14 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
if default is None :
default = { }
default = default . copy ( )
default . update ( { ' move_ids ' : [ ] , ' date_done ' : False } )
return super ( stock_inventory , self ) . copy ( cr , uid , id , default , context = context )
2010-02-28 03:18:01 +00:00
def _inventory_line_hook ( self , cr , uid , inventory_line , move_vals ) :
2010-05-26 12:59:30 +00:00
""" Creates a stock move from an inventory line
@param inventory_line :
@param move_vals :
2010-06-16 08:40:57 +00:00
@return :
2010-05-26 12:59:30 +00:00
"""
2010-02-28 03:18:01 +00:00
return self . pool . get ( ' stock.move ' ) . create ( cr , uid , move_vals )
2009-08-28 09:40:49 +00:00
2008-07-22 15:11:28 +00:00
def action_done ( self , cr , uid , ids , context = None ) :
2010-12-20 14:30:49 +00:00
""" Finish the inventory
2010-11-23 13:24:34 +00:00
@return : True
"""
if context is None :
context = { }
2010-11-18 06:47:12 +00:00
move_obj = self . pool . get ( ' stock.move ' )
2010-11-18 07:08:23 +00:00
for inv in self . browse ( cr , uid , ids , context = context ) :
2013-08-06 12:30:08 +00:00
if not inv . move_ids :
self . action_check ( cr , uid , [ inv . id ] , context = context )
2013-08-07 09:58:11 +00:00
inv . refresh ( )
2010-11-18 07:08:23 +00:00
move_obj . action_done ( cr , uid , [ x . id for x in inv . move_ids ] , context = context )
2013-08-05 23:23:48 +00:00
self . write ( cr , uid , [ inv . id ] , { ' state ' : ' done ' , ' date_done ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) } , context = context )
2010-11-11 10:46:37 +00:00
return True
2013-08-05 23:23:48 +00:00
def _create_stock_move ( self , cr , uid , inventory , todo_line , context = None ) :
stock_move_obj = self . pool . get ( ' stock.move ' )
product_obj = self . pool . get ( ' product.product ' )
inventory_location_id = product_obj . browse ( cr , uid , todo_line [ ' product_id ' ] , context = context ) . property_stock_inventory . id
vals = {
' name ' : _ ( ' INV: ' ) + ( inventory . name or ' ' ) ,
' product_id ' : todo_line [ ' product_id ' ] ,
' product_uom ' : todo_line [ ' product_uom_id ' ] ,
' date ' : inventory . date ,
' company_id ' : inventory . company_id . id ,
' inventory_id ' : inventory . id ,
}
if todo_line [ ' product_qty ' ] < 0 :
#found more than expected
vals [ ' location_id ' ] = inventory_location_id
vals [ ' location_dest_id ' ] = todo_line [ ' location_id ' ]
vals [ ' product_uom_qty ' ] = - todo_line [ ' product_qty ' ]
else :
#found less than expected
vals [ ' location_id ' ] = todo_line [ ' location_id ' ]
vals [ ' location_dest_id ' ] = inventory_location_id
vals [ ' product_uom_qty ' ] = todo_line [ ' product_qty ' ]
stock_move_obj . create ( cr , uid , vals , context = context )
2013-08-06 12:30:08 +00:00
def action_check ( self , cr , uid , ids , context = None ) :
""" Checks the inventory and computes the stock move to do
2010-05-26 12:59:30 +00:00
@return : True
"""
2013-08-05 23:23:48 +00:00
inventory_line_obj = self . pool . get ( ' stock.inventory.line ' )
stock_move_obj = self . pool . get ( ' stock.move ' )
for inventory in self . browse ( cr , uid , ids , context = context ) :
#first remove the existing stock moves linked to this inventory
move_ids = [ move . id for move in inventory . move_ids ]
stock_move_obj . unlink ( cr , uid , move_ids , context = context )
#compute what should be in the inventory lines
theorical_lines = self . _get_inventory_lines ( cr , uid , inventory , context = context )
for line in inventory . line_ids :
#compare the inventory lines to the theorical ones and store the diff in theorical_lines
inventory_line_obj . _resolve_inventory_line ( cr , uid , line , theorical_lines , context = context )
#each theorical_lines where product_qty is not 0 is a difference for which we need to create a stock move
for todo_line in theorical_lines :
if todo_line [ ' product_qty ' ] != 0 :
self . _create_stock_move ( cr , uid , inventory , todo_line , context = context )
2008-07-22 15:11:28 +00:00
2011-01-17 10:01:22 +00:00
def action_cancel_draft ( self , cr , uid , ids , context = None ) :
""" Cancels the stock move and change inventory state to draft.
@return : True
"""
for inv in self . browse ( cr , uid , ids , context = context ) :
self . pool . get ( ' stock.move ' ) . action_cancel ( cr , uid , [ x . id for x in inv . move_ids ] , context = context )
2013-08-05 23:23:48 +00:00
self . write ( cr , uid , [ inv . id ] , { ' state ' : ' draft ' } , context = context )
2011-01-17 10:01:22 +00:00
return True
2011-12-21 11:38:22 +00:00
def action_cancel_inventory ( self , cr , uid , ids , context = None ) :
2013-07-11 13:05:28 +00:00
#TODO test
self . action_cancel_draft ( cr , uid , ids , context = context )
2009-08-28 09:40:49 +00:00
2013-08-29 11:28:27 +00:00
def prepare_inventory ( self , cr , uid , ids , context = None ) :
2013-08-05 23:23:48 +00:00
inventory_line_obj = self . pool . get ( ' stock.inventory.line ' )
for inventory in self . browse ( cr , uid , ids , context = context ) :
#clean the existing inventory lines before redoing an inventory proposal
line_ids = [ line . id for line in inventory . line_ids ]
inventory_line_obj . unlink ( cr , uid , line_ids , context = context )
#compute the inventory lines and create them
vals = self . _get_inventory_lines ( cr , uid , inventory , context = context )
for product_line in vals :
inventory_line_obj . create ( cr , uid , product_line , context = context )
2013-08-06 12:30:08 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' confirm ' } )
2013-08-05 23:23:48 +00:00
def _get_inventory_lines ( self , cr , uid , inventory , context = None ) :
location_obj = self . pool . get ( ' stock.location ' )
product_obj = self . pool . get ( ' product.product ' )
location_ids = location_obj . search ( cr , uid , [ ( ' id ' , ' child_of ' , [ inventory . location_id . id ] ) ] , context = context )
domain = ' location_id in %s '
args = ( tuple ( location_ids ) , )
if inventory . partner_id :
domain + = ' and partner_id = %s '
args + = ( inventory . partner_id . id , )
if inventory . lot_id :
domain + = ' and lot_id = %s '
args + = ( inventory . lot_id . id , )
if inventory . product_id :
domain + = ' and product_id = %s '
args + = ( inventory . product_id . id , )
if inventory . package_id :
domain + = ' and package_id = %s '
args + = ( inventory . package_id . id , )
cr . execute ( '''
SELECT product_id , sum ( qty ) as product_qty , location_id , lot_id as prod_lot_id , package_id
FROM stock_quant WHERE ''' + domain + '''
GROUP BY product_id , location_id , lot_id , package_id
''' , args)
vals = [ ]
for product_line in cr . dictfetchall ( ) :
product_line [ ' inventory_id ' ] = inventory . id
if product_line [ ' product_id ' ] :
product_line [ ' product_uom_id ' ] = product_obj . browse ( cr , uid , product_line [ ' product_id ' ] , context = context ) . uom_id . id
vals . append ( product_line )
return vals
2006-12-07 13:41:40 +00:00
class stock_inventory_line ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " stock.inventory.line "
2010-05-19 18:32:32 +00:00
_description = " Inventory Line "
2011-05-05 05:46:31 +00:00
_rec_name = " inventory_id "
2008-07-22 15:11:28 +00:00
_columns = {
2009-08-28 09:40:49 +00:00
' inventory_id ' : fields . many2one ( ' stock.inventory ' , ' Inventory ' , ondelete = ' cascade ' , select = True ) ,
2013-08-05 23:23:48 +00:00
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True , select = True ) ,
2010-09-06 13:51:23 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , select = True ) ,
2013-08-05 23:23:48 +00:00
' package_id ' : fields . many2one ( ' stock.quant.package ' , ' Pack ' , select = True ) ,
' product_uom_id ' : fields . many2one ( ' product.uom ' , ' Product Unit of Measure ' , required = True ) ,
2012-04-25 12:09:08 +00:00
' product_qty ' : fields . float ( ' Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ) ,
2013-07-09 13:22:39 +00:00
' company_id ' : fields . related ( ' inventory_id ' , ' company_id ' , type = ' many2one ' , relation = ' res.company ' , string = ' Company ' , store = True , select = True , readonly = True ) ,
2012-04-24 13:08:05 +00:00
' prod_lot_id ' : fields . many2one ( ' stock.production.lot ' , ' Serial Number ' , domain = " [( ' product_id ' , ' = ' ,product_id)] " ) ,
2013-07-09 13:22:39 +00:00
' state ' : fields . related ( ' inventory_id ' , ' state ' , type = ' char ' , string = ' Status ' , readonly = True ) ,
2013-08-29 11:28:27 +00:00
' real_qty ' : fields . related ( ' product_id ' , ' qty_available ' , type = ' float ' , string = ' Real Quantity ' ) ,
2008-07-22 15:11:28 +00:00
}
2010-03-15 13:16:40 +00:00
2013-08-05 23:23:48 +00:00
def _resolve_inventory_line ( self , cr , uid , inventory_line , theorical_lines , context = None ) :
found = False
2013-08-06 07:47:30 +00:00
#first try to match the inventory line with a theorical line with same product, lot and location
2013-08-05 23:23:48 +00:00
for th_line in theorical_lines :
if th_line [ ' location_id ' ] == inventory_line . location_id . id and th_line [ ' product_id ' ] == inventory_line . product_id . id and th_line [ ' prod_lot_id ' ] == inventory_line . prod_lot_id . id :
th_line [ ' product_qty ' ] - = inventory_line . product_qty
found = True
break
2013-08-06 07:47:30 +00:00
#then if the line was not found, try to match the inventory line with a theorical line with same product and location (only if it has no lot information given)
2013-08-05 23:23:48 +00:00
if not found :
for th_line in theorical_lines :
2013-08-06 07:47:30 +00:00
if th_line [ ' location_id ' ] == inventory_line . location_id . id and th_line [ ' product_id ' ] == inventory_line . product_id . id and not inventory_line . prod_lot_id . id :
2013-08-05 23:23:48 +00:00
th_line [ ' product_qty ' ] - = inventory_line . product_qty
found = True
break
2013-08-06 07:47:30 +00:00
#if it was still not found, we add it to the theorical lines so that it will create a stock move for it
2013-08-05 23:23:48 +00:00
if not found :
vals = {
' inventory_id ' : inventory_line . inventory_id . id ,
' location_id ' : inventory_line . location_id . id ,
' product_id ' : inventory_line . product_id . id ,
' product_uom_id ' : inventory_line . product_id . uom_id . id ,
' product_qty ' : - inventory_line . product_qty ,
' prod_lot_id ' : inventory_line . prod_lot_id . id ,
}
theorical_lines . append ( vals )
2012-08-03 12:23:54 +00:00
2013-06-30 14:29:27 +00:00
def on_change_product_id ( self , cr , uid , ids , location_id , product , uom = False , to_date = False , context = None ) :
2010-05-26 12:59:30 +00:00
""" Changes UoM and name if product_id changes.
@param location_id : Location id
@param product : Changed product_id
2010-06-16 08:40:57 +00:00
@param uom : UoM product
2010-05-26 12:59:30 +00:00
@return : Dictionary of changed values
"""
2013-07-09 13:22:39 +00:00
context = context or { }
2008-07-22 15:11:28 +00:00
if not product :
2013-08-05 23:23:48 +00:00
return { ' value ' : { ' product_qty ' : 0.0 , ' product_uom_id ' : False } }
2013-06-30 14:29:27 +00:00
context [ ' location ' ] = location_id
obj_product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product , context = context )
2011-03-29 13:15:43 +00:00
uom = uom or obj_product . uom_id . id
2013-06-30 14:29:27 +00:00
amount = obj_product . qty_available
2013-08-05 23:23:48 +00:00
result = { ' product_qty ' : amount , ' product_uom_id ' : uom }
2009-08-28 09:40:49 +00:00
return { ' value ' : result }
2006-12-07 13:41:40 +00:00
#----------------------------------------------------------
# Stock Warehouse
#----------------------------------------------------------
class stock_warehouse ( osv . osv ) :
2008-07-22 15:11:28 +00:00
_name = " stock.warehouse "
_description = " Warehouse "
_columns = {
2010-09-06 13:51:23 +00:00
' name ' : fields . char ( ' Name ' , size = 128 , required = True , select = True ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True , select = True ) ,
2013-09-03 12:54:19 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Address ' ) ,
2013-07-09 13:22:39 +00:00
' lot_stock_id ' : fields . many2one ( ' stock.location ' , ' Location Stock ' , required = True , domain = [ ( ' usage ' , ' = ' , ' internal ' ) ] ) ,
2008-07-22 15:11:28 +00:00
}
2012-08-03 12:23:54 +00:00
2013-09-03 09:03:34 +00:00
def _default_stock_id ( self , cr , uid , context = None ) :
2012-08-03 12:23:54 +00:00
lot_input_stock = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , ' stock ' , ' stock_location_stock ' )
return lot_input_stock . id
2009-11-28 09:25:22 +00:00
_defaults = {
2010-07-06 11:21:27 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.inventory ' , context = c ) ,
2013-09-03 09:03:34 +00:00
' lot_stock_id ' : _default_stock_id ,
2009-11-28 09:25:22 +00:00
}
2010-07-06 11:21:27 +00:00
2006-12-07 13:41:40 +00:00
2013-06-20 09:31:02 +00:00
# -------------------------
# Packaging related stuff
# -------------------------
2013-07-08 10:34:00 +00:00
from openerp . report import report_sxw
report_sxw . report_sxw ( ' report.stock.quant.package.barcode ' , ' stock.quant.package ' , ' addons/stock/report/picking_barcode.rml ' )
2013-06-20 09:31:02 +00:00
class stock_package ( osv . osv ) :
"""
2013-08-29 13:02:07 +00:00
These are the packages , containing quants and / or other packages
2013-06-20 09:31:02 +00:00
"""
_name = " stock.quant.package "
2013-06-29 22:17:03 +00:00
_description = " Physical Packages "
2013-07-28 12:42:01 +00:00
_order = ' name '
2013-07-22 09:11:09 +00:00
def name_get ( self , cr , uid , ids , context = None ) :
res = self . _complete_name ( cr , uid , ids , ' complete_name ' , None , context = context )
return res . items ( )
def _complete_name ( self , cr , uid , ids , name , args , context = None ) :
""" Forms complete name of location from parent location to child location.
@return : Dictionary of values
"""
res = { }
for m in self . browse ( cr , uid , ids , context = context ) :
res [ m . id ] = m . name
parent = m . parent_id
while parent :
res [ m . id ] = parent . name + ' / ' + res [ m . id ]
2013-08-01 14:04:01 +00:00
parent = parent . parent_id
2013-07-22 09:11:09 +00:00
return res
2013-07-22 09:32:52 +00:00
def _get_packages ( self , cr , uid , ids , context = None ) :
""" Returns packages from quants for store """
res = set ( )
for quant in self . browse ( cr , uid , ids , context = context ) :
2013-07-23 15:34:24 +00:00
if quant . package_id :
res . add ( quant . package_id . id )
2013-07-22 09:32:52 +00:00
return list ( res )
2013-08-02 08:00:07 +00:00
def _get_packages_to_relocate ( self , cr , uid , ids , context = None ) :
res = set ( )
for pack in self . browse ( cr , uid , ids , context = context ) :
res . add ( pack . id )
if pack . parent_id :
res . add ( pack . parent_id . id )
return list ( res )
2013-08-02 09:04:46 +00:00
def _get_package_info ( self , cr , uid , ids , name , args , context = None ) :
default_company_id = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . company_id . id
res = { } . fromkeys ( ids , { ' location_id ' : False , ' company_id ' : default_company_id } )
2013-08-02 08:00:07 +00:00
for pack in self . browse ( cr , uid , ids , context = context ) :
if pack . quant_ids :
2013-08-02 09:04:46 +00:00
res [ pack . id ] [ ' location_id ' ] = pack . quant_ids [ 0 ] . location_id . id
res [ pack . id ] [ ' company_id ' ] = pack . quant_ids [ 0 ] . company_id . id
2013-08-02 08:00:07 +00:00
elif pack . children_ids :
2013-08-02 09:04:46 +00:00
res [ pack . id ] [ ' location_id ' ] = pack . children_ids [ 0 ] . location_id . id
res [ pack . id ] [ ' company_id ' ] = pack . children_ids [ 0 ] . company_id . id
2013-08-02 08:00:07 +00:00
return res
2013-06-20 09:31:02 +00:00
_columns = {
' name ' : fields . char ( ' Package Reference ' , size = 64 , select = True ) ,
2013-08-02 08:00:07 +00:00
' complete_name ' : fields . function ( _complete_name , type = ' char ' , string = " Package Name " , ) ,
2013-07-22 09:11:09 +00:00
' parent_left ' : fields . integer ( ' Left Parent ' , select = 1 ) ,
' parent_right ' : fields . integer ( ' Right Parent ' , select = 1 ) ,
2013-07-23 15:34:24 +00:00
' packaging_id ' : fields . many2one ( ' product.packaging ' , ' Type of Packaging ' ) ,
2013-08-02 09:04:46 +00:00
' location_id ' : fields . function ( _get_package_info , type = ' many2one ' , relation = ' stock.location ' , string = ' Location ' , multi = " package " ,
2013-08-02 08:00:07 +00:00
store = {
' stock.quant ' : ( _get_packages , [ ' location_id ' ] , 10 ) ,
' stock.quant.package ' : ( _get_packages_to_relocate , [ ' children_ids ' , ' quant_ids ' , ' parent_id ' ] , 10 ) ,
} , readonly = True ) ,
2013-07-23 07:50:53 +00:00
' quant_ids ' : fields . one2many ( ' stock.quant ' , ' package_id ' , ' Bulk Content ' ) ,
2013-08-02 08:00:07 +00:00
' parent_id ' : fields . many2one ( ' stock.quant.package ' , ' Parent Package ' , help = " The package containing this item " ) ,
2013-06-29 22:17:03 +00:00
' children_ids ' : fields . one2many ( ' stock.quant.package ' , ' parent_id ' , ' Contained Packages ' ) ,
2013-08-02 09:04:46 +00:00
' company_id ' : fields . function ( _get_package_info , type = " many2one " , relation = ' res.company ' , string = ' Company ' , multi = " package " ) ,
2013-06-29 22:17:03 +00:00
}
2013-06-28 07:35:06 +00:00
_defaults = {
' name ' : lambda self , cr , uid , context : self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.quant.package ' ) or _ ( ' Unknown Pack ' )
}
2013-06-20 09:31:02 +00:00
def _check_location ( self , cr , uid , ids , context = None ) :
2013-06-29 22:17:03 +00:00
''' checks that all quants in a package are stored in the same location '''
2013-08-01 14:04:01 +00:00
quant_obj = self . pool . get ( ' stock.quant ' )
2013-06-20 09:31:02 +00:00
for pack in self . browse ( cr , uid , ids , context = context ) :
2013-08-01 14:04:01 +00:00
parent = pack
2013-08-02 08:00:07 +00:00
while parent . parent_id :
2013-08-01 14:04:01 +00:00
parent = parent . parent_id
2013-08-02 08:00:07 +00:00
quant_ids = self . get_content ( cr , uid , [ parent . id ] , context = context )
quants = quant_obj . browse ( cr , uid , quant_ids , context = context )
location_id = quants and quants [ 0 ] . location_id . id or False
if not all ( [ quant . location_id . id == location_id for quant in quants ] ) :
2013-06-20 09:31:02 +00:00
return False
return True
2013-08-02 08:00:07 +00:00
2013-06-20 09:31:02 +00:00
_constraints = [
2013-08-02 08:00:07 +00:00
( _check_location , ' Everything inside a package should be in the same location ' , [ ' location_id ' ] ) ,
2013-06-20 09:31:02 +00:00
]
2013-07-08 10:34:00 +00:00
def action_print ( self , cr , uid , ids , context = None ) :
if context is None :
context = { }
datas = {
' ids ' : context . get ( ' active_id ' ) and [ context . get ( ' active_id ' ) ] or ids ,
' model ' : ' stock.quant.package ' ,
' form ' : self . read ( cr , uid , ids ) [ 0 ]
}
return {
' type ' : ' ir.actions.report.xml ' ,
2013-07-08 10:40:45 +00:00
' report_name ' : ' stock.quant.package.barcode ' ,
2013-07-08 10:34:00 +00:00
' datas ' : datas
}
2013-08-01 14:04:01 +00:00
def unpack ( self , cr , uid , ids , context = None ) :
quant_obj = self . pool . get ( ' stock.quant ' )
for package in self . browse ( cr , uid , ids , context = context ) :
2013-08-02 08:00:07 +00:00
quant_ids = [ quant . id for quant in package . quant_ids ]
quant_obj . write ( cr , uid , quant_ids , { ' package_id ' : package . parent_id . id or False } , context = context )
children_package_ids = [ child_package . id for child_package in package . children_ids ]
self . write ( cr , uid , children_package_ids , { ' parent_id ' : package . parent_id . id or False } , context = context )
2013-08-01 14:04:01 +00:00
#delete current package since it contains nothing anymore
self . unlink ( cr , uid , ids , context = context )
2013-08-02 08:00:07 +00:00
return self . pool . get ( ' ir.actions.act_window ' ) . for_xml_id ( cr , uid , ' stock ' , ' action_package_view ' , context = context )
2013-08-01 14:04:01 +00:00
2013-08-02 08:00:07 +00:00
def get_content ( self , cr , uid , ids , context = None ) :
child_package_ids = self . search ( cr , uid , [ ( ' id ' , ' child_of ' , ids ) ] , context = context )
return self . pool . get ( ' stock.quant ' ) . search ( cr , uid , [ ( ' package_id ' , ' in ' , child_package_ids ) ] , context = context )
def get_content_package ( self , cr , uid , ids , context = None ) :
quants_ids = self . get_content ( cr , uid , ids , context = context )
res = self . pool . get ( ' ir.actions.act_window ' ) . for_xml_id ( cr , uid , ' stock ' , ' quantsact ' , context = context )
res [ ' domain ' ] = [ ( ' id ' , ' in ' , quants_ids ) ]
2013-06-28 15:40:36 +00:00
return res
2013-06-26 14:02:33 +00:00
2013-06-30 13:41:05 +00:00
def _get_product_total_qty ( self , cr , uid , package_record , product_id , context = None ) :
''' find the total of given product ' product_id ' inside the given package ' package_id '''
quant_obj = self . pool . get ( ' stock.quant ' )
2013-08-02 08:00:07 +00:00
all_quant_ids = self . get_content ( cr , uid , [ package_record . id ] , context = context )
2013-06-30 13:41:05 +00:00
total = 0
for quant in quant_obj . browse ( cr , uid , all_quant_ids , context = context ) :
if quant . product_id . id == product_id :
2013-07-24 17:30:39 +00:00
total + = quant . qty
2013-06-30 13:41:05 +00:00
return total
2013-06-26 13:10:04 +00:00
2013-06-20 09:31:02 +00:00
class stock_pack_operation ( osv . osv ) :
_name = " stock.pack.operation "
_description = " Packing Operation "
_columns = {
2013-07-23 15:34:24 +00:00
' picking_id ' : fields . many2one ( ' stock.picking ' , ' Stock Picking ' , help = ' The stock operation where the packing has been made ' , required = True ) ,
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , ondelete = " CASCADE " ) , # 1
' product_uom_id ' : fields . many2one ( ' product.uom ' , ' Product Unit of Measure ' ) ,
2013-06-20 09:31:02 +00:00
' product_qty ' : fields . float ( ' Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) , required = True ) ,
2013-06-24 09:41:50 +00:00
' package_id ' : fields . many2one ( ' stock.quant.package ' , ' Package ' ) , # 2
' quant_id ' : fields . many2one ( ' stock.quant ' , ' Quant ' ) , # 3
2013-08-06 07:47:30 +00:00
' lot_id ' : fields . many2one ( ' stock.production.lot ' , ' Lot/Serial Number ' ) ,
2013-06-29 22:17:03 +00:00
' result_package_id ' : fields . many2one ( ' stock.quant.package ' , ' Container Package ' , help = " If set, the operations are packed into this package " , required = False , ondelete = ' cascade ' ) ,
2013-07-23 15:34:24 +00:00
' date ' : fields . datetime ( ' Date ' , required = True ) ,
2013-08-30 13:25:07 +00:00
' owner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' , help = " Owner of the quants " ) ,
2013-07-23 15:34:24 +00:00
#'update_cost': fields.boolean('Need cost update'),
' cost ' : fields . float ( " Cost " , help = " Unit Cost for this product line " ) ,
' currency ' : fields . many2one ( ' res.currency ' , string = " Currency " , help = " Currency in which Unit cost is expressed " , ondelete = ' CASCADE ' ) ,
}
_defaults = {
' date ' : fields . date . context_today ,
2013-06-20 09:31:02 +00:00
}
2013-09-02 17:07:40 +00:00
def _get_domain ( self , cr , uid , ops , context = None ) :
'''
Gives domain for different
'''
res = [ ]
2013-08-26 10:32:24 +00:00
if ops . package_id :
2013-09-02 17:07:40 +00:00
res . append ( ( ' package_id ' , ' = ' , ops . package_id . id ) , )
2013-08-26 10:32:24 +00:00
if ops . lot_id :
2013-09-02 17:07:40 +00:00
res . append ( ( ' lot_id ' , ' = ' , ops . lot_id . id ) , )
if ops . owner_id :
res . append ( ( ' owner_id ' , ' = ' , ops . owner_id . id ) , )
2013-08-30 13:25:07 +00:00
else :
2013-09-02 17:07:40 +00:00
res . append ( ( ' owner_id ' , ' = ' , False ) , )
2013-08-22 09:48:13 +00:00
return res
2013-07-23 15:34:24 +00:00
#TODO: this function can be refactored
2013-06-24 15:40:18 +00:00
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
: param key : tuple directly reusable in a domain
2013-06-26 13:10:04 +00:00
context can receive a key ' current_package_id ' with the package to consider for this operation
2013-07-26 12:55:32 +00:00
returns True
previously : returns the update to do in stock . move one2many field of picking ( adapt remaining quantities ) and to the list of package in the classic one2many syntax
2013-06-26 13:10:04 +00:00
( 0 , 0 , { values } ) link to a new record that needs to be created with the given values dictionary
( 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 )
2013-06-24 15:40:18 +00:00
'''
2013-07-23 15:34:24 +00:00
quant_obj = self . pool . get ( ' stock.quant ' )
2013-06-26 13:10:04 +00:00
if context is None :
context = { }
2013-06-24 15:40:18 +00:00
2013-06-26 13:10:04 +00:00
#if current_package_id is given in the context, we increase the number of items in this package
2013-06-28 08:00:35 +00:00
package_clause = [ ( ' result_package_id ' , ' = ' , context . get ( ' current_package_id ' , False ) ) ]
2013-06-26 13:10:04 +00:00
existing_operation_ids = self . search ( cr , uid , [ ( ' picking_id ' , ' = ' , picking_id ) , key ] + package_clause , context = context )
2013-06-24 15:40:18 +00:00
if existing_operation_ids :
#existing operation found for the given key and picking => increment its quantity
2013-06-26 13:10:04 +00:00
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 )
2013-06-25 09:00:27 +00:00
else :
#no existing operation found for the given key and picking => create a new one
var_name , dummy , value = key
2013-07-23 15:34:24 +00:00
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
2013-06-26 13:10:04 +00:00
values = {
2013-06-25 09:00:27 +00:00
' picking_id ' : picking_id ,
var_name : value ,
2013-07-23 15:34:24 +00:00
' product_qty ' : 1 ,
' product_uom_id ' : uom_id ,
2013-06-26 13:10:04 +00:00
}
operation_id = self . create ( cr , uid , values , context = context )
values . update ( { ' id ' : operation_id } )
2013-07-26 12:55:32 +00:00
return True
2013-06-24 15:40:18 +00:00
2013-06-30 22:26:46 +00:00
class stock_warehouse_orderpoint ( osv . osv ) :
"""
Defines Minimum stock rules .
"""
_name = " stock.warehouse.orderpoint "
_description = " Minimum Inventory Rule "
def _get_draft_procurements ( self , cr , uid , ids , field_name , arg , context = None ) :
if context is None :
context = { }
result = { }
procurement_obj = self . pool . get ( ' procurement.order ' )
for orderpoint in self . browse ( cr , uid , ids , context = context ) :
2013-07-09 13:22:39 +00:00
procurement_ids = procurement_obj . search ( cr , uid , [ ( ' state ' , ' = ' , ' draft ' ) , ( ' product_id ' , ' = ' , orderpoint . product_id . id ) , ( ' location_id ' , ' = ' , orderpoint . location_id . id ) ] )
2013-06-30 22:26:46 +00:00
result [ orderpoint . id ] = procurement_ids
return result
def _check_product_uom ( self , cr , uid , ids , context = None ) :
'''
Check if the UoM has the same category as the product standard UoM
'''
if not context :
context = { }
2013-07-09 13:22:39 +00:00
2013-06-30 22:26:46 +00:00
for rule in self . browse ( cr , uid , ids , context = context ) :
if rule . product_id . uom_id . category_id . id != rule . product_uom . category_id . id :
return False
2013-07-09 13:22:39 +00:00
2013-06-30 22:26:46 +00:00
return True
_columns = {
' name ' : fields . char ( ' Name ' , size = 32 , required = True ) ,
' active ' : fields . boolean ( ' Active ' , help = " If the active field is set to False, it will allow you to hide the orderpoint without removing it. " ) ,
2013-07-09 13:22:39 +00:00
' logic ' : fields . selection ( [ ( ' max ' , ' Order to Max ' ) , ( ' price ' , ' Best price (not yet active!) ' ) ] , ' Reordering Mode ' , required = True ) ,
2013-06-30 22:26:46 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , required = True , ondelete = " cascade " ) ,
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True , ondelete = " cascade " ) ,
2013-07-09 13:22:39 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , ondelete = ' cascade ' , domain = [ ( ' type ' , ' != ' , ' service ' ) ] ) ,
2013-06-30 22:26:46 +00:00
' product_uom ' : fields . many2one ( ' product.uom ' , ' Product Unit of Measure ' , required = True ) ,
' product_min_qty ' : fields . float ( ' Minimum Quantity ' , required = True ,
help = " When the virtual stock goes below the Min Quantity specified for this field, OpenERP generates " \
" a procurement to bring the forecasted quantity to the Max Quantity. " ) ,
' product_max_qty ' : fields . float ( ' Maximum Quantity ' , required = True ,
help = " When the virtual stock goes below the Min Quantity, OpenERP generates " \
" a procurement to bring the forecasted quantity to the Quantity specified as Max Quantity. " ) ,
' qty_multiple ' : fields . integer ( ' Qty Multiple ' , required = True ,
help = " The procurement quantity will be rounded up to this multiple. " ) ,
' procurement_id ' : fields . many2one ( ' procurement.order ' , ' Latest procurement ' , ondelete = " set null " ) ,
2013-07-09 13:22:39 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
2013-06-30 22:26:46 +00:00
' procurement_draft_ids ' : fields . function ( _get_draft_procurements , type = ' many2many ' , relation = " procurement.order " , \
2013-07-09 13:22:39 +00:00
string = " Related Procurement Orders " , help = " Draft procurement of the product and location of that orderpoint " ) ,
2013-06-30 22:26:46 +00:00
}
_defaults = {
' active ' : lambda * a : 1 ,
' logic ' : lambda * a : ' max ' ,
' qty_multiple ' : lambda * a : 1 ,
2013-07-09 13:22:39 +00:00
' name ' : lambda self , cr , uid , context : self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.orderpoint ' ) or ' ' ,
' product_uom ' : lambda self , cr , uid , context : context . get ( ' product_uom ' , False ) ,
' company_id ' : lambda self , cr , uid , context : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.warehouse.orderpoint ' , context = context )
2013-06-30 22:26:46 +00:00
}
_sql_constraints = [
( ' qty_multiple_check ' , ' CHECK( qty_multiple > 0 ) ' , ' Qty Multiple must be greater than zero. ' ) ,
]
_constraints = [
( _check_product_uom , ' You have to select a product unit of measure in the same category than the default unit of measure of the product ' , [ ' product_id ' , ' product_uom ' ] ) ,
]
def default_get ( self , cr , uid , fields , context = None ) :
res = super ( stock_warehouse_orderpoint , self ) . default_get ( cr , uid , fields , context )
# default 'warehouse_id' and 'location_id'
if ' warehouse_id ' not in res :
warehouse = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , ' stock ' , ' warehouse0 ' , context )
res [ ' warehouse_id ' ] = warehouse . id
if ' location_id ' not in res :
warehouse = self . pool . get ( ' stock.warehouse ' ) . browse ( cr , uid , res [ ' warehouse_id ' ] , context )
res [ ' location_id ' ] = warehouse . lot_stock_id . id
return res
def onchange_warehouse_id ( self , cr , uid , ids , warehouse_id , context = None ) :
""" Finds location id for changed warehouse.
@param warehouse_id : Changed id of warehouse .
@return : Dictionary of values .
"""
if warehouse_id :
w = self . pool . get ( ' stock.warehouse ' ) . browse ( cr , uid , warehouse_id , context = context )
v = { ' location_id ' : w . lot_stock_id . id }
return { ' value ' : v }
return { }
def onchange_product_id ( self , cr , uid , ids , product_id , context = None ) :
""" Finds UoM for changed product.
@param product_id : Changed id of product .
@return : Dictionary of values .
"""
if product_id :
prod = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
d = { ' product_uom ' : [ ( ' category_id ' , ' = ' , prod . uom_id . category_id . id ) ] }
v = { ' product_uom ' : prod . uom_id . id }
return { ' value ' : v , ' domain ' : d }
return { ' domain ' : { ' product_uom ' : [ ] } }
def copy ( self , cr , uid , id , default = None , context = None ) :
if not default :
default = { }
default . update ( {
' name ' : self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.orderpoint ' ) or ' ' ,
} )
return super ( stock_warehouse_orderpoint , self ) . copy ( cr , uid , id , default , context = context )
class product_template ( osv . osv ) :
2013-07-09 13:22:39 +00:00
_inherit = " product.template "
2013-06-30 22:26:46 +00:00
_columns = {
2013-07-12 16:31:24 +00:00
' supply_method ' : fields . selection ( [ ( ' produce ' , ' Manufacture ' ) , ( ' buy ' , ' Buy ' ) , ( ' wait ' , ' None ' ) ] , ' Supply Method ' , required = True , help = " Manufacture: When procuring the product, a manufacturing order or a task will be generated, depending on the product type. \n Buy: When procuring the product, a purchase order will be generated. " ) ,
2013-06-30 22:26:46 +00:00
}
_defaults = {
' supply_method ' : ' buy ' ,
}
2013-07-23 09:44:36 +00:00
class stock_picking_type ( osv . osv ) :
2013-07-24 15:32:35 +00:00
_name = " stock.picking.type "
2013-07-26 15:40:20 +00:00
_description = " The picking type determines the picking view "
2013-07-28 12:42:01 +00:00
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.
This method performs obj . read_group ( cr , uid , domain , read_fields , groupby_field ) .
: param obj : the target model ( i . e . crm_lead )
: param domain : the domain applied to the read_group
: param list read_fields : the list of fields to read in the read_group
: param str value_field : the field used to compute the value of the bar slice
: param str groupby_field : the fields used to group
: return list section_result : a list of dicts : [
{ ' value ' : ( int ) bar_column_value ,
' tootip ' : ( str ) bar_column_tooltip ,
}
]
"""
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 ) ]
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 )
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 ' ) }
return section_result
def _get_picking_data ( 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 )
for id in ids :
created_domain = [
( ' picking_type_id ' , ' = ' , id ) ,
( ' state ' , ' not in ' , [ ' draft ' , ' cancel ' ] ) ,
( ' date ' , ' >= ' , groupby_begin ) ,
( ' date ' , ' < ' , groupby_end ) ,
]
2013-07-30 08:20:08 +00:00
res [ id ] = self . __get_bar_values ( cr , uid , obj , created_domain , [ ' date ' ] , ' picking_type_id_count ' , ' date ' , context = context )
2013-07-28 12:42:01 +00:00
return res
def _get_picking_count ( self , cr , uid , ids , field_names , arg , context = None ) :
obj = self . pool . get ( ' stock.picking ' )
domains = {
2013-07-28 17:01:14 +00:00
' count_picking_waiting ' : [ ( ' state ' , ' = ' , ' confirmed ' ) ] ,
' count_picking ' : [ ( ' state ' , ' = ' , ' assigned ' ) ] ,
2013-07-28 12:42:01 +00:00
' count_picking_late ' : [ ( ' min_date ' , ' < ' , time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ) ] ,
' count_picking_backorders ' : [ ( ' backorder_id ' , ' <> ' , False ) ] ,
}
result = { }
2013-07-28 13:16:47 +00:00
for field in domains :
2013-07-29 21:44:43 +00:00
data = obj . read_group ( cr , uid , domains [ field ] +
2013-07-28 12:42:01 +00:00
[ ( ' state ' , ' not in ' , ( ' done ' , ' cancel ' , ' draft ' ) ) , ( ' picking_type_id ' , ' in ' , ids ) ] ,
[ ' picking_type_id ' ] , [ ' picking_type_id ' ] , context = context )
2013-07-30 08:24:55 +00:00
count = dict ( map ( lambda x : ( x [ ' picking_type_id ' ] and x [ ' picking_type_id ' ] [ 0 ] , x [ ' picking_type_id_count ' ] ) , data ) )
2013-07-28 12:42:01 +00:00
for tid in ids :
result . setdefault ( tid , { } ) [ field ] = count . get ( tid , 0 )
2013-07-28 13:16:47 +00:00
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 ' ]
else :
result [ tid ] [ ' rate_picking_late ' ] = 0
result [ tid ] [ ' rate_picking_backorders ' ] = 0
2013-07-28 12:42:01 +00:00
return result
2013-08-06 07:00:46 +00:00
#TODO: not returning valus in required format to show in sparkline library,just added latest_picking_waiting need to add proper logic.
2013-07-28 12:42:01 +00:00
def _get_picking_history ( self , cr , uid , ids , field_names , arg , context = None ) :
obj = self . pool . get ( ' stock.picking ' )
result = { }
for id in ids :
result [ id ] = {
' latest_picking_late ' : [ ] ,
2013-08-06 06:23:14 +00:00
' latest_picking_backorders ' : [ ] ,
' latest_picking_waiting ' : [ ]
2013-07-28 12:42:01 +00:00
}
for type_id in ids :
pick_ids = obj . search ( cr , uid , [ ( ' state ' , ' = ' , ' done ' ) , ( ' picking_type_id ' , ' = ' , type_id ) ] , limit = 12 , order = " date desc " , context = context )
for pick in obj . browse ( cr , uid , pick_ids , context = context ) :
result [ type_id ] [ ' latest_picking_late ' ] = cmp ( pick . date [ : 10 ] , time . strftime ( ' % Y- % m- %d ' ) )
result [ type_id ] [ ' latest_picking_backorders ' ] = bool ( pick . backorder_id )
2013-08-06 06:23:14 +00:00
result [ type_id ] [ ' latest_picking_waiting ' ] = cmp ( pick . date [ : 10 ] , time . strftime ( ' % Y- % m- %d ' ) )
2013-07-28 12:42:01 +00:00
return result
2013-09-03 08:01:21 +00:00
def onchange_picking_code ( self , cr , uid , ids , picking_code = False ) :
if not picking_code :
return False
obj_data = self . pool . get ( ' ir.model.data ' )
stock_loc = obj_data . get_object_reference ( cr , uid , ' stock ' , ' stock_location_stock ' ) [ 1 ]
result = {
' default_location_src_id ' : stock_loc ,
' default_location_dest_id ' : stock_loc ,
}
if picking_code == ' incoming ' :
result [ ' default_location_src_id ' ] = obj_data . get_object_reference ( cr , uid , ' stock ' , ' stock_location_suppliers ' ) [ 1 ]
return { ' value ' : result }
if picking_code == ' outgoing ' :
result [ ' default_location_dest_id ' ] = obj_data . get_object_reference ( cr , uid , ' stock ' , ' stock_location_customers ' ) [ 1 ]
return { ' value ' : result }
else :
return { ' value ' : result }
def name_get ( self , cr , uid , ids , context = None ) :
""" Overides orm name_get method to display ' Warehouse_name: PickingType_name ' """
if not isinstance ( ids , list ) :
ids = [ ids ]
res = [ ]
if not ids :
return res
2013-09-04 15:01:37 +00:00
for record in self . browse ( cr , uid , ids , context = context ) :
2013-09-03 09:42:14 +00:00
name = record . name
if record . warehouse_id :
2013-09-04 15:01:37 +00:00
name = record . warehouse_id . name + ' : ' + name
2013-09-03 08:01:21 +00:00
res . append ( ( record . id , name ) )
return res
def _default_warehouse ( self , cr , uid , context = None ) :
user = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context )
res = self . pool . get ( ' stock.warehouse ' ) . search ( cr , uid , [ ( ' company_id ' , ' = ' , user . company_id . id ) ] , limit = 1 , context = context )
return res and res [ 0 ] or False
2013-07-23 09:44:36 +00:00
_columns = {
2013-08-03 23:47:59 +00:00
' name ' : fields . char ( ' name ' , translate = True , required = True ) ,
2013-09-03 08:01:21 +00:00
' pack ' : fields . boolean ( ' Prefill Pack Operations ' , help = ' This picking type needs packing interface ' ) ,
2013-09-05 07:23:15 +00:00
' auto_force_assign ' : fields . boolean ( ' Automatic Availability ' , help = ' This picking type does \' t need to check for the availability in source location. ' ) ,
2013-07-28 12:42:01 +00:00
' color ' : fields . integer ( ' Color Index ' ) ,
2013-07-23 16:47:55 +00:00
' delivery ' : fields . boolean ( ' Print delivery ' ) ,
2013-08-07 09:24:33 +00:00
' sequence_id ' : fields . many2one ( ' ir.sequence ' , ' Sequence ' , required = True ) ,
2013-07-26 15:40:20 +00:00
' default_location_src_id ' : fields . many2one ( ' stock.location ' , ' Default Source Location ' ) ,
2013-07-26 11:53:32 +00:00
' default_location_dest_id ' : fields . many2one ( ' stock.location ' , ' Default Destination Location ' ) ,
2013-09-03 08:01:21 +00:00
' code_id ' : fields . selection ( [ ( ' incoming ' , ' Suppliers ' ) , ( ' outgoing ' , ' Customers ' ) , ( ' internal ' , ' Internal ' ) ] , ' Picking type code ' , required = True ) ,
2013-08-07 09:24:33 +00:00
' return_picking_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Picking Type for Returns ' ) ,
2013-09-03 09:42:14 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' ) ,
2013-07-28 12:42:01 +00:00
# Statistics for the kanban view
' weekly_picking ' : fields . function ( _get_picking_data ,
2013-07-29 21:44:43 +00:00
type = ' string ' ,
2013-07-28 12:42:01 +00:00
string = ' Scheduled pickings per week ' ) ,
' count_picking ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
2013-07-28 17:01:14 +00:00
' count_picking_waiting ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
2013-07-28 12:42:01 +00:00
' count_picking_late ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
' count_picking_backorders ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
2013-07-28 13:16:47 +00:00
' rate_picking_late ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
' rate_picking_backorders ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
2013-07-28 12:42:01 +00:00
' latest_picking_late ' : fields . function ( _get_picking_history ,
type = ' string ' , multi = ' _get_picking_history ' ) ,
' latest_picking_backorders ' : fields . function ( _get_picking_history ,
type = ' string ' , multi = ' _get_picking_history ' ) ,
2013-08-06 06:23:14 +00:00
' latest_picking_waiting ' : fields . function ( _get_picking_history ,
type = ' string ' , multi = ' _get_picking_history ' ) ,
2013-07-28 12:42:01 +00:00
}
2013-09-03 08:01:21 +00:00
_defaults = {
' warehouse_id ' : _default_warehouse ,
}
2013-07-26 15:40:20 +00:00