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 _
2013-12-11 10:25:11 +00:00
from openerp . tools import DEFAULT_SERVER_DATETIME_FORMAT , DEFAULT_SERVER_DATE_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
2006-12-07 13:41:40 +00:00
2013-09-09 11:55:15 +00:00
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 '
2013-10-30 13:30:00 +00:00
_rec_name = ' complete_name '
2009-09-24 10:46:21 +00:00
2014-03-19 16:33:59 +00:00
def _location_owner ( self , cr , uid , location , context = None ) :
''' Return the company owning the location if any '''
return location and ( location . usage == ' internal ' ) and location . company_id or False
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) """
2013-09-30 10:17:28 +00:00
if context is None :
context = { }
2013-09-30 08:02:59 +00:00
context_with_inactive = context . copy ( )
2013-11-18 13:36:50 +00:00
context_with_inactive [ ' active_test ' ] = False
2013-09-30 08:02:59 +00:00
return self . search ( cr , uid , [ ( ' id ' , ' child_of ' , ids ) ] , context = context_with_inactive )
2009-02-11 13:32:54 +00:00
2014-02-14 14:57:12 +00:00
def _name_get ( self , cr , uid , location , context = None ) :
name = location . name
while location . location_id and location . usage != ' view ' :
location = location . location_id
name = location . name + ' / ' + name
2014-02-12 12:46:46 +00:00
return name
def name_get ( self , cr , uid , ids , context = None ) :
res = [ ]
for location in self . browse ( cr , uid , ids , context = context ) :
res . append ( ( location . id , self . _name_get ( cr , uid , location , context = context ) ) )
return res
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
2013-11-18 13:36:50 +00:00
""" , 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 " ,
2013-09-26 15:37:25 +00:00
store = { ' stock.location ' : ( _get_sublocations , [ ' name ' , ' location_id ' , ' active ' ] , 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 ' ) ,
2014-02-07 16:17:56 +00:00
' scrap_location ' : fields . boolean ( ' Is a Scrap Location? ' , help = ' Check this box to allow using this location to put scrapped/damaged goods. ' ) ,
2013-10-04 14:56:59 +00:00
' removal_strategy_ids ' : fields . one2many ( ' product.removal ' , ' location_id ' , ' Removal Strategies ' ) ,
' putaway_strategy_ids ' : fields . one2many ( ' product.putaway ' , ' location_id ' , ' Put Away Strategies ' ) ,
2014-03-07 16:37:47 +00:00
' loc_barcode ' : fields . char ( ' Location barcode ' ) ,
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
}
2014-03-07 16:37:47 +00:00
_sql_constraints = [ ( ' loc_barcode_company_uniq ' , ' unique (loc_barcode,company_id) ' , ' The barcode for a location must be unique per company ! ' ) ]
def create ( self , cr , uid , default , context = None ) :
if not default . get ( ' loc_barcode ' , False ) :
default . update ( { ' loc_barcode ' : default . get ( ' complete_name ' , False ) } )
return super ( stock_location , self ) . create ( cr , uid , default , context = context )
2013-11-18 13:36:50 +00:00
2013-10-04 14:56:59 +00:00
def get_putaway_strategy ( self , cr , uid , location , product , context = None ) :
pa = self . pool . get ( ' product.putaway ' )
categ = product . categ_id
categs = [ categ . id , False ]
while categ . parent_id :
categ = categ . parent_id
categs . append ( categ . id )
2013-11-18 13:36:50 +00:00
result = pa . search ( cr , uid , [ ( ' location_id ' , ' = ' , location . id ) , ( ' product_categ_id ' , ' in ' , categs ) ] , context = context )
2013-10-04 14:56:59 +00:00
if result :
return pa . browse ( cr , uid , result [ 0 ] , context = context )
2013-11-18 13:36:50 +00:00
2013-06-30 10:20:28 +00:00
def get_removal_strategy ( self , cr , uid , location , product , context = None ) :
2013-10-04 14:56:59 +00:00
pr = self . pool . get ( ' product.removal ' )
categ = product . categ_id
categs = [ categ . id , False ]
while categ . parent_id :
categ = categ . parent_id
categs . append ( categ . id )
2013-11-18 13:36:50 +00:00
result = pr . search ( cr , uid , [ ( ' location_id ' , ' = ' , location . id ) , ( ' product_categ_id ' , ' in ' , categs ) ] , context = context )
2013-10-04 14:56:59 +00:00
if result :
2013-10-07 13:04:47 +00:00
return pr . browse ( cr , uid , result [ 0 ] , context = context ) . method
2009-08-28 09:40:49 +00:00
2013-09-09 11:55:15 +00:00
#----------------------------------------------------------
# Routes
#----------------------------------------------------------
class stock_location_route ( osv . osv ) :
_name = ' stock.location.route '
_description = " Inventory Routes "
_order = ' sequence '
_columns = {
' name ' : fields . char ( ' Route Name ' , required = True ) ,
' sequence ' : fields . integer ( ' Sequence ' ) ,
' pull_ids ' : fields . one2many ( ' procurement.rule ' , ' route_id ' , ' Pull Rules ' ) ,
2013-10-04 14:56:59 +00:00
' active ' : fields . boolean ( ' Active ' , help = " If the active field is set to False, it will allow you to hide the route without removing it. " ) ,
' push_ids ' : fields . one2many ( ' stock.location.path ' , ' route_id ' , ' Push Rules ' ) ,
2013-11-05 16:15:17 +00:00
' product_selectable ' : fields . boolean ( ' Applicable on Product ' ) ,
' product_categ_selectable ' : fields . boolean ( ' Applicable on Product Category ' ) ,
' warehouse_selectable ' : fields . boolean ( ' Applicable on Warehouse ' ) ,
2013-10-16 10:03:18 +00:00
' supplied_wh_id ' : fields . many2one ( ' stock.warehouse ' , ' Supplied Warehouse ' ) ,
' supplier_wh_id ' : fields . many2one ( ' stock.warehouse ' , ' Supplier Warehouse ' ) ,
2014-01-27 10:01:42 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , select = 1 , help = ' Let this field empty if this route is shared between all companies ' ) ,
2013-09-09 11:55:15 +00:00
}
_defaults = {
2013-10-02 14:56:07 +00:00
' sequence ' : lambda self , cr , uid , ctx : 0 ,
2013-09-27 11:18:40 +00:00
' active ' : True ,
2013-10-04 14:56:59 +00:00
' product_selectable ' : True ,
2014-01-27 10:01:42 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.location.route ' , context = c ) ,
2013-09-09 11:55:15 +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 :
2013-11-18 13:36:50 +00:00
res [ q . id ] = q . lot_id . name
res [ q . id ] + = ' : ' + str ( q . qty ) + q . product_id . uom_id . name
2013-08-06 15:36:01 +00:00
return res
2013-12-05 18:10:52 +00:00
def _calc_inventory_value ( self , cr , uid , ids , name , attr , context = None ) :
res = { }
uid_company_id = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . company_id . id
for quant in self . browse ( cr , uid , ids , context = context ) :
context . pop ( ' force_company ' , None )
if quant . company_id . id != uid_company_id :
#if the company of the quant is different than the current user company, force the company in the context
#then re-do a browse to read the property fields for the good company.
context [ ' force_company ' ] = quant . company_id . id
quant = self . browse ( cr , uid , quant . id , context = context )
res [ quant . id ] = self . _get_inventory_value ( cr , uid , quant , context = context )
return res
def _get_inventory_value ( self , cr , uid , quant , context = None ) :
return quant . product_id . standard_price * quant . qty
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 ' ) ,
2014-03-14 13:59:38 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , ondelete = " restrict " ) ,
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True , ondelete = " restrict " ) ,
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-09-24 14:32:20 +00:00
' reservation_id ' : fields . many2one ( ' stock.move ' , ' Reserved for Move ' , help = " The move the quant is reserved for " ) ,
2013-07-24 15:16:11 +00:00
' 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 ' ) ,
2014-02-06 14:57:41 +00:00
' negative_move_id ' : fields . many2one ( ' stock.move ' , ' Move Negative Quant ' , help = ' If this is a negative quant, this will be the move that caused this negative quant. ' ) ,
' negative_dest_location_id ' : fields . related ( ' negative_move_id ' , ' location_dest_id ' , type = ' many2one ' , relation = ' stock.location ' , string = " Negative Destination Location " ,
2014-03-12 14:30:30 +00:00
help = " Technical field used to record the destination location of a move that created a negative quant " ) ,
2013-12-05 18:10:52 +00:00
' inventory_value ' : fields . function ( _calc_inventory_value , string = " Inventory Value " , type = ' float ' , readonly = True ) ,
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-06-29 22:17:03 +00:00
}
2013-07-24 15:16:11 +00:00
2013-12-05 18:10:52 +00:00
def read_group ( self , cr , uid , domain , fields , groupby , offset = 0 , limit = None , context = None , orderby = False ) :
''' Overwrite the read_group in order to sum the function field ' inventory_value ' in group by '''
res = super ( stock_quant , self ) . read_group ( cr , uid , domain , fields , groupby , offset = offset , limit = limit , context = context , orderby = orderby )
if ' inventory_value ' in fields :
for line in res :
if ' __domain ' in line :
lines = self . search ( cr , uid , line [ ' __domain ' ] , context = context )
inv_value = 0.0
for line2 in self . browse ( cr , uid , lines , context = context ) :
inv_value + = line2 . inventory_value
line [ ' inventory_value ' ] = inv_value
return res
2013-11-08 16:52:17 +00:00
def action_view_quant_history ( self , cr , uid , ids , context = None ) :
'''
This function returns an action that display the history of the quant , which
mean all the stock moves that lead to this quant creation with this quant quantity .
'''
mod_obj = self . pool . get ( ' ir.model.data ' )
act_obj = self . pool . get ( ' ir.actions.act_window ' )
result = mod_obj . get_object_reference ( cr , uid , ' stock ' , ' action_move_form2 ' )
id = result and result [ 1 ] or False
result = act_obj . read ( cr , uid , [ id ] , context = { } ) [ 0 ]
move_ids = [ ]
for quant in self . browse ( cr , uid , ids , context = context ) :
move_ids + = [ move . id for move in quant . history_ids ]
2013-11-18 13:36:50 +00:00
2013-11-08 16:52:17 +00:00
result [ ' domain ' ] = " [( ' id ' , ' in ' ,[ " + ' , ' . join ( map ( str , move_ids ) ) + " ])] "
return result
2013-11-19 15:33:00 +00:00
def quants_reserve ( self , cr , uid , quants , move , link = False , context = None ) :
2013-11-19 16:15:54 +00:00
''' This function reserves quants for the given move (and optionally given link). If the total of quantity reserved is enough, the move ' s state
is also set to ' assigned '
2014-02-05 10:43:24 +00:00
: param quants : list of tuple ( quant browse record or None , qty to reserve ) . If None is given as first tuple element , the item will be ignored . Negative quants should not be received as argument
2013-11-15 17:55:05 +00:00
: param move : browse record
2013-11-19 15:33:00 +00:00
: param link : browse record ( stock . move . operation . link )
2013-11-15 17:55:05 +00:00
'''
2013-06-29 22:17:03 +00:00
toreserve = [ ]
2014-03-13 17:06:20 +00:00
reserved_availability = move . reserved_availability
2013-11-15 17:55:05 +00:00
#split quants if needed
for quant , qty in quants :
2014-02-05 12:51:07 +00:00
if qty < = 0.0 or ( quant and quant . qty < = 0.0 ) :
2014-02-05 10:43:24 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' You can not reserve a negative quantity or a negative quant. ' ) )
2013-11-15 17:55:05 +00:00
if not quant :
continue
2013-06-29 22:17:03 +00:00
self . _quant_split ( cr , uid , quant , qty , context = context )
toreserve . append ( quant . id )
2014-03-07 10:33:12 +00:00
reserved_availability + = quant . qty
2013-11-15 17:55:05 +00:00
#reserve quants
if toreserve :
2014-03-25 15:06:38 +00:00
self . write ( cr , SUPERUSER_ID , toreserve , { ' reservation_id ' : move . id } , context = context )
2014-04-01 09:31:27 +00:00
#if move has a picking_id, write on that picking that pack_operation might have changed and need to be recomputed
2014-03-28 10:49:06 +00:00
if move . picking_id :
2014-04-01 09:31:27 +00:00
self . pool . get ( ' stock.picking ' ) . write ( cr , uid , [ move . picking_id . id ] , { ' recompute_pack_op ' : True } , context = context )
2013-11-15 17:55:05 +00:00
#check if move'state needs to be set as 'assigned'
2014-03-07 10:33:12 +00:00
if reserved_availability == move . product_qty and move . state in ( ' confirmed ' , ' waiting ' ) :
2013-11-15 17:55:05 +00:00
self . pool . get ( ' stock.move ' ) . write ( cr , uid , [ move . id ] , { ' state ' : ' assigned ' } , context = context )
2014-03-07 10:33:12 +00:00
elif reserved_availability > 0 and not move . partially_available :
2014-02-26 12:00:51 +00:00
self . pool . get ( ' stock.move ' ) . write ( cr , uid , [ move . id ] , { ' partially_available ' : True } , context = context )
2013-11-15 17:55:05 +00:00
2014-03-11 13:31:09 +00:00
def quants_move ( self , cr , uid , quants , move , location_to , lot_id = False , owner_id = False , src_package_id = False , dest_package_id = False , context = None ) :
""" Moves all given stock.quant in the given destination location.
2013-11-18 13:36:50 +00:00
: param quants : list of tuple ( browse record ( stock . quant ) or None , quantity to move )
: param move : browse record ( stock . move )
2014-03-11 13:31:09 +00:00
: param location_to : browse record ( stock . location ) depicting where the quants have to be moved
2014-03-03 17:31:47 +00:00
: param lot_id : ID of the lot that must be set on the quants to move
2013-11-18 13:36:50 +00:00
: param owner_id : ID of the partner that must own the quants to move
2013-11-19 15:33:00 +00:00
: param src_package_id : ID of the package that contains the quants to move
: param dest_package_id : ID of the package that must be set on the moved quant
2013-11-18 13:36:50 +00:00
"""
2014-03-12 10:19:17 +00:00
quants_reconcile = [ ]
2014-03-19 16:33:59 +00:00
to_move_quants = [ ]
2014-03-13 09:57:46 +00:00
self . _check_location ( cr , uid , location_to , context = context )
2013-07-11 13:05:28 +00:00
for quant , qty in quants :
2013-11-18 13:36:50 +00:00
if not quant :
#If quant is None, we will create a quant to move (and potentially a negative counterpart too)
2014-03-19 16:33:59 +00:00
quant = self . _quant_create ( cr , uid , qty , move , lot_id = lot_id , owner_id = owner_id , src_package_id = src_package_id , dest_package_id = dest_package_id , force_location = location_to , context = context )
2014-03-12 10:19:17 +00:00
else :
2014-03-19 16:33:59 +00:00
self . _quant_split ( cr , uid , quant , qty , context = context )
quant . refresh ( )
to_move_quants . append ( quant )
2014-03-12 10:19:17 +00:00
quants_reconcile . append ( quant )
2014-03-19 16:33:59 +00:00
if to_move_quants :
self . move_quants_write ( cr , uid , to_move_quants , move , location_to , dest_package_id , context = context )
if location_to . usage == ' internal ' :
if self . search ( cr , uid , [ ( ' product_id ' , ' = ' , move . product_id . id ) , ( ' qty ' , ' < ' , 0 ) ] , limit = 1 , context = context ) :
for quant in quants_reconcile :
quant . refresh ( )
self . _quant_reconcile_negative ( cr , uid , quant , move , context = context )
def move_quants_write ( self , cr , uid , quants , move , location_dest_id , dest_package_id , context = None ) :
vals = { ' location_id ' : location_dest_id . id ,
' history_ids ' : [ ( 4 , move . id ) ] ,
' package_id ' : dest_package_id }
self . write ( cr , SUPERUSER_ID , [ q . id for q in quants ] , vals , context = context )
2013-11-18 13:36:50 +00:00
2013-10-11 14:28:27 +00:00
def quants_get_prefered_domain ( self , cr , uid , location , product , qty , domain = None , prefered_domain = False , fallback_domain = False , restrict_lot_id = False , restrict_partner_id = False , context = None ) :
2013-10-29 16:09:51 +00:00
''' This function tries to find quants in the given location for the given domain, by trying to first limit
the choice on the quants that match the prefered_domain as well . But if the qty requested is not reached
it tries to find the remaining quantity by using the fallback_domain .
'''
2014-03-19 16:33:59 +00:00
#don't look for quants in location that are of type production, supplier or inventory.
2014-03-05 15:48:36 +00:00
if location . usage in [ ' inventory ' , ' production ' , ' supplier ' ] :
return [ ( None , qty ) ]
2013-10-11 14:28:27 +00:00
if prefered_domain and fallback_domain :
if domain is None :
domain = [ ]
2013-10-29 16:09:51 +00:00
quants = self . quants_get ( cr , uid , location , product , qty , domain = domain + prefered_domain , restrict_lot_id = restrict_lot_id , restrict_partner_id = restrict_partner_id , context = context )
2013-10-11 14:28:27 +00:00
res_qty = qty
2013-10-29 16:09:51 +00:00
quant_ids = [ ]
2013-10-11 14:28:27 +00:00
for quant in quants :
if quant [ 0 ] :
2013-10-29 16:09:51 +00:00
quant_ids . append ( quant [ 0 ] . id )
2013-10-11 14:28:27 +00:00
res_qty - = quant [ 1 ]
if res_qty > 0 :
#try to replace the last tuple (None, res_qty) with something that wasn't chosen at first because of the prefered order
quants . pop ( )
2013-10-29 16:09:51 +00:00
#make sure the quants aren't found twice (if the prefered_domain and the fallback_domain aren't orthogonal
domain + = [ ( ' id ' , ' not in ' , quant_ids ) ]
unprefered_quants = self . quants_get ( cr , uid , location , product , res_qty , domain = domain + fallback_domain , restrict_lot_id = restrict_lot_id , restrict_partner_id = restrict_partner_id , context = context )
2013-10-11 14:28:27 +00:00
for quant in unprefered_quants :
quants . append ( quant )
return quants
return self . quants_get ( cr , uid , location , product , qty , domain = domain , restrict_lot_id = restrict_lot_id , restrict_partner_id = restrict_partner_id , context = context )
def quants_get ( self , cr , uid , location , product , qty , domain = None , restrict_lot_id = False , restrict_partner_id = False , 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-11-15 17:55:05 +00:00
: location : browse record of the parent location where the quants have to be found
2013-07-23 15:34:24 +00:00
: product : browse record of the product to find
2013-06-29 22:17:03 +00:00
: qty in UoM of product
2013-06-27 23:30:58 +00:00
"""
2013-07-17 13:26:27 +00:00
result = [ ]
2013-09-16 07:52:08 +00:00
domain = domain or [ ( ' qty ' , ' > ' , 0.0 ) ]
if restrict_partner_id :
domain + = [ ( ' owner_id ' , ' = ' , restrict_partner_id ) ]
if restrict_lot_id :
domain + = [ ( ' lot_id ' , ' = ' , restrict_lot_id ) ]
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 '
2013-09-16 07:52:08 +00:00
if removal_strategy == ' fifo ' :
2013-10-11 14:28:27 +00:00
result + = self . _quants_get_fifo ( cr , uid , location , product , qty , domain , context = context )
2013-09-16 07:52:08 +00:00
elif removal_strategy == ' lifo ' :
2013-10-11 14:28:27 +00:00
result + = self . _quants_get_lifo ( cr , uid , location , product , qty , domain , 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-06-29 22:17:03 +00:00
return result
2013-06-19 14:54:06 +00:00
2013-11-19 15:33:00 +00:00
def _quant_create ( self , cr , uid , qty , move , lot_id = False , owner_id = False , src_package_id = False , dest_package_id = False , force_location = False , context = None ) :
2013-10-29 16:09:51 +00:00
''' Create a quant in the destination location and create a negative quant in the source location if it ' s an internal location.
'''
2013-09-13 06:42:03 +00:00
if context is None :
context = { }
2013-09-03 16:27:57 +00:00
price_unit = self . pool . get ( ' stock.move ' ) . get_price_unit ( cr , uid , move , context = context )
2013-09-13 06:42:03 +00:00
location = force_location or move . location_dest_id
2013-07-17 13:26:27 +00:00
vals = {
' product_id ' : move . product_id . id ,
2013-09-13 06:42:03 +00:00
' location_id ' : location . id ,
2013-07-17 13:26:27 +00:00
' qty ' : qty ,
' cost ' : price_unit ,
' history_ids ' : [ ( 4 , move . id ) ] ,
2013-12-11 10:25:11 +00:00
' in_date ' : datetime . now ( ) . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) ,
2013-07-17 13:26:27 +00:00
' company_id ' : move . company_id . id ,
2013-09-03 14:28:47 +00:00
' lot_id ' : lot_id ,
2013-09-13 06:42:03 +00:00
' owner_id ' : owner_id ,
2013-11-19 15:33:00 +00:00
' package_id ' : dest_package_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
2014-01-31 10:42:06 +00:00
negative_vals [ ' negative_move_id ' ] = move . id
2013-11-19 15:33:00 +00:00
negative_vals [ ' package_id ' ] = src_package_id
2013-10-11 14:25:32 +00:00
negative_quant_id = self . create ( cr , SUPERUSER_ID , 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
2013-10-11 08:55:59 +00:00
#create the quant as superuser, because we want to restrict the creation of quant manually: they should always use this method to create quants
quant_id = self . create ( cr , SUPERUSER_ID , vals , context = context )
2013-07-17 13:26:27 +00:00
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-10-11 14:25:32 +00:00
new_quant = self . copy ( cr , SUPERUSER_ID , quant . id , default = { ' qty ' : quant . qty - qty } , context = context )
2013-10-14 15:17:21 +00:00
self . write ( cr , SUPERUSER_ID , quant . id , { ' qty ' : qty } , context = context )
2013-06-29 22:17:03 +00:00
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-09-23 14:33:09 +00:00
def _quants_merge ( self , cr , uid , solved_quant_ids , solving_quant , context = None ) :
path = [ ]
for move in solving_quant . history_ids :
path . append ( ( 4 , move . id ) )
2013-10-14 15:17:21 +00:00
self . write ( cr , SUPERUSER_ID , solved_quant_ids , { ' history_ids ' : path } , context = context )
2013-09-09 10:02:29 +00:00
2014-01-31 10:42:06 +00:00
def _quant_reconcile_negative ( self , cr , uid , quant , move , 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 .
"""
2013-09-18 13:04:58 +00:00
solving_quant = quant
2013-09-25 13:34:42 +00:00
dom = [ ( ' qty ' , ' < ' , 0 ) ]
2014-01-31 10:42:06 +00:00
if quant . lot_id :
dom + = [ ( ' lot_id ' , ' = ' , quant . lot_id . id ) ]
2014-02-04 10:32:09 +00:00
dom + = [ ( ' owner_id ' , ' = ' , quant . owner_id . id ) ]
dom + = [ ( ' package_id ' , ' = ' , quant . package_id . id ) ]
2014-01-31 10:42:06 +00:00
if move . move_dest_id :
dom + = [ ( ' negative_move_id ' , ' = ' , move . move_dest_id . id ) ]
quants = self . quants_get ( cr , uid , quant . location_id , quant . product_id , quant . qty , dom , context = context )
2013-06-30 20:13:23 +00:00
for quant_neg , qty in quants :
2013-07-17 13:26:27 +00:00
if not quant_neg :
continue
2013-09-23 14:33:09 +00:00
to_solve_quant_ids = self . search ( cr , uid , [ ( ' propagated_from_id ' , ' = ' , quant_neg . id ) ] , context = context )
if not to_solve_quant_ids :
2013-07-17 13:26:27 +00:00
continue
2013-09-23 14:33:09 +00:00
solving_qty = qty
solved_quant_ids = [ ]
for to_solve_quant in self . browse ( cr , uid , to_solve_quant_ids , context = context ) :
if solving_qty < = 0 :
continue
solved_quant_ids . append ( to_solve_quant . id )
self . _quant_split ( cr , uid , to_solve_quant , min ( solving_qty , to_solve_quant . qty ) , context = context )
solving_qty - = min ( solving_qty , to_solve_quant . qty )
remaining_solving_quant = self . _quant_split ( cr , uid , solving_quant , qty , context = context )
2013-07-17 13:26:27 +00:00
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
2013-09-23 14:33:09 +00:00
if remaining_neg_quant :
remaining_to_solve_quant_ids = self . search ( cr , uid , [ ( ' propagated_from_id ' , ' = ' , quant_neg . id ) , ( ' id ' , ' not in ' , solved_quant_ids ) ] , context = context )
if remaining_to_solve_quant_ids :
2013-10-14 15:17:21 +00:00
self . write ( cr , SUPERUSER_ID , remaining_to_solve_quant_ids , { ' propagated_from_id ' : remaining_neg_quant . id } , context = context )
2013-09-24 12:49:06 +00:00
#delete the reconciled quants, as it is replaced by the solved quants
self . unlink ( cr , SUPERUSER_ID , [ quant_neg . id ] , context = context )
2013-09-24 08:28:04 +00:00
#price update + accounting entries adjustments
2013-09-24 12:49:06 +00:00
self . _price_update ( cr , uid , solved_quant_ids , solving_quant . cost , context = context )
#merge history (and cost?)
self . _quants_merge ( cr , uid , solved_quant_ids , solving_quant , context = context )
self . unlink ( cr , SUPERUSER_ID , [ solving_quant . id ] , context = context )
2013-09-23 14:33:09 +00:00
solving_quant = remaining_solving_quant
def _price_update ( self , cr , uid , ids , newprice , context = None ) :
2013-10-14 15:38:22 +00:00
self . write ( cr , SUPERUSER_ID , ids , { ' cost ' : newprice } , context = context )
2013-06-21 16:23:48 +00:00
2013-07-16 10:15:40 +00:00
def quants_unreserve ( self , cr , uid , move , context = None ) :
2013-08-29 13:02:07 +00:00
related_quants = [ x . id for x in move . reserved_quant_ids ]
2014-03-26 08:56:57 +00:00
if related_quants :
2014-04-01 09:31:27 +00:00
#if move has a picking_id, write on that picking that pack_operation might have changed and need to be recomputed
2014-03-28 10:49:06 +00:00
if move . picking_id :
2014-04-01 09:31:27 +00:00
self . pool . get ( ' stock.picking ' ) . write ( cr , uid , [ move . picking_id . id ] , { ' recompute_pack_op ' : True } , context = context )
2014-03-26 08:56:57 +00:00
if move . partially_available :
self . pool . get ( " stock.move " ) . write ( cr , uid , [ move . id ] , { ' partially_available ' : False } , context = context )
return self . write ( cr , SUPERUSER_ID , related_quants , { ' reservation_id ' : False } , context = context )
2013-07-16 10:15:40 +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-10-29 16:09:51 +00:00
''' Implementation of removal strategies
If it can not reserve , it will return a tuple ( None , qty )
'''
2014-03-12 13:47:51 +00:00
if context is None :
context = { }
2013-06-30 20:13:23 +00:00
domain + = location and [ ( ' location_id ' , ' child_of ' , location . id ) ] or [ ]
2014-03-12 13:47:51 +00:00
domain + = [ ( ' product_id ' , ' = ' , product . id ) ]
if context . get ( ' force_company ' ) :
domain + = [ ( ' company_id ' , ' = ' , context . get ( ' force_company ' ) ) ]
else :
domain + = [ ( ' company_id ' , ' = ' , self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . company_id . id ) ]
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-10-11 14:28:27 +00:00
def _quants_get_fifo ( self , cr , uid , location , product , quantity , domain = [ ] , context = None ) :
2014-03-18 12:27:26 +00:00
order = ' in_date, id '
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-10-11 14:28:27 +00:00
def _quants_get_lifo ( self , cr , uid , location , product , quantity , domain = [ ] , context = None ) :
2014-03-18 12:27:26 +00:00
order = ' in_date desc, id 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
2014-02-24 15:10:30 +00:00
def _check_location ( self , cr , uid , location , context = None ) :
if location . usage == ' view ' :
raise osv . except_osv ( _ ( ' Error ' ) , _ ( ' You cannot move to a location of type view %s . ' ) % ( location . name ) )
2013-07-29 21:44:43 +00:00
return True
2014-03-14 13:59:38 +00:00
def _check_open_inventory_location ( self , cr , uid , ids , context = None ) :
""" Check if there is an inventory running for the locations implied
"""
inventory_obj = self . pool . get ( ' stock.inventory ' )
for quant in self . browse ( cr , uid , ids , context = context ) :
loc = quant . location_id
parent_location_ids = [ ]
while loc :
parent_location_ids . append ( loc . id )
loc = loc . location_id
domain = [ ( ' state ' , ' = ' , ' confirm ' ) , ( ' location_id ' , ' in ' , parent_location_ids ) , ' | ' , ( ' product_id ' , ' = ' , False ) , ( ' product_id ' , ' = ' , quant . product_id . id ) ]
domain + = [ ' | ' , ( ' lot_id ' , ' = ' , False ) , ( ' lot_id ' , ' = ' , quant . lot_id . id ) ]
domain + = [ ' | ' , ( ' partner_id ' , ' = ' , False ) , ( ' partner_id ' , ' = ' , quant . owner_id . id ) ]
domain + = [ ' | ' , ( ' package_id ' , ' = ' , False ) , ( ' package_id ' , ' = ' , quant . package_id . id ) ]
inventory_ids = inventory_obj . search ( cr , uid , domain , context = context )
if inventory_ids :
inventory = inventory_obj . browse ( cr , uid , inventory_ids [ 0 ] , context = context )
raise osv . except_osv ( _ ( ' Error! Location on inventory ' ) ,
_ ( ' There exists an inventory conflicting with your operation : \n %s ' ) % inventory . name )
return True
2013-07-29 21:44:43 +00:00
_constraints = [
2014-03-14 13:59:38 +00:00
( _check_open_inventory_location ,
" A Physical Inventory is being conducted at this location " , [ ' location_id ' ] ) ,
2013-07-29 21:44:43 +00:00
]
2014-03-14 13:59:38 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
#check the inventory constraint before the write
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2014-03-17 10:59:15 +00:00
if ' owner_id ' in vals or ' lot_id ' in vals or ' package_id ' in vals or ' location_id ' in vals or ' product_id ' in vals :
self . _check_open_inventory_location ( cr , uid , ids , context = context )
2014-03-14 13:59:38 +00:00
return super ( stock_quant , self ) . write ( cr , uid , ids , vals , context = context )
2013-07-29 21:44:43 +00:00
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 "
2014-02-11 15:51:57 +00:00
_order = " priority desc, date asc, id desc "
2013-10-08 12:36:59 +00:00
def _set_min_date ( self , cr , uid , id , field , value , arg , context = None ) :
move_obj = self . pool . get ( " stock.move " )
2013-10-15 09:28:17 +00:00
if value :
move_ids = [ move . id for move in self . browse ( cr , uid , id , context = context ) . move_lines ]
move_obj . write ( cr , uid , move_ids , { ' date_expected ' : value } , context = context )
2013-10-08 12:36:59 +00:00
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
2013-11-18 13:36:50 +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 18:09:34 +00:00
def _state_get ( self , cr , uid , ids , field_name , arg , context = None ) :
2013-10-29 16:09:51 +00:00
''' The state of a picking depends on the state of its related stock.move
draft : the picking has no line or any one of the lines is draft
done , draft , cancel : all lines are done / draft / cancel
confirmed , auto , assigned depends on move_type ( all at once or direct )
'''
2013-07-17 15:29:03 +00:00
res = { }
for pick in self . browse ( cr , uid , ids , context = context ) :
2014-03-19 16:33:59 +00:00
if ( not pick . move_lines ) or any ( [ x . state == ' draft ' for x in pick . move_lines ] ) :
2013-07-28 18:09:34 +00:00
res [ pick . id ] = ' draft '
continue
2014-03-19 16:33:59 +00:00
if all ( [ x . state == ' cancel ' for x in pick . move_lines ] ) :
2013-07-28 18:09:34 +00:00
res [ pick . id ] = ' cancel '
continue
2014-03-19 16:33:59 +00:00
if all ( [ x . state in ( ' cancel ' , ' done ' ) for x in pick . move_lines ] ) :
2013-07-28 18:09:34 +00:00
res [ pick . id ] = ' done '
continue
2013-11-18 13:36:50 +00:00
order = { ' confirmed ' : 0 , ' waiting ' : 1 , ' assigned ' : 2 }
2014-02-03 09:31:15 +00:00
order_inv = { 0 : ' confirmed ' , 1 : ' waiting ' , 2 : ' assigned ' }
2014-03-19 16:33:59 +00:00
lst = [ order [ x . state ] for x in pick . move_lines if x . state not in ( ' cancel ' , ' done ' ) ]
2014-01-28 10:41:52 +00:00
if pick . move_type == ' 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 :
2014-02-03 09:31:15 +00:00
#we are in the case of partial delivery, so if all move are assigned, picking
#should be assign too, else if one of the move is assigned, or partially available, picking should be
2014-01-28 10:41:52 +00:00
#in partially available state, otherwise, picking is in waiting or confirmed state
2013-07-28 18:09:34 +00:00
res [ pick . id ] = order_inv [ max ( lst ) ]
2014-01-28 10:41:52 +00:00
if not all ( x == 2 for x in lst ) :
2014-02-03 09:31:15 +00:00
#if all moves aren't assigned, check if we have one product partially available
2014-03-19 16:33:59 +00:00
for move in pick . move_lines :
if move . partially_available :
2014-02-03 09:31:15 +00:00
res [ pick . id ] = ' partially_available '
2014-02-26 13:20:21 +00:00
break
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
2013-10-01 08:28:19 +00:00
def _get_quant_reserved_exist ( self , cr , uid , ids , field_name , arg , context = None ) :
res = { }
2013-11-18 13:36:50 +00:00
for pick in self . browse ( cr , uid , ids , context = context ) :
2013-10-01 08:28:19 +00:00
res [ pick . id ] = False
for move in pick . move_lines :
2013-10-01 09:09:14 +00:00
if move . reserved_quant_ids :
2013-10-08 12:36:59 +00:00
res [ pick . id ] = True
2013-11-18 13:36:50 +00:00
continue
2013-10-01 08:28:19 +00:00
return res
2013-09-12 16:05:46 +00:00
2014-04-01 09:31:27 +00:00
def check_group_lot ( self , cr , uid , context = None ) :
""" This function will return true if we have the setting to use lots activated. """
2014-03-31 09:40:20 +00:00
settings_obj = self . pool . get ( ' stock.config.settings ' )
config_ids = settings_obj . search ( cr , uid , [ ] , limit = 1 , order = ' id DESC ' , context = context )
#If we don't have updated config until now, all fields are by default false and so should be not dipslayed
if not config_ids :
return False
2014-04-01 09:31:27 +00:00
stock_settings = settings_obj . browse ( cr , uid , config_ids [ 0 ] , context = context )
return stock_settings . group_stock_production_lot
2014-03-31 09:40:20 +00:00
2014-04-01 09:31:27 +00:00
def check_group_pack ( self , cr , uid , context = None ) :
""" This function will return true if we have the setting to use package activated. """
settings_obj = self . pool . get ( ' stock.config.settings ' )
config_ids = settings_obj . search ( cr , uid , [ ] , limit = 1 , order = ' id DESC ' , context = context )
#If we don't have updated config until now, all fields are by default false and so should be not dipslayed
if not config_ids :
return False
2014-03-31 09:40:20 +00:00
stock_settings = settings_obj . browse ( cr , uid , config_ids [ 0 ] , context = context )
2014-04-01 09:31:27 +00:00
return stock_settings . group_stock_tracking_lot
2014-03-31 09:40:20 +00:00
2013-10-11 16:04:23 +00:00
def action_assign_owner ( self , cr , uid , ids , context = None ) :
2013-10-17 16:07:25 +00:00
for picking in self . browse ( cr , uid , ids , context = context ) :
2013-11-18 13:36:50 +00:00
packop_ids = [ op . id for op in picking . pack_operation_ids ]
2013-10-17 16:07:25 +00:00
self . pool . get ( ' stock.pack.operation ' ) . write ( cr , uid , packop_ids , { ' owner_id ' : picking . owner_id . id } , context = context )
2013-09-12 16:05:46 +00:00
2008-07-22 15:11:28 +00:00
_columns = {
2013-11-18 13:36:50 +00:00
' name ' : fields . char ( ' Reference ' , size = 64 , select = True , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
' origin ' : fields . char ( ' Source Document ' , size = 64 , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , help = " Reference of the document " , select = True ) ,
' backorder_id ' : fields . many2one ( ' stock.picking ' , ' Back Order of ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , help = " If this shipment was split, then this field links to the shipment which contains the already processed part. " , select = True ) ,
' note ' : fields . text ( ' Notes ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
' move_type ' : fields . selection ( [ ( ' direct ' , ' Partial ' ) , ( ' one ' , ' All at once ' ) ] , ' Delivery Method ' , required = True , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , help = " It specifies goods to be deliver partially or all at once " ) ,
' state ' : fields . function ( _state_get , type = " selection " , store = {
2014-02-24 11:04:13 +00:00
' stock.picking ' : ( lambda self , cr , uid , ids , ctx : ids , [ ' move_type ' ] , 20 ) ,
2014-02-26 12:00:51 +00:00
' stock.move ' : ( _get_pickings , [ ' state ' , ' picking_id ' , ' partially_available ' ] , 20 ) } , selection = [
2013-11-18 13:36:50 +00:00
( ' draft ' , ' Draft ' ) ,
( ' cancel ' , ' Cancelled ' ) ,
( ' waiting ' , ' Waiting Another Operation ' ) ,
( ' confirmed ' , ' Waiting Availability ' ) ,
2014-01-28 10:41:52 +00:00
( ' partially_available ' , ' Partially Available ' ) ,
2013-11-18 13:36:50 +00:00
( ' assigned ' , ' Ready to Transfer ' ) ,
( ' done ' , ' Transferred ' ) ,
] , string = ' Status ' , readonly = True , select = True , track_visibility = ' onchange ' , help = """
* Draft : not confirmed yet and will not be scheduled until confirmed \n
* Waiting Another Operation : waiting for another move to proceed before it becomes automatically available ( e . g . in Make - To - Order flows ) \n
* Waiting Availability : still waiting for the availability of products \n
2014-01-28 10:41:52 +00:00
* Partially Available : some products are available and reserved \n
2013-11-18 13:36:50 +00:00
* 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 " " "
2012-05-04 15:36:13 +00:00
) ,
2014-01-31 10:28:01 +00:00
' priority ' : fields . selection ( [ ( ' 0 ' , ' Low ' ) , ( ' 1 ' , ' Normal ' ) , ( ' 2 ' , ' High ' ) ] , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , string = ' Priority ' , required = True ) ,
2013-10-08 12:36:59 +00:00
' min_date ' : fields . function ( get_min_max_date , multi = " min_max_date " , fnct_inv = _set_min_date ,
2014-02-26 14:12:23 +00:00
store = { ' stock.move ' : ( _get_pickings , [ ' date_expected ' ] , 20 ) } , type = ' datetime ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , string = ' Scheduled Date ' , select = 1 , help = " Scheduled time for the first part of the shipment to be processed. Setting manually a value here would set it as expected date for all the stock moves. " , track_visibility = ' onchange ' ) ,
2013-06-29 22:17:03 +00:00
' max_date ' : fields . function ( get_min_max_date , multi = " min_max_date " ,
2014-02-26 14:12:23 +00:00
store = { ' stock.move ' : ( _get_pickings , [ ' 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-12-11 10:25:11 +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 ) ] } , track_visibility = ' onchange ' ) ,
2013-11-18 13:36:50 +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 ) ] } ) ,
2013-10-08 12:36:59 +00:00
' quant_reserved_exist ' : fields . function ( _get_quant_reserved_exist , type = ' boolean ' , string = ' Quant already reserved ? ' , help = ' technical field used to know if there is already at least one quant reserved on moves of a given picking ' ) ,
2013-11-18 13:36:50 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Partner ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True , select = True , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } ) ,
2014-01-31 10:28:01 +00:00
' pack_operation_ids ' : fields . one2many ( ' stock.pack.operation ' , ' picking_id ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , 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 ' ) ,
2014-01-31 10:28:01 +00:00
' picking_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Picking Type ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , required = True ) ,
2013-12-18 13:33:06 +00:00
' picking_type_code ' : fields . related ( ' picking_type_id ' , ' code ' , type = ' char ' , string = ' Picking Type Code ' , help = " Technical field used to display the correct label on print button in the picking view " ) ,
2013-07-29 21:44:43 +00:00
2014-01-31 10:28:01 +00:00
' owner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancel ' : [ ( ' readonly ' , True ) ] } , help = " Default Owner " ) ,
2013-07-29 21:44:43 +00:00
# Used to search on pickings
2013-11-18 13:36:50 +00:00
' product_id ' : fields . related ( ' move_lines ' , ' product_id ' , type = ' many2one ' , relation = ' product.product ' , string = ' Product ' ) ,
2014-04-01 09:31:27 +00:00
' recompute_pack_op ' : fields . boolean ( ' Recompute pack operation? ' , help = ' True if reserved quants changed, which mean we might need to recompute the package operations ' ) ,
2013-07-24 15:32:35 +00:00
' 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 ) ,
2013-11-18 13:36:50 +00:00
' group_id ' : fields . related ( ' move_lines ' , ' group_id ' , type = ' many2one ' , relation = ' procurement.group ' , string = ' Procurement Group ' , readonly = True ,
2013-10-22 16:03:12 +00:00
store = {
2013-10-23 09:30:47 +00:00
' stock.picking ' : ( lambda self , cr , uid , ids , ctx : ids , [ ' move_lines ' ] , 10 ) ,
2013-10-22 16:03:12 +00:00
' stock.move ' : ( _get_pickings , [ ' group_id ' , ' picking_id ' ] , 10 ) ,
} ) ,
2008-07-22 15:11:28 +00:00
}
2013-10-17 16:07:25 +00:00
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 ' ,
2013-11-18 10:57:17 +00:00
' priority ' : ' 1 ' , # normal
2013-12-11 10:25:11 +00:00
' date ' : fields . datetime . now ,
2014-03-28 10:49:06 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.picking ' , context = c ) ,
' recompute_pack_op ' : True ,
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 ' ) :
2010-09-01 08:40:10 +00:00
default [ ' backorder_id ' ] = False
2013-11-18 10:57:17 +00:00
default [ ' pack_operation_ids ' ] = [ ]
2014-01-06 14:30:02 +00:00
default [ ' date_done ' ] = 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
2013-12-18 13:33:06 +00:00
def do_print_picking ( self , cr , uid , ids , context = None ) :
''' This function prints the picking list '''
2014-03-27 17:42:28 +00:00
return self . pool . get ( " report " ) . get_action ( cr , uid , ids , ' stock.report_picking ' , context = context )
2013-12-18 13:33:06 +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-11-13 08:32:55 +00:00
def action_assign ( self , cr , uid , ids , context = None ) :
""" Check availability of picking moves.
2013-11-15 17:55:05 +00:00
This has the effect of changing the state and reserve quants on available moves , and may
2013-11-13 08:32:55 +00:00
also impact the state of the picking as it is computed based on move ' s states.
2010-05-26 12:59:30 +00:00
@return : True
"""
2013-11-13 08:32:55 +00:00
for pick in self . browse ( cr , uid , ids , context = context ) :
2012-02-29 12:06:38 +00:00
if pick . state == ' draft ' :
2013-11-13 08:32:55 +00:00
self . action_confirm ( cr , uid , [ pick . id ] , context = context )
2014-02-14 08:46:50 +00:00
pick . refresh ( )
2013-11-19 15:33:00 +00:00
#skip the moves that don't need to be checked
2013-11-18 09:50:21 +00:00
move_ids = [ x . id for x in pick . move_lines if x . state not in ( ' draft ' , ' cancel ' , ' done ' ) ]
2010-03-18 07:12:23 +00:00
if not move_ids :
2013-11-15 17:55:05 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' Nothing to check the availability for. ' ) )
2013-11-13 08:32:55 +00:00
self . pool . get ( ' stock.move ' ) . action_assign ( cr , uid , move_ids , context = context )
2008-07-22 15:11:28 +00:00
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 )
2014-04-01 09:31:27 +00:00
#pack_operation might have changed and need to be recomputed
self . write ( cr , uid , ids , { ' recompute_pack_op ' : True } , context = context )
2008-07-22 15:11:28 +00:00
return True
2008-11-03 14:40:49 +00:00
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 ' :
2014-02-14 08:46:50 +00:00
todo . extend ( self . pool . get ( ' stock.move ' ) . action_confirm ( cr , uid , [ move . id ] , context = context ) )
2013-11-18 13:36:50 +00:00
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 ) :
2013-11-20 10:42:33 +00:00
#on picking deletion, cancel its move then unlink them too
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-11-20 10:42:33 +00:00
move_ids = [ move . id for move in pick . move_lines ]
move_obj . action_cancel ( cr , uid , move_ids , context = context )
move_obj . unlink ( cr , uid , move_ids , context = context )
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-11-19 15:33:00 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
res = super ( stock_picking , self ) . write ( cr , uid , ids , vals , context = context )
2013-11-20 10:42:33 +00:00
#if we changed the move lines or the pack operations, we need to recompute the remaining quantities of both
2013-11-19 15:33:00 +00:00
if ' move_lines ' in vals or ' pack_operation_ids ' in vals :
self . do_recompute_remaining_quantities ( cr , uid , ids , context = context )
return res
2013-07-30 07:46:15 +00:00
2013-08-03 23:28:48 +00:00
def _create_backorder ( self , cr , uid , picking , backorder_moves = [ ] , context = None ) :
2013-11-20 10:42:33 +00:00
""" Move all non-done lines into a new backorder picking. If the key ' do_only_split ' is given in the context, then move all lines not in context.get( ' split ' , []) instead of all non-done lines.
2013-07-30 08:58:59 +00:00
"""
2013-08-03 23:28:48 +00:00
if not backorder_moves :
backorder_moves = picking . move_lines
2013-11-18 13:36:50 +00:00
backorder_move_ids = [ x . id for x in backorder_moves if x . state not in ( ' done ' , ' cancel ' ) ]
2013-09-24 15:31:46 +00:00
if ' do_only_split ' in context and context [ ' do_only_split ' ] :
2013-11-18 13:36:50 +00:00
backorder_move_ids = [ x . id for x in backorder_moves if x . id not in context . get ( ' split ' , [ ] ) ]
2013-08-03 23:28:48 +00:00
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 )
2013-11-18 13:36:50 +00:00
2014-01-06 14:30:02 +00:00
self . write ( cr , uid , [ picking . id ] , { ' date_done ' : time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) } , context = context )
2013-09-10 15:52:14 +00:00
self . action_confirm ( cr , uid , [ backorder_id ] , context = context )
2013-08-26 15:36:48 +00:00
return backorder_id
return False
2013-06-29 22:17:03 +00:00
2014-02-13 10:51:48 +00:00
def recheck_availability ( self , cr , uid , picking_ids , context = None ) :
self . action_assign ( cr , uid , picking_ids , context = context )
self . do_prepare_partial ( cr , uid , picking_ids , context = context )
2014-03-11 13:31:09 +00:00
def _picking_putaway_resolution ( self , cr , uid , picking , product , putaway , context = None ) :
2014-03-03 16:42:10 +00:00
if putaway . method == ' fixed ' and putaway . location_spec_id :
2014-03-11 13:31:09 +00:00
return putaway . location_spec_id . id
2014-03-03 16:42:10 +00:00
return False
2014-03-11 13:31:09 +00:00
def _get_top_level_packages ( self , cr , uid , quants_suggested_locations , context = None ) :
""" This method searches for the higher level packages that can be moved as a single operation, given a list of quants
to move and their suggested destination , and returns the list of matching packages .
2014-03-03 16:42:10 +00:00
"""
2014-03-11 13:31:09 +00:00
# Try to find as much as possible top-level packages that can be moved
pack_obj = self . pool . get ( " stock.quant.package " )
2014-03-19 16:33:59 +00:00
quant_obj = self . pool . get ( " stock.quant " )
2014-03-11 13:31:09 +00:00
top_lvl_packages = set ( )
2014-03-11 15:44:09 +00:00
quants_to_compare = quants_suggested_locations . keys ( )
2014-03-11 13:31:09 +00:00
for pack in list ( set ( [ x . package_id for x in quants_suggested_locations . keys ( ) if x and x . package_id ] ) ) :
loop = True
test_pack = pack
2014-03-11 15:44:09 +00:00
good_pack = False
pack_destination = False
2014-03-11 13:31:09 +00:00
while loop :
2014-03-19 16:33:59 +00:00
pack_quants = pack_obj . get_content ( cr , uid , [ test_pack . id ] , context = context )
2014-03-11 13:31:09 +00:00
all_in = True
2014-03-19 16:33:59 +00:00
for quant in quant_obj . browse ( cr , uid , pack_quants , context = context ) :
2014-03-11 13:31:09 +00:00
# If the quant is not in the quants to compare and not in the common location
if not quant in quants_to_compare :
all_in = False
break
else :
#if putaway strat apply, the destination location of each quant may be different (and thus the package should not be taken as a single operation)
if not pack_destination :
pack_destination = quants_suggested_locations [ quant ]
elif pack_destination != quants_suggested_locations [ quant ] :
all_in = False
break
if all_in :
2014-03-13 09:57:46 +00:00
good_pack = test_pack
2014-03-11 13:31:09 +00:00
if test_pack . parent_id :
test_pack = test_pack . parent_id
else :
#stop the loop when there's no parent package anymore
loop = False
else :
#stop the loop when the package test_pack is not totally reserved for moves of this picking
#(some quants may be reserved for other picking or not reserved at all)
loop = False
if good_pack :
top_lvl_packages . add ( good_pack )
return list ( top_lvl_packages )
def _prepare_pack_ops ( self , cr , uid , picking , quants , forced_qties , context = None ) :
""" returns a list of dict, ready to be used in create() of stock.pack.operation.
: param picking : browse record ( stock . picking )
: param quants : browse record list ( stock . quant ) . List of quants associated to the picking
: param forced_qties : dictionary showing for each product ( keys ) its corresponding quantity ( value ) that is not covered by the quants associated to the picking
2014-03-03 16:42:10 +00:00
"""
2014-03-11 13:31:09 +00:00
def _picking_putaway_apply ( product ) :
2014-03-03 16:42:10 +00:00
location = False
# Search putaway strategy
2014-03-11 13:31:09 +00:00
if product_putaway_strats . get ( product . id ) :
putaway_strat = product_putaway_strats [ product . id ]
2014-03-03 16:42:10 +00:00
else :
2014-03-11 13:31:09 +00:00
putaway_strat = self . pool . get ( ' stock.location ' ) . get_putaway_strategy ( cr , uid , picking . location_dest_id , product , context = context )
product_putaway_strats [ product . id ] = putaway_strat
if putaway_strat :
location = self . _picking_putaway_resolution ( cr , uid , picking , product , putaway_strat , context = context )
return location or picking . picking_type_id . default_location_dest_id . id or picking . location_dest_id . id
pack_obj = self . pool . get ( " stock.quant.package " )
quant_obj = self . pool . get ( " stock.quant " )
vals = [ ]
qtys_grouped = { }
#for each quant of the picking, find the suggested location
quants_suggested_locations = { }
product_putaway_strats = { }
for quant in quants :
if quant . qty < = 0 :
continue
suggested_location_id = _picking_putaway_apply ( quant . product_id )
quants_suggested_locations [ quant ] = suggested_location_id
#find the packages we can movei as a whole
top_lvl_packages = self . _get_top_level_packages ( cr , uid , quants_suggested_locations , context = context )
# and then create pack operations for the top-level packages found
2014-03-13 09:57:46 +00:00
for pack in top_lvl_packages :
2014-03-25 16:03:23 +00:00
pack_quant_ids = pack_obj . get_content ( cr , uid , [ pack . id ] , context = context )
2014-03-19 16:33:59 +00:00
pack_quants = quant_obj . browse ( cr , uid , pack_quant_ids , context = context )
2014-03-11 13:31:09 +00:00
vals . append ( {
' picking_id ' : picking . id ,
' package_id ' : pack . id ,
' product_qty ' : 1.0 ,
' location_id ' : pack . location_id . id ,
2014-03-20 18:03:10 +00:00
' location_dest_id ' : quants_suggested_locations [ pack_quants [ 0 ] ] ,
2014-03-11 13:31:09 +00:00
} )
#remove the quants inside the package so that they are excluded from the rest of the computation
2014-03-13 09:57:46 +00:00
for quant in pack_quants :
2014-03-11 13:31:09 +00:00
del quants_suggested_locations [ quant ]
# Go through all remaining reserved quants and group by product, package, lot, owner, source location and dest location
for quant , dest_location_id in quants_suggested_locations . items ( ) :
key = ( quant . product_id . id , quant . package_id . id , quant . lot_id . id , quant . owner_id . id , quant . location_id . id , dest_location_id )
if qtys_grouped . get ( key ) :
qtys_grouped [ key ] + = quant . qty
else :
qtys_grouped [ key ] = quant . qty
# Do the same for the forced quantities (in cases of force_assign or incomming shipment for example)
for product , qty in forced_qties . items ( ) :
if qty < = 0 :
continue
suggested_location_id = _picking_putaway_apply ( product )
key = ( product . id , False , False , False , picking . picking_type_id . default_location_src_id . id or picking . location_id . id , suggested_location_id )
if qtys_grouped . get ( key ) :
qtys_grouped [ key ] + = qty
2014-03-03 16:42:10 +00:00
else :
2014-03-11 13:31:09 +00:00
qtys_grouped [ key ] = qty
# Create the necessary operations for the grouped quants and remaining qtys
for key , qty in qtys_grouped . items ( ) :
vals . append ( {
' picking_id ' : picking . id ,
' product_qty ' : qty ,
' product_id ' : key [ 0 ] ,
' package_id ' : key [ 1 ] ,
' lot_id ' : key [ 2 ] ,
' owner_id ' : key [ 3 ] ,
' location_id ' : key [ 4 ] ,
' location_dest_id ' : key [ 5 ] ,
' product_uom_id ' : self . pool . get ( " product.product " ) . browse ( cr , uid , key [ 0 ] , context = context ) . uom_id . id ,
} )
return vals
2014-03-28 10:49:06 +00:00
2014-03-25 08:08:23 +00:00
def open_barcode_interface ( self , cr , uid , picking_ids , context = None ) :
final_url = " /barcode/web/#action=stock.ui&picking_id= " + str ( picking_ids [ 0 ] )
return { ' type ' : ' ir.actions.act_url ' , ' url ' : final_url , ' target ' : ' self ' , }
def do_partial_open_barcode ( self , cr , uid , picking_ids , context = None ) :
self . do_prepare_partial ( cr , uid , picking_ids , context = context )
return self . open_barcode_interface ( cr , uid , picking_ids , context = context )
2014-03-03 16:42:10 +00:00
2013-07-30 06:26:41 +00:00
def do_prepare_partial ( self , cr , uid , picking_ids , context = None ) :
context = context or { }
2014-03-19 16:33:59 +00:00
pack_operation_obj = self . pool . get ( ' stock.pack.operation ' )
#used to avoid recomputing the remaining quantities at each new pack operation created
2014-03-10 15:11:33 +00:00
ctx = context . copy ( )
ctx [ ' no_recompute ' ] = True
2014-03-03 16:42:10 +00:00
2014-02-20 08:35:19 +00:00
#get list of existing operations and delete them
2014-01-28 10:41:52 +00:00
existing_package_ids = pack_operation_obj . search ( cr , uid , [ ( ' picking_id ' , ' in ' , picking_ids ) ] , context = context )
if existing_package_ids :
pack_operation_obj . unlink ( cr , uid , existing_package_ids , context )
2013-07-30 06:26:41 +00:00
for picking in self . browse ( cr , uid , picking_ids , context = context ) :
2014-03-11 13:31:09 +00:00
forced_qties = { } # Quantity remaining after calculating reserved quants
picking_quants = [ ]
2014-02-14 09:15:40 +00:00
#Calculate packages, reserved quants, qtys of this picking's moves
2013-07-30 06:26:41 +00:00
for move in picking . move_lines :
2014-01-28 10:41:52 +00:00
if move . state not in ( ' assigned ' , ' confirmed ' ) :
2013-11-18 13:36:50 +00:00
continue
2014-03-11 13:31:09 +00:00
move_quants = move . reserved_quant_ids
picking_quants + = move_quants
2014-03-13 09:57:46 +00:00
forced_qty = ( move . state == ' assigned ' ) and move . product_qty - sum ( [ x . qty for x in move_quants ] ) or 0
2014-03-11 13:31:09 +00:00
#if we used force_assign() on the move, or if the move is incomming, forced_qty > 0
if forced_qty :
if forced_qties . get ( move . product_id ) :
forced_qties [ move . product_id ] + = forced_qty
2014-02-14 09:15:40 +00:00
else :
2014-03-11 13:31:09 +00:00
forced_qties [ move . product_id ] = forced_qty
for vals in self . _prepare_pack_ops ( cr , uid , picking , picking_quants , forced_qties , context = context ) :
2014-03-19 16:33:59 +00:00
pack_operation_obj . create ( cr , uid , vals , context = ctx )
#recompute the remaining quantities all at once
self . do_recompute_remaining_quantities ( cr , uid , picking_ids , context = context )
2014-04-01 09:31:27 +00:00
self . write ( cr , uid , picking_ids , { ' recompute_pack_op ' : False } , context = context )
2013-07-30 06:24:10 +00:00
2013-11-14 21:14:28 +00:00
def do_unreserve ( self , cr , uid , picking_ids , context = None ) :
2013-07-23 15:34:24 +00:00
"""
2013-10-01 08:28:19 +00:00
Will remove all quants for picking in picking_ids
"""
2013-11-14 21:14:28 +00:00
moves_to_unreserve = [ ]
2014-01-28 10:41:52 +00:00
pack_line_to_unreserve = [ ]
2013-07-30 07:46:15 +00:00
for picking in self . browse ( cr , uid , picking_ids , context = context ) :
2014-02-06 12:38:52 +00:00
moves_to_unreserve + = [ m . id for m in picking . move_lines if m . state not in ( ' done ' , ' cancel ' ) ]
2014-02-03 09:31:15 +00:00
pack_line_to_unreserve + = [ p . id for p in picking . pack_operation_ids ]
2013-11-14 21:14:28 +00:00
if moves_to_unreserve :
2014-02-06 12:38:52 +00:00
if pack_line_to_unreserve :
self . pool . get ( ' stock.pack.operation ' ) . unlink ( cr , uid , pack_line_to_unreserve , context = context )
2013-11-14 21:14:28 +00:00
self . pool . get ( ' stock.move ' ) . do_unreserve ( cr , uid , moves_to_unreserve , context = context )
2013-09-24 14:32:20 +00:00
2014-03-10 15:11:33 +00:00
def recompute_remaining_qty ( self , cr , uid , picking , context = None ) :
2014-03-25 15:06:38 +00:00
def _create_link_for_index ( operation_id , index , product_id , qty_to_assign , quant_id = False ) :
move_dict = prod2move_ids [ product_id ] [ index ]
qty_on_link = min ( move_dict [ ' remaining_qty ' ] , qty_to_assign )
self . pool . get ( ' stock.move.operation.link ' ) . create ( cr , uid , { ' move_id ' : move_dict [ ' move ' ] . id , ' operation_id ' : operation_id , ' qty ' : qty_on_link , ' reserved_quant_id ' : quant_id } , context = context )
if move_dict [ ' remaining_qty ' ] == qty_on_link :
prod2move_ids [ product_id ] . pop ( index )
else :
move_dict [ ' remaining_qty ' ] - = qty_on_link
return qty_on_link
def _create_link_for_quant ( operation_id , quant , qty ) :
""" create a link for given operation and reserved move of given quant, for the max quantity possible, and returns this quantity """
if not quant . reservation_id . id :
return _create_link_for_product ( operation_id , quant . product_id . id , qty )
qty_on_link = 0
for i in range ( 0 , len ( prod2move_ids [ quant . product_id . id ] ) ) :
if prod2move_ids [ quant . product_id . id ] [ i ] [ ' move ' ] . id != quant . reservation_id . id :
continue
qty_on_link = _create_link_for_index ( operation_id , i , quant . product_id . id , qty , quant_id = quant . id )
break
return qty_on_link
def _create_link_for_product ( operation_id , product_id , qty ) :
''' method that creates the link between a given operation and move(s) of given product, for the given quantity.
Returns True if it was possible to create links for the requested quantity ( False if there was not enough quantity on stock moves ) '''
2014-03-10 15:11:33 +00:00
qty_to_assign = qty
2014-03-25 15:06:38 +00:00
if prod2move_ids . get ( product_id ) :
while prod2move_ids [ product_id ] and qty_to_assign > 0 :
qty_on_link = _create_link_for_index ( operation_id , 0 , product_id , qty_to_assign , quant_id = False )
qty_to_assign - = qty_on_link
2014-03-10 15:11:33 +00:00
return qty_to_assign == 0
uom_obj = self . pool . get ( ' product.uom ' )
package_obj = self . pool . get ( ' stock.quant.package ' )
2014-03-25 15:06:38 +00:00
quant_obj = self . pool . get ( ' stock.quant ' )
quants_in_package_done = set ( )
prod2move_ids = { }
still_to_do = [ ]
#make a dictionary giving for each product, the moves and related quantity that can be used in operation links
for move in picking . move_lines :
if not prod2move_ids . get ( move . product_id . id ) :
prod2move_ids [ move . product_id . id ] = [ { ' move ' : move , ' remaining_qty ' : move . product_qty } ]
else :
prod2move_ids [ move . product_id . id ] . append ( { ' move ' : move , ' remaining_qty ' : move . product_qty } )
need_rereserve = False
#sort the operations in order to give higher priority to those with a package, then a serial number
2014-03-10 15:11:33 +00:00
operations = picking . pack_operation_ids
operations . sort ( key = lambda x : ( ( x . package_id and not x . product_id ) and - 4 or 0 ) + ( x . package_id and - 2 or 0 ) + ( x . lot_id and - 1 or 0 ) )
2014-03-25 15:06:38 +00:00
#delete existing operations to start again from scratch
2014-03-20 18:03:10 +00:00
cr . execute ( " DELETE FROM stock_move_operation_link WHERE operation_id in %s " , ( tuple ( [ x . id for x in operations ] ) , ) )
2014-03-25 15:06:38 +00:00
#1) first, try to create links when quants can be identified without any doubt
for ops in operations :
#for each operation, create the links with the stock move by seeking on the matching reserved quants,
#and deffer the operation if there is some ambiguity on the move to select
if ops . package_id and not ops . product_id :
#entire package
quant_ids = package_obj . get_content ( cr , uid , [ ops . package_id . id ] , context = context )
for quant in quant_obj . browse ( cr , uid , quant_ids , context = context ) :
remaining_qty_on_quant = quant . qty
if quant . reservation_id :
#avoid quants being counted twice
quants_in_package_done . add ( quant . id )
qty_on_link = _create_link_for_quant ( ops . id , quant , quant . qty )
remaining_qty_on_quant - = qty_on_link
if remaining_qty_on_quant :
still_to_do . append ( ( ops , quant . product_id . id , remaining_qty_on_quant ) )
need_rereserve = True
elif ops . product_id . id :
#Check moves with same product
qty_to_assign = uom_obj . _compute_qty_obj ( cr , uid , ops . product_uom_id , ops . product_qty , ops . product_id . uom_id , context = context )
for move_dict in prod2move_ids . get ( ops . product_id . id , [ ] ) :
move = move_dict [ ' move ' ]
for quant in move . reserved_quant_ids :
if not qty_to_assign > 0 :
break
if quant . id in quants_in_package_done :
continue
#check if the quant is matching the operation details
if ops . package_id :
flag = quant . package_id and bool ( package_obj . search ( cr , uid , [ ( ' id ' , ' child_of ' , [ ops . package_id . id ] ) , ( ' id ' , ' = ' , quant . package_id . id ) ] , context = context ) ) or False
else :
flag = not quant . package_id . id
flag = flag and ( ( ops . lot_id and ops . lot_id . id == quant . lot_id . id ) or not ops . lot_id )
flag = flag and ( ops . owner_id . id == quant . owner_id . id )
if flag :
max_qty_on_link = min ( quant . qty , qty_to_assign )
qty_on_link = _create_link_for_quant ( ops . id , quant , max_qty_on_link )
qty_to_assign - = qty_on_link
if qty_to_assign > 0 :
#qty reserved is less than qty put in operations. We need to create a link but it's deferred after we processed
#all the quants (because they leave no choice on their related move and needs to be processed with higher priority)
still_to_do + = [ ( ops , ops . product_id . id , qty_to_assign ) ]
need_rereserve = True
#2) then, process the remaining part
all_op_processed = True
for ops , product_id , remaining_qty in still_to_do :
all_op_processed = all_op_processed and _create_link_for_product ( ops . id , product_id , remaining_qty )
return ( need_rereserve , all_op_processed )
def picking_recompute_remaining_quantities ( self , cr , uid , picking , context = None ) :
need_rereserve = False
all_op_processed = True
if picking . pack_operation_ids :
need_rereserve , all_op_processed = self . recompute_remaining_qty ( cr , uid , picking , context = context )
return need_rereserve , all_op_processed
2014-03-10 15:11:33 +00:00
2013-11-18 09:50:21 +00:00
def do_recompute_remaining_quantities ( self , cr , uid , picking_ids , context = None ) :
for picking in self . browse ( cr , uid , picking_ids , context = context ) :
2014-03-10 15:11:33 +00:00
if picking . pack_operation_ids :
2014-03-25 15:06:38 +00:00
self . recompute_remaining_qty ( cr , uid , picking , context = context )
2013-11-18 09:50:21 +00:00
2013-11-15 17:55:05 +00:00
def _create_extra_moves ( self , cr , uid , picking , context = None ) :
2013-11-20 10:42:33 +00:00
''' This function creates move lines on a picking, at the time of do_transfer, based on
unexpected product transfers ( or exceeding quantities ) found in the pack operations .
2013-11-14 21:14:28 +00:00
'''
2013-11-15 17:55:05 +00:00
move_obj = self . pool . get ( ' stock.move ' )
2013-11-18 09:50:21 +00:00
operation_obj = self . pool . get ( ' stock.pack.operation ' )
2013-11-15 17:55:05 +00:00
for op in picking . pack_operation_ids :
2013-11-18 09:50:21 +00:00
for product_id , remaining_qty in operation_obj . _get_remaining_prod_quantities ( cr , uid , op , context = context ) . items ( ) :
2013-11-18 10:57:17 +00:00
if remaining_qty > 0 :
2013-11-18 08:30:26 +00:00
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
2013-11-15 17:55:05 +00:00
vals = {
' picking_id ' : picking . id ,
' location_id ' : picking . location_id . id ,
' location_dest_id ' : picking . location_dest_id . id ,
2013-11-18 08:30:26 +00:00
' product_id ' : product_id ,
' product_uom ' : product . uom_id . id ,
' product_uom_qty ' : remaining_qty ,
' name ' : _ ( ' Extra Move: ' ) + product . name ,
2013-11-18 10:57:17 +00:00
' state ' : ' confirmed ' ,
2013-11-15 17:55:05 +00:00
}
move_obj . create ( cr , uid , vals , context = context )
2013-09-24 14:32:20 +00:00
2013-11-14 21:14:28 +00:00
def rereserve_quants ( self , cr , uid , picking , move_ids = [ ] , context = None ) :
2013-11-20 10:42:33 +00:00
""" Unreserve quants then try to reassign quants. """
2013-11-14 21:14:28 +00:00
stock_move_obj = self . pool . get ( ' stock.move ' )
if not move_ids :
self . do_unreserve ( cr , uid , [ picking . id ] , context = context )
self . action_assign ( cr , uid , [ picking . id ] , context = context )
2013-09-24 14:32:20 +00:00
else :
2013-11-14 21:14:28 +00:00
stock_move_obj . do_unreserve ( cr , uid , move_ids , context = context )
stock_move_obj . action_assign ( cr , uid , move_ids , context = context )
2013-07-30 07:46:15 +00:00
2013-11-14 21:14:28 +00:00
def do_transfer ( self , cr , uid , picking_ids , context = None ) :
2013-07-30 08:47:16 +00:00
"""
2013-09-30 09:04:37 +00:00
If no pack operation , we do simple action_done of the picking
2013-07-30 08:47:16 +00:00
Otherwise , do the pack operations
2013-07-23 15:34:24 +00:00
"""
2013-09-24 15:31:46 +00:00
if not context :
2013-11-14 21:14:28 +00:00
context = { }
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 :
2014-03-25 15:06:38 +00:00
need_rereserve , all_op_processed = self . picking_recompute_remaining_quantities ( cr , uid , picking , context = context )
2013-11-14 21:14:28 +00:00
#create extra moves in the picking (unexpected product moves coming from pack operations)
2014-03-25 15:06:38 +00:00
if not all_op_processed :
2014-02-27 13:12:37 +00:00
self . _create_extra_moves ( cr , uid , picking , context = context )
2013-11-14 21:14:28 +00:00
picking . refresh ( )
#split move lines eventually
todo_move_ids = [ ]
toassign_move_ids = [ ]
for move in picking . move_lines :
2014-02-27 08:45:09 +00:00
remaining_qty = move . remaining_qty
2014-01-16 10:26:21 +00:00
if move . state in ( ' done ' , ' cancel ' ) :
#ignore stock moves cancelled or already done
continue
elif move . state == ' draft ' :
2013-11-14 21:14:28 +00:00
toassign_move_ids . append ( move . id )
2014-02-27 08:45:09 +00:00
if remaining_qty == 0 :
2013-11-14 21:14:28 +00:00
if move . state in ( ' draft ' , ' assigned ' , ' confirmed ' ) :
todo_move_ids . append ( move . id )
2014-02-27 08:45:09 +00:00
elif remaining_qty > 0 and remaining_qty < move . product_qty :
new_move = stock_move_obj . split ( cr , uid , move , remaining_qty , context = context )
2013-11-19 15:33:00 +00:00
todo_move_ids . append ( move . id )
2013-11-14 21:14:28 +00:00
#Assign move as it was assigned before
toassign_move_ids . append ( new_move )
2014-03-25 15:06:38 +00:00
if ( need_rereserve or not all_op_processed ) and not picking . location_id . usage in ( " supplier " , " production " , " inventory " ) :
2014-02-27 13:12:37 +00:00
self . rereserve_quants ( cr , uid , picking , move_ids = todo_move_ids , context = context )
2014-03-26 08:56:57 +00:00
self . do_recompute_remaining_quantities ( cr , uid , [ picking . id ] , context = context )
2013-11-14 21:14:28 +00:00
if todo_move_ids and not context . get ( ' do_only_split ' ) :
self . pool . get ( ' stock.move ' ) . action_done ( cr , uid , todo_move_ids , context = context )
elif context . get ( ' do_only_split ' ) :
context . update ( { ' split ' : todo_move_ids } )
2013-08-26 13:03:22 +00:00
picking . refresh ( )
2013-07-30 07:46:15 +00:00
self . _create_backorder ( cr , uid , picking , context = context )
2013-11-14 21:14:28 +00:00
if toassign_move_ids :
stock_move_obj . action_assign ( cr , uid , toassign_move_ids , context = context )
2013-07-30 07:46:15 +00:00
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-11-20 10:42:33 +00:00
""" just split the picking (create a backorder) 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
2013-11-20 10:42:33 +00:00
return self . do_transfer ( cr , uid , picking_ids , context = ctx )
2013-08-03 23:28:48 +00:00
2013-11-20 10:42:33 +00:00
def get_next_picking_for_ui ( self , cr , uid , context = None ) :
""" returns the next pickings to process. Used in the barcode scanner UI """
if context is None :
context = { }
2014-02-11 15:51:57 +00:00
domain = [ ( ' state ' , ' in ' , ( ' assigned ' , ' partially_available ' ) ) ]
2013-11-20 10:42:33 +00:00
if context . get ( ' default_picking_type_id ' ) :
domain . append ( ( ' picking_type_id ' , ' = ' , context [ ' default_picking_type_id ' ] ) )
return self . search ( cr , uid , domain , context = context )
def action_done_from_ui ( self , cr , uid , picking_id , context = None ) :
2014-02-11 15:51:57 +00:00
""" called when button ' done ' is pushed in the barcode scanner UI """
2014-03-11 09:31:05 +00:00
#write qty_done into field product_qty for every package_operation before doing the transfer
pack_op_obj = self . pool . get ( ' stock.pack.operation ' )
for operation in self . browse ( cr , uid , picking_id , context = context ) . pack_operation_ids :
pack_op_obj . write ( cr , uid , operation . id , { ' product_qty ' : operation . qty_done } , context = context )
2013-11-20 10:42:33 +00:00
self . do_transfer ( cr , uid , [ picking_id ] , context = context )
2013-06-26 13:10:04 +00:00
#return id of next picking to work on
2013-11-20 10:42:33 +00:00
return self . get_next_picking_for_ui ( cr , uid , context = context )
2013-06-26 13:10:04 +00:00
2014-03-06 13:38:55 +00:00
def action_pack ( self , cr , uid , picking_ids , operation_filter_ids = None , context = None ) :
2013-11-20 10:42:33 +00:00
""" Create a package with the current pack_operation_ids of the picking that aren ' t yet in a pack.
2014-03-06 13:38:55 +00:00
Used in the barcode scanner UI and the normal interface as well .
operation_filter_ids is used by barcode scanner interface to specify a subset of operation to pack """
if operation_filter_ids == None :
operation_filter_ids = [ ]
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 ' )
2014-01-28 16:02:43 +00:00
stock_move_obj = self . pool . get ( ' stock.move ' )
2013-07-30 06:26:41 +00:00
for picking_id in picking_ids :
2014-03-06 13:38:55 +00:00
operation_search_domain = [ ( ' picking_id ' , ' = ' , picking_id ) , ( ' result_package_id ' , ' = ' , False ) ]
if operation_filter_ids != [ ] :
operation_search_domain . append ( ( ' id ' , ' in ' , operation_filter_ids ) )
operation_ids = stock_operation_obj . search ( cr , uid , operation_search_domain , context = context )
pack_operation_ids = [ ]
2013-07-30 06:26:41 +00:00
if operation_ids :
2014-01-28 16:02:43 +00:00
for operation in stock_operation_obj . browse ( cr , uid , operation_ids , context = context ) :
2014-03-06 13:38:55 +00:00
#If we haven't done all qty in operation, we have to split into 2 operation
op = operation
if ( operation . qty_done < operation . product_qty ) :
new_operation = stock_operation_obj . copy ( cr , uid , operation . id , { ' product_qty ' : operation . qty_done , ' qty_done ' : operation . qty_done } , context = context )
stock_operation_obj . write ( cr , uid , operation . id , { ' product_qty ' : operation . product_qty - operation . qty_done , ' qty_done ' : 0 } , context = context )
op = stock_operation_obj . browse ( cr , uid , new_operation , context = context )
pack_operation_ids . append ( op . id )
for record in op . linked_move_operation_ids :
stock_move_obj . check_tracking ( cr , uid , record . move_id , op . package_id . id or op . lot_id . id , context = context )
2013-07-30 06:26:41 +00:00
package_id = package_obj . create ( cr , uid , { } , context = context )
2014-03-06 13:38:55 +00:00
stock_operation_obj . write ( cr , uid , pack_operation_ids , { ' result_package_id ' : package_id } , context = context )
2013-07-30 06:26:41 +00:00
return True
2013-06-26 13:10:04 +00:00
2014-03-24 10:46:21 +00:00
def process_product_id_from_ui ( self , cr , uid , picking_id , product_id , op_id , increment = True , context = None ) :
return self . pool . get ( ' stock.pack.operation ' ) . _search_and_increment ( cr , uid , picking_id , [ ( ' product_id ' , ' = ' , product_id ) , ( ' id ' , ' = ' , op_id ) ] , increment = increment , context = context )
2013-11-20 10:42:33 +00:00
2014-03-24 10:46:21 +00:00
def process_barcode_from_ui ( self , cr , uid , picking_id , barcode_str , visible_op_ids , context = None ) :
2013-06-26 13:10:04 +00:00
''' This function is called each time there barcode scanner reads an input '''
2013-11-20 10:42:33 +00:00
lot_obj = self . pool . get ( ' stock.production.lot ' )
2013-06-26 13:10:04 +00:00
package_obj = self . pool . get ( ' stock.quant.package ' )
product_obj = self . pool . get ( ' product.product ' )
stock_operation_obj = self . pool . get ( ' stock.pack.operation ' )
2014-03-07 16:37:47 +00:00
stock_location_obj = self . pool . get ( ' stock.location ' )
2014-03-10 16:57:07 +00:00
answer = { ' filter_loc ' : False , ' operation_id ' : False }
2014-03-07 16:37:47 +00:00
#check if the barcode correspond to a location
matching_location_ids = stock_location_obj . search ( cr , uid , [ ( ' loc_barcode ' , ' = ' , barcode_str ) ] , context = context )
if matching_location_ids :
#if we have a location, return immediatly with the location name
location = stock_location_obj . browse ( cr , uid , matching_location_ids [ 0 ] , context = None )
2014-03-10 16:57:07 +00:00
answer [ ' filter_loc ' ] = stock_location_obj . _name_get ( cr , uid , location , context = None )
2014-03-20 15:40:56 +00:00
answer [ ' filter_loc_id ' ] = matching_location_ids [ 0 ]
2014-03-10 16:57:07 +00:00
return answer
2013-06-26 13:10:04 +00:00
#check if the barcode correspond to a product
2014-02-21 17:25:55 +00:00
matching_product_ids = product_obj . search ( cr , uid , [ ' | ' , ( ' ean13 ' , ' = ' , barcode_str ) , ( ' default_code ' , ' = ' , barcode_str ) ] , context = context )
2013-06-26 13:10:04 +00:00
if matching_product_ids :
2014-03-24 10:46:21 +00:00
op_id = stock_operation_obj . _search_and_increment ( cr , uid , picking_id , [ ( ' product_id ' , ' = ' , matching_product_ids [ 0 ] ) ] , filter_visible = True , visible_op_ids = visible_op_ids , increment = True , context = context )
2014-03-10 16:57:07 +00:00
answer [ ' operation_id ' ] = op_id
return answer
2013-11-20 10:42:33 +00:00
#check if the barcode correspond to a lot
matching_lot_ids = lot_obj . search ( cr , uid , [ ( ' name ' , ' = ' , barcode_str ) ] , context = context )
if matching_lot_ids :
lot = lot_obj . browse ( cr , uid , matching_lot_ids [ 0 ] , context = context )
2014-03-24 10:46:21 +00:00
op_id = stock_operation_obj . _search_and_increment ( cr , uid , picking_id , [ ( ' product_id ' , ' = ' , lot . product_id . id ) , ( ' lot_id ' , ' = ' , lot . id ) ] , filter_visible = True , visible_op_ids = visible_op_ids , increment = True , context = context )
2014-03-10 16:57:07 +00:00
answer [ ' operation_id ' ] = op_id
return answer
2013-06-26 13:10:04 +00:00
#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 :
2014-03-24 10:46:21 +00:00
op_id = stock_operation_obj . _search_and_increment ( cr , uid , picking_id , [ ( ' package_id ' , ' = ' , matching_package_ids [ 0 ] ) ] , filter_visible = True , visible_op_ids = visible_op_ids , increment = True , context = context )
2014-03-10 16:57:07 +00:00
answer [ ' operation_id ' ] = op_id
return answer
return answer
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 ' ) ,
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
]
2012-11-27 10:20:02 +00:00
2014-01-24 16:30:32 +00:00
def action_traceability ( self , cr , uid , ids , context = None ) :
2014-01-27 11:25:27 +00:00
""" It traces the information of lots
2014-01-24 16:30:32 +00:00
@param self : The object pointer .
@param cr : A database cursor
@param uid : ID of the user currently logged in
@param ids : List of IDs selected
@param context : A standard dictionary
@return : A dictionary of values
"""
quant_obj = self . pool . get ( " stock.quant " )
quants = quant_obj . search ( cr , uid , [ ( ' lot_id ' , ' in ' , ids ) ] , context = context )
moves = set ( )
for quant in quant_obj . browse ( cr , uid , quants , context = context ) :
2014-01-27 11:25:27 +00:00
moves | = { move . id for move in quant . history_ids }
2014-01-24 16:30:32 +00:00
if moves :
2014-02-05 17:24:44 +00:00
return {
' domain ' : " [( ' id ' , ' in ' ,[ " + ' , ' . join ( map ( str , list ( moves ) ) ) + " ])] " ,
2014-01-27 11:25:27 +00:00
' name ' : _ ( ' Traceability ' ) ,
2014-01-24 16:55:03 +00:00
' view_mode ' : ' tree,form ' ,
' view_type ' : ' form ' ,
2014-01-27 09:47:35 +00:00
' context ' : { ' tree_view_ref ' : ' stock.view_move_tree ' } ,
2014-01-24 16:30:32 +00:00
' res_model ' : ' stock.move ' ,
' type ' : ' ir.actions.act_window ' ,
}
2014-01-27 11:25:27 +00:00
return False
2014-01-24 16:30:32 +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 ) :
2013-09-05 10:34:35 +00:00
""" Returns the unit price to store on the quant """
2013-09-03 16:27:57 +00:00
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 ) :
2013-09-05 10:34:35 +00:00
name = line . location_id . name + ' > ' + line . location_dest_id . name
2012-09-11 16:14:35 +00:00
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-10-29 15:09:59 +00:00
def create ( self , cr , uid , vals , context = None ) :
if vals . get ( ' product_id ' ) and not vals . get ( ' price_unit ' ) :
prod_obj = self . pool . get ( ' product.product ' )
vals [ ' price_unit ' ] = prod_obj . browse ( cr , uid , vals [ ' product_id ' ] , context = context ) . standard_price
return super ( stock_move , self ) . create ( cr , uid , vals , context = context )
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 ) :
2014-02-28 17:32:41 +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 , context = context )
2013-06-30 14:10:14 +00:00
return res
2013-06-30 13:41:05 +00:00
def _get_remaining_qty ( self , cr , uid , ids , field_name , args , context = None ) :
2013-11-18 09:50:21 +00:00
uom_obj = self . pool . get ( ' product.uom ' )
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-11-15 17:55:05 +00:00
qty = move . product_qty
for record in move . linked_move_operation_ids :
qty - = record . qty
#converting the remaining quantity in the move UoM
2014-02-28 17:32:41 +00:00
res [ move . id ] = uom_obj . _compute_qty_obj ( cr , uid , move . product_id . uom_id , qty , move . product_uom , round = False , context = context )
2013-06-30 13:41:05 +00:00
return res
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
2014-01-28 10:41:52 +00:00
def _get_string_qty_information ( self , cr , uid , ids , field_name , args , context = None ) :
2014-02-04 10:52:31 +00:00
settings_obj = self . pool . get ( ' stock.config.settings ' )
2014-02-07 08:40:00 +00:00
uom_obj = self . pool . get ( ' product.uom ' )
2014-02-03 09:31:15 +00:00
res = dict . fromkeys ( ids , ' ' )
2014-01-28 10:41:52 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2014-02-04 10:52:31 +00:00
if move . state in ( ' draft ' , ' done ' , ' cancel ' ) or move . location_id . usage != ' internal ' :
2014-02-07 16:17:56 +00:00
res [ move . id ] = ' ' # 'not applicable' or 'n/a' could work too
2014-02-03 09:31:15 +00:00
continue
2014-02-04 10:52:31 +00:00
total_available = min ( move . product_qty , move . reserved_availability + move . availability )
2014-02-28 17:32:41 +00:00
total_available = uom_obj . _compute_qty_obj ( cr , uid , move . product_id . uom_id , total_available , move . product_uom , context = context )
2014-02-04 10:52:31 +00:00
info = str ( total_available )
#look in the settings if we need to display the UoM name or not
config_ids = settings_obj . search ( cr , uid , [ ] , limit = 1 , order = ' id DESC ' , context = context )
if config_ids :
stock_settings = settings_obj . browse ( cr , uid , config_ids [ 0 ] , context = context )
if stock_settings . group_uom :
2014-02-07 08:40:00 +00:00
info + = ' ' + move . product_uom . name
2014-02-03 09:31:15 +00:00
if move . reserved_availability :
2014-02-04 10:52:31 +00:00
if move . reserved_availability != total_available :
2014-02-03 09:31:15 +00:00
#some of the available quantity is assigned and some are available but not reserved
2014-02-28 17:32:41 +00:00
reserved_available = uom_obj . _compute_qty_obj ( cr , uid , move . product_id . uom_id , move . reserved_availability , move . product_uom , context = context )
2014-02-07 08:40:00 +00:00
info + = _ ( ' ( %s reserved) ' ) % str ( reserved_available )
2014-02-03 09:31:15 +00:00
else :
#all available quantity is assigned
info + = _ ( ' (reserved) ' )
2014-01-28 10:41:52 +00:00
res [ move . id ] = info
return res
def _get_reserved_availability ( self , cr , uid , ids , field_name , args , context = None ) :
2014-02-03 09:31:15 +00:00
res = dict . fromkeys ( ids , 0 )
2014-01-28 10:41:52 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2014-02-03 09:31:15 +00:00
res [ move . id ] = sum ( [ quant . qty for quant in move . reserved_quant_ids ] )
2014-01-28 10:41:52 +00:00
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 )
2014-01-16 10:55:56 +00:00
def _get_move_ids ( self , cr , uid , ids , context = None ) :
res = [ ]
for picking in self . browse ( cr , uid , ids , context = context ) :
res + = [ x . id for x in picking . move_lines ]
return res
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 ) ] } ) ,
2013-12-06 10:31:50 +00:00
' date_expected ' : fields . datetime ( ' Expected Date ' , states = { ' done ' : [ ( ' readonly ' , True ) ] } , required = True , select = True , help = " Scheduled date for the processing of this move " ) ,
2013-11-18 13:36:50 +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
2014-02-24 11:24:37 +00:00
' product_qty ' : fields . function ( _quantity_normalize , type = ' float ' , store = { ' stock.move ' : ( lambda self , cr , uid , ids , ctx : ids , [ ' product_uom_qty ' , ' product_uom ' ] , 20 ) } , 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 ' ) ,
2013-11-18 13:36:50 +00:00
required = True , states = { ' done ' : [ ( ' readonly ' , True ) ] } ,
2012-09-29 10:30:13 +00:00
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. "
) ,
2013-11-18 13:36:50 +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
2013-11-18 13:36:50 +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
2013-10-21 09:55:44 +00:00
' picking_id ' : fields . many2one ( ' stock.picking ' , ' Reference ' , select = True , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
2014-01-16 10:55:56 +00:00
' picking_priority ' : fields . related ( ' picking_id ' , ' priority ' , type = ' selection ' , selection = [ ( ' 0 ' , ' Low ' ) , ( ' 1 ' , ' Normal ' ) , ( ' 2 ' , ' High ' ) ] , string = ' Picking Priority ' , store = { ' stock.picking ' : ( _get_move_ids , [ ' priority ' ] , 10 ) } ) ,
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 \' . " ) ,
2014-03-19 16:33:59 +00:00
' partially_available ' : fields . boolean ( ' Partially Available ' , readonly = True , help = " Checks if the move has some stock reserved " ) ,
2013-10-29 15:09:59 +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 costing method used is ' average price ' or ' real ' ). Value given in company currency and in product uom. " ) , # as it's a technical field, we intentionally don't provide the digits attribute
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 ) ,
2014-02-05 17:24:44 +00:00
' split_from ' : fields . many2one ( ' stock.move ' , string = " Move Split From " , help = " Technical field used to track the origin of a split move, which can be useful in case of debug " ) ,
2013-11-18 09:50:21 +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-11-18 09:50:21 +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:
2013-11-18 13:36:50 +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-11-18 09:50:21 +00:00
' quant_ids ' : fields . many2many ( ' stock.quant ' , ' stock_quant_move_rel ' , ' move_id ' , ' quant_id ' , ' Moved Quants ' ) ,
2013-06-29 22:17:03 +00:00
' reserved_quant_ids ' : fields . one2many ( ' stock.quant ' , ' reservation_id ' , ' Reserved quants ' ) ,
2013-11-15 17:55:05 +00:00
' linked_move_operation_ids ' : fields . one2many ( ' stock.move.operation.link ' , ' move_id ' , string = ' Linked Operations ' , readonly = True , help = ' Operations that impact this move for the computation of the remaining quantities ' ) ,
2013-11-18 09:50:21 +00:00
' remaining_qty ' : fields . function ( _get_remaining_qty , type = ' float ' , string = ' Remaining Quantity ' ,
2013-11-15 17:55:05 +00:00
digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) , states = { ' done ' : [ ( ' readonly ' , True ) ] } , ) ,
2013-08-04 13:40:14 +00:00
' procurement_id ' : fields . many2one ( ' procurement.order ' , ' Procurement ' ) ,
2013-07-19 09:48:38 +00:00
' group_id ' : fields . many2one ( ' procurement.group ' , ' Procurement Group ' ) ,
2013-11-07 11:20:53 +00:00
' rule_id ' : fields . many2one ( ' procurement.rule ' , ' Procurement Rule ' , help = ' The pull rule that created this stock move ' ) ,
2013-12-11 10:25:11 +00:00
' push_rule_id ' : fields . many2one ( ' stock.location.path ' , ' Push Rule ' , help = ' The push rule that created this stock move ' ) ,
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 ' ) ,
2014-01-28 10:41:52 +00:00
' reserved_availability ' : fields . function ( _get_reserved_availability , type = ' float ' , string = ' Quantity Reserved ' , readonly = True , help = ' Quantity that has already been reserved for this move ' ) ,
2014-02-03 09:31:15 +00:00
' availability ' : fields . function ( _get_product_availability , type = ' float ' , string = ' Quantity Available ' , readonly = True , help = ' Quantity in stock that can still be reserved for this move ' ) ,
' string_availability_info ' : fields . function ( _get_string_qty_information , type = ' text ' , string = ' Availability ' , readonly = True , help = ' Show various information on stock availability for this move ' ) ,
2013-09-16 07:52:08 +00:00
' restrict_lot_id ' : fields . many2one ( ' stock.production.lot ' , ' Lot ' , help = " Technical field used to depict a restriction on the lot of quants to consider when marking this move as ' done ' " ) ,
' restrict_partner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' , help = " Technical field used to depict a restriction on the ownership of quants to consider when marking this move as ' done ' " ) ,
2013-10-04 14:56:59 +00:00
' route_ids ' : fields . many2many ( ' stock.location.route ' , ' stock_location_route_move ' , ' move_id ' , ' route_id ' , ' Destination route ' , help = " Preferred route to be followed by the procurement order " ) ,
2013-10-22 12:06:05 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , help = " Technical field depicting the warehouse to consider for the route selection on the next procurement (if any). " ) ,
2013-10-04 14:56:59 +00:00
}
2012-09-21 15:02:06 +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 ,
2013-12-11 10:25:11 +00:00
' date ' : fields . datetime . now ,
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 ) ,
2013-12-11 10:25:11 +00:00
' date_expected ' : fields . datetime . now ,
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
2014-03-19 16:33:59 +00:00
def _check_uom ( self , cr , uid , ids , context = None ) :
for move in self . browse ( cr , uid , ids , context = context ) :
if move . product_id . uom_id . category_id . id != move . product_uom . category_id . id :
return False
return True
_constraints = [
( _check_uom ,
' You try to move a product using a UoM that is not compatible with the UoM of the product moved. Please use an UoM in the same UoM category. ' ,
[ ' product_uom ' ] ) ,
]
2013-11-18 10:57:17 +00:00
def copy_data ( self , cr , uid , id , default = None , context = None ) :
2013-11-12 14:26:56 +00:00
if default is None :
default = { }
default = default . copy ( )
default [ ' move_orig_ids ' ] = [ ]
default [ ' quant_ids ' ] = [ ]
2014-02-20 14:15:31 +00:00
default [ ' move_dest_id ' ] = False
2013-11-12 14:26:56 +00:00
default [ ' reserved_quant_ids ' ] = [ ]
default [ ' returned_move_ids ' ] = [ ]
2013-11-18 10:57:17 +00:00
default [ ' linked_move_operation_ids ' ] = [ ]
2014-03-28 10:49:06 +00:00
default [ ' partially_available ' ] = False
2014-01-29 15:56:38 +00:00
if not default . get ( ' origin_returned_move_id ' ) :
default [ ' origin_returned_move_id ' ] = False
2013-11-12 14:26:56 +00:00
default [ ' state ' ] = ' draft '
2013-11-18 10:57:17 +00:00
return super ( stock_move , self ) . copy_data ( cr , uid , id , default , context )
2013-11-12 14:26:56 +00:00
2013-11-14 21:14:28 +00:00
def do_unreserve ( self , cr , uid , move_ids , context = None ) :
quant_obj = self . pool . get ( " stock.quant " )
for move in self . browse ( cr , uid , move_ids , context = context ) :
2014-02-06 12:38:52 +00:00
if move . state in ( ' done ' , ' cancel ' ) :
raise osv . except_osv ( _ ( ' Operation Forbidden! ' ) , _ ( ' Cannot unreserve a done move ' ) )
2013-11-19 15:33:00 +00:00
quant_obj . quants_unreserve ( cr , uid , move , context = context )
2014-03-03 16:42:10 +00:00
self . write ( cr , uid , [ move . id ] , { ' state ' : ' confirmed ' } , context = context )
2013-11-12 14:26:56 +00:00
2013-09-11 10:32:23 +00:00
def _prepare_procurement_from_move ( self , cr , uid , move , context = None ) :
2013-11-07 11:20:53 +00:00
origin = ( move . group_id and ( move . group_id . name + " : " ) or " " ) + ( move . rule_id and move . rule_id . name or " / " )
group_id = move . group_id and move . group_id . id or False
if move . rule_id :
if move . rule_id . group_propagation_option == ' fixed ' and move . rule_id . group_id :
group_id = move . rule_id . group_id . id
elif move . rule_id . group_propagation_option == ' none ' :
group_id = False
return {
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 ,
2013-11-07 11:20:53 +00:00
' group_id ' : group_id ,
' route_ids ' : [ ( 4 , x . id ) for x in move . route_ids ] ,
2013-10-22 12:06:05 +00:00
' warehouse_id ' : move . warehouse_id and move . warehouse_id . id or False ,
2013-09-11 10:32:23 +00:00
}
2013-12-11 10:25:11 +00:00
def _push_apply ( self , cr , uid , moves , context = None ) :
2013-10-04 14:56:59 +00:00
push_obj = self . pool . get ( " stock.location.path " )
for move in moves :
2014-02-06 12:38:52 +00:00
#1) if the move is already chained, there is no need to check push rules
#2) if the move is a returned move, we don't want to check push rules, as returning a returned move is the only decent way
# to receive goods without triggering the push rules again (which would duplicate chained operations)
if not move . move_dest_id and not move . origin_returned_move_id :
2013-12-11 10:25:11 +00:00
domain = [ ( ' location_from_id ' , ' = ' , move . location_dest_id . id ) ]
2013-12-13 13:26:41 +00:00
if move . warehouse_id :
2013-12-11 10:25:11 +00:00
domain + = [ ' | ' , ( ' warehouse_id ' , ' = ' , move . warehouse_id . id ) , ( ' warehouse_id ' , ' = ' , False ) ]
#priority goes to the route defined on the product and product category
route_ids = [ x . id for x in move . product_id . route_ids + move . product_id . categ_id . total_route_ids ]
rules = push_obj . search ( cr , uid , domain + [ ( ' route_id ' , ' in ' , route_ids ) ] , order = ' route_sequence, sequence ' , context = context )
if not rules :
#but if there's no rule matching, we try without filtering on routes
rules = push_obj . search ( cr , uid , domain , order = ' route_sequence, sequence ' , context = context )
if rules :
rule = push_obj . browse ( cr , uid , rules [ 0 ] , context = context )
push_obj . _apply ( cr , uid , rule , move , context = context )
2013-10-04 14:56:59 +00:00
return True
2013-09-11 10:32:23 +00:00
2013-07-09 13:41:21 +00:00
def _create_procurement ( self , cr , uid , move , context = None ) :
2013-11-18 13:36:50 +00:00
""" This will create a procurement order """
return self . pool . get ( " procurement.order " ) . create ( cr , uid , self . _prepare_procurement_from_move ( cr , uid , move , context = context ) )
2013-07-09 13:41:21 +00:00
2010-08-31 06:39:28 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2013-12-13 13:26:41 +00:00
if context is None :
context = { }
2011-03-29 12:27:12 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2013-12-11 10:25:11 +00:00
# Check that we do not modify a stock.move which is done
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). ' ) )
2013-12-13 13:26:41 +00:00
propagated_changes_dict = { }
#propagation of quantity change
if vals . get ( ' product_uom_qty ' ) :
propagated_changes_dict [ ' product_uom_qty ' ] = vals [ ' product_uom_qty ' ]
if vals . get ( ' product_uom_id ' ) :
propagated_changes_dict [ ' product_uom_id ' ] = vals [ ' product_uom_id ' ]
#propagation of expected date:
2013-12-11 10:25:11 +00:00
propagated_date_field = False
if vals . get ( ' date_expected ' ) :
#propagate any manual change of the expected date
propagated_date_field = ' date_expected '
elif ( vals . get ( ' state ' , ' ' ) == ' done ' and vals . get ( ' date ' ) ) :
#propagate also any delta observed when setting the move as done
propagated_date_field = ' date '
2013-12-13 13:26:41 +00:00
if not context . get ( ' do_not_propagate ' , False ) and ( propagated_date_field or propagated_changes_dict ) :
#any propagation is (maybe) needed
2013-12-11 10:25:11 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2013-12-13 13:26:41 +00:00
if move . move_dest_id and move . propagate :
if ' date_expected ' in propagated_changes_dict :
propagated_changes_dict . pop ( ' date_expected ' )
if propagated_date_field :
current_date = datetime . strptime ( move . date_expected , DEFAULT_SERVER_DATETIME_FORMAT )
new_date = datetime . strptime ( vals . get ( propagated_date_field ) , DEFAULT_SERVER_DATETIME_FORMAT )
delta = new_date - current_date
if abs ( delta . days ) > = move . company_id . propagation_minimum_delta :
old_move_date = datetime . strptime ( move . move_dest_id . date_expected , DEFAULT_SERVER_DATETIME_FORMAT )
new_move_date = ( old_move_date + relativedelta . relativedelta ( days = delta . days or 0 ) ) . strftime ( DEFAULT_SERVER_DATETIME_FORMAT )
propagated_changes_dict [ ' date_expected ' ] = new_move_date
#For pushed moves as well as for pulled moves, propagate by recursive call of write().
#Note that, for pulled moves we intentionally don't propagate on the procurement.
if propagated_changes_dict :
self . write ( cr , uid , [ move . move_dest_id . id ] , propagated_changes_dict , context = context )
2013-11-18 13:36:50 +00:00
return super ( stock_move , self ) . write ( cr , uid , ids , vals , context = context )
2010-09-01 08:21:28 +00:00
2013-11-18 13:36:50 +00:00
def onchange_quantity ( self , cr , uid , ids , product_id , product_qty , product_uom , product_uos ) :
2010-05-26 12:59:30 +00:00
""" 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
2013-11-18 13:36:50 +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 ( {
2013-11-18 13:36:50 +00:00
' 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 "
2013-11-18 13:36:50 +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
2013-11-18 13:36:50 +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 ( {
2013-11-18 13:36:50 +00:00
' title ' : _ ( ' Warning: No Back Order ' ) ,
' message ' : _ ( " By changing the quantity here, you accept the "
2012-02-29 11:08:20 +00:00
" new quantity as complete: OpenERP will not "
2013-11-18 13:36:50 +00:00
" automatically generate a Back Order. " ) } )
2012-02-29 11:08:20 +00:00
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
2013-11-18 13:36:50 +00:00
def onchange_product_id ( self , cr , uid , ids , prod_id = False , loc_id = False , 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 { }
2013-06-26 13:08:20 +00:00
user = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid )
lang = user and user . lang or 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 ]
2013-11-18 13:36:50 +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 ,
2013-11-18 13:36:50 +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
2014-03-18 11:57:46 +00:00
def _picking_assign ( self , cr , uid , move_ids , procurement_group , location_from , location_to , context = None ) :
""" Assign a picking on the given move_ids, which is a list of move supposed to share the same procurement_group, location_from and location_to
( and company ) . Those attributes are also given as parameters .
"""
2013-07-17 13:51:42 +00:00
pick_obj = self . pool . get ( " stock.picking " )
2013-07-29 21:44:43 +00:00
picks = pick_obj . search ( cr , uid , [
2014-03-18 11:57:46 +00:00
( ' group_id ' , ' = ' , procurement_group ) ,
( ' location_id ' , ' = ' , location_from ) ,
( ' location_dest_id ' , ' = ' , location_to ) ,
2013-10-18 12:20:34 +00:00
( ' state ' , ' in ' , [ ' draft ' , ' confirmed ' , ' waiting ' ] ) ] , context = context )
2013-07-17 13:51:42 +00:00
if picks :
pick = picks [ 0 ]
else :
2014-03-18 11:57:46 +00:00
move = self . browse ( cr , uid , move_ids , context = context ) [ 0 ]
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
' 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 )
2014-03-18 11:57:46 +00:00
return self . write ( cr , uid , move_ids , { ' picking_id ' : pick } , context = context )
2014-02-25 16:22:11 +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 :
2013-12-11 10:25:11 +00:00
date_expected = time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT )
2013-11-19 15:33:00 +00:00
return { ' value ' : { ' date ' : date_expected } }
2011-05-03 07:25:13 +00:00
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 .
"""
2014-02-14 10:50:04 +00:00
if isinstance ( ids , ( int , long ) ) :
2014-02-10 16:03:46 +00:00
ids = [ ids ]
2013-06-29 22:17:03 +00:00
states = {
' confirmed ' : [ ] ,
' waiting ' : [ ]
}
2014-03-18 11:57:46 +00:00
to_assign = { }
2013-06-29 22:17:03 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
state = ' confirmed '
2014-02-18 09:31:49 +00:00
#if the move is preceeded, then it's waiting (if preceeding move is done, then action_assign has been called already and its state is already available)
if move . move_orig_ids :
state = ' waiting '
#if the move is split and some of the ancestor was preceeded, then it's waiting as well
elif move . split_from :
move2 = move . split_from
while move2 and state != ' waiting ' :
if move2 . move_orig_ids :
state = ' waiting '
move2 = move2 . split_from
2013-06-29 22:17:03 +00:00
states [ state ] . append ( move . id )
2014-03-18 11:57:46 +00:00
if not move . picking_id and move . picking_type_id :
key = ( move . group_id . id , move . location_id . id , move . location_dest_id . id )
if key not in to_assign :
to_assign [ key ] = [ ]
to_assign [ key ] . append ( move . id )
2013-06-29 22:17:03 +00:00
2014-02-17 15:03:50 +00:00
for move in self . browse ( cr , uid , states [ ' confirmed ' ] , context = context ) :
if move . procure_method == ' make_to_order ' :
self . _create_procurement ( cr , uid , move , context = context )
states [ ' waiting ' ] . append ( move . id )
states [ ' confirmed ' ] . remove ( move . id )
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 } )
2014-03-18 11:57:46 +00:00
#assign picking in batch for all confirmed move that share the same details
for key , move_ids in to_assign . items ( ) :
procurement_group , location_from , location_to = key
self . _picking_assign ( cr , uid , move_ids , procurement_group , location_from , location_to , context = context )
2013-10-04 14:56:59 +00:00
moves = self . browse ( cr , uid , ids , context = context )
self . _push_apply ( cr , uid , moves , context = context )
2014-02-10 14:54:46 +00:00
return ids
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
"""
2014-01-29 10:04:10 +00:00
#check putaway method
2014-01-06 16:41:09 +00:00
return self . write ( cr , uid , ids , { ' state ' : ' assigned ' } )
2013-07-29 21:44:43 +00:00
2014-01-07 10:39:15 +00:00
def check_tracking ( self , cr , uid , move , lot_id , context = None ) :
""" Checks if serial number is assigned to stock move or not and raise an error if it had to.
"""
check = False
2014-01-07 12:30:38 +00:00
if move . product_id . track_all and not move . location_dest_id . usage == ' inventory ' :
2014-01-07 10:39:15 +00:00
check = True
elif move . product_id . track_incoming and move . location_id . usage in ( ' supplier ' , ' transit ' , ' inventory ' ) and move . location_dest_id . usage == ' internal ' :
check = True
2014-01-07 12:30:38 +00:00
elif move . product_id . track_outgoing and move . location_dest_id . usage in ( ' customer ' , ' transit ' ) and move . location_id . usage == ' internal ' :
2014-01-07 10:39:15 +00:00
check = True
if check and not lot_id :
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' You must assign a serial number for the product %s ' ) % ( move . product_id . name ) )
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.
"""
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 " )
2014-01-06 16:09:17 +00:00
to_assign_moves = [ ]
2014-02-13 14:44:49 +00:00
main_domain = { }
todo_moves = [ ]
operations = set ( )
2010-06-21 18:40:58 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2013-11-18 10:57:17 +00:00
if move . state not in ( ' confirmed ' , ' waiting ' , ' assigned ' ) :
2013-06-29 22:17:03 +00:00
continue
2014-02-20 14:05:06 +00:00
if move . picking_type_id and move . picking_type_id . auto_force_assign :
2014-01-06 16:09:17 +00:00
to_assign_moves . append ( move . id )
2014-02-21 09:55:16 +00:00
#in case the move is returned, we want to try to find quants before forcing the assignment
2014-02-20 14:05:06 +00:00
if not move . origin_returned_move_id :
2014-02-20 10:14:35 +00:00
continue
2013-06-29 22:17:03 +00:00
if move . product_id . type == ' consu ' :
2014-01-06 16:09:17 +00:00
to_assign_moves . append ( move . id )
2008-07-22 15:11:28 +00:00
continue
2010-08-30 13:31:38 +00:00
else :
2014-02-13 14:44:49 +00:00
todo_moves . append ( move )
2014-02-18 10:28:53 +00:00
2013-11-19 15:33:00 +00:00
#we always keep the quants already assigned and try to find the remaining quantity on quants not assigned only
2014-02-13 14:44:49 +00:00
main_domain [ move . id ] = [ ( ' reservation_id ' , ' = ' , False ) , ( ' qty ' , ' > ' , 0 ) ]
2014-02-18 09:31:49 +00:00
#if the move is preceeded, restrict the choice of quants in the ones moved previously in original move
2014-02-14 10:18:47 +00:00
move_orig_ids = [ ]
move2 = move
while move2 :
move_orig_ids + = [ x . id for x in move2 . move_orig_ids ]
2014-02-28 11:00:48 +00:00
#loop on the split_from to find the ancestor of split moves only if the move has not direct ancestor (priority goes to them)
move2 = not move2 . move_orig_ids and move2 . split_from or False
2014-02-14 10:18:47 +00:00
if move_orig_ids :
2014-02-18 10:28:53 +00:00
main_domain [ move . id ] + = [ ( ' history_ids ' , ' in ' , move_orig_ids ) ]
2014-02-14 10:18:47 +00:00
2014-02-05 17:24:44 +00:00
#if the move is returned from another, restrict the choice of quants to the ones that follow the returned move
2014-01-30 09:53:28 +00:00
if move . origin_returned_move_id :
2014-02-13 14:44:49 +00:00
main_domain [ move . id ] + = [ ( ' history_ids ' , ' in ' , move . origin_returned_move_id . id ) ]
for link in move . linked_move_operation_ids :
operations . add ( link . operation_id )
2014-02-18 10:25:03 +00:00
# Check all ops and sort them: we want to process first the packages, then operations with lot then the rest
2014-02-13 14:44:49 +00:00
operations = list ( operations )
2014-02-18 10:25:03 +00:00
operations . sort ( key = lambda x : ( ( x . package_id and not x . product_id ) and - 4 or 0 ) + ( x . package_id and - 2 or 0 ) + ( x . lot_id and - 1 or 0 ) )
2014-02-13 14:44:49 +00:00
for ops in operations :
#first try to find quants based on specific domains given by linked operations
for record in ops . linked_move_operation_ids :
move = record . move_id
2014-03-13 14:20:02 +00:00
if move . id in main_domain :
domain = main_domain [ move . id ] + self . pool . get ( ' stock.move.operation.link ' ) . get_specific_domain ( cr , uid , record , context = context )
2014-03-26 08:56:57 +00:00
qty = record . qty
if qty :
quants = quant_obj . quants_get_prefered_domain ( cr , uid , ops . location_id , move . product_id , qty , domain = domain , prefered_domain = [ ] , fallback_domain = [ ] , restrict_lot_id = move . restrict_lot_id . id , restrict_partner_id = move . restrict_partner_id . id , context = context )
quant_obj . quants_reserve ( cr , uid , quants , move , record , context = context )
2014-02-13 14:44:49 +00:00
for move in todo_moves :
2014-04-01 12:28:54 +00:00
move . refresh ( )
2014-02-13 14:44:49 +00:00
#then if the move isn't totally assigned, try to find quants without any specific domain
if move . state != ' assigned ' :
2014-02-20 08:35:19 +00:00
qty_already_assigned = move . reserved_availability
2014-02-13 14:44:49 +00:00
qty = move . product_qty - qty_already_assigned
2014-02-28 17:32:57 +00:00
quants = quant_obj . quants_get_prefered_domain ( cr , uid , move . location_id , move . product_id , qty , domain = main_domain [ move . id ] , prefered_domain = [ ] , fallback_domain = [ ] , restrict_lot_id = move . restrict_lot_id . id , restrict_partner_id = move . restrict_partner_id . id , context = context )
2014-02-13 14:44:49 +00:00
quant_obj . quants_reserve ( cr , uid , quants , move , context = context )
2013-11-19 15:33:00 +00:00
2014-02-18 13:28:43 +00:00
#force assignation of consumable products and picking type auto_force_assign
2014-01-06 16:09:17 +00:00
if to_assign_moves :
self . force_assign ( cr , uid , to_assign_moves , context = context )
2010-06-16 08:40:57 +00:00
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-10-21 15:12:35 +00:00
procurement_obj = self . pool . get ( ' procurement.order ' )
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-09-13 06:42:03 +00:00
if move . state == ' done ' :
raise osv . except_osv ( _ ( ' Operation Forbidden! ' ) ,
_ ( ' You cannot cancel a stock move that has been set to \' Done \' . ' ) )
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-10-21 15:12:35 +00:00
if context . get ( ' cancel_procurement ' ) :
if move . propagate :
procurement_ids = procurement_obj . search ( cr , uid , [ ( ' move_dest_id ' , ' = ' , move . id ) ] , context = context )
procurement_obj . cancel ( cr , uid , procurement_ids , context = context )
elif move . move_dest_id :
#cancel chained moves
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 ' :
2014-03-13 09:37:27 +00:00
self . write ( cr , uid , [ move . move_dest_id . id ] , { ' state ' : ' confirmed ' } , context = context )
return self . write ( cr , uid , ids , { ' state ' : ' cancel ' , ' move_dest_id ' : False } , context = context )
2013-06-29 22:17:03 +00:00
2014-03-19 16:33:59 +00:00
def _check_package_from_moves ( self , cr , uid , ids , context = None ) :
2014-02-04 15:49:54 +00:00
pack_obj = self . pool . get ( " stock.quant.package " )
2014-02-06 15:31:28 +00:00
packs = set ( )
2014-03-19 16:33:59 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
packs | = set ( [ q . package_id . id for q in move . quant_ids if q . package_id and q . qty > 0 ] )
2014-02-06 15:31:28 +00:00
return pack_obj . _check_location_constraint ( cr , uid , list ( packs ) , context = context )
2014-02-04 10:32:09 +00:00
2013-11-14 21:14:28 +00:00
def action_done ( self , cr , uid , ids , context = None ) :
2014-02-06 14:57:41 +00:00
""" Process completly the moves given as ids and if all moves are done, it will finish the picking.
"""
2013-06-30 08:55:03 +00:00
context = context or { }
2014-01-06 14:30:02 +00:00
picking_obj = self . pool . get ( " stock.picking " )
2013-06-30 08:55:03 +00:00
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 :
2014-02-10 14:54:46 +00:00
ids = self . action_confirm ( cr , uid , todo , context = context )
2013-07-26 12:55:32 +00:00
pickings = set ( )
2013-10-09 08:04:52 +00:00
procurement_ids = [ ]
2014-02-04 10:32:09 +00:00
#Search operations that are linked to the moves
operations = set ( )
2014-02-06 14:57:41 +00:00
move_qty = { }
2013-07-16 10:15:40 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2014-02-04 11:36:53 +00:00
move_qty [ move . id ] = move . product_qty
2014-02-04 10:32:09 +00:00
for link in move . linked_move_operation_ids :
operations . add ( link . operation_id )
#Sort operations according to entire packages first, then package + lot, package only, lot only
2014-02-06 14:57:41 +00:00
operations = list ( operations )
2014-02-04 10:32:09 +00:00
operations . sort ( key = lambda x : ( ( x . package_id and not x . product_id ) and - 4 or 0 ) + ( x . package_id and - 2 or 0 ) + ( x . lot_id and - 1 or 0 ) )
for ops in operations :
if ops . picking_id :
pickings . add ( ops . picking_id . id )
2014-02-04 15:49:54 +00:00
main_domain = [ ( ' qty ' , ' > ' , 0 ) ]
for record in ops . linked_move_operation_ids :
move = record . move_id
2014-02-06 14:57:41 +00:00
self . check_tracking ( cr , uid , move , ops . package_id . id or ops . lot_id . id , context = context )
2014-03-25 15:06:38 +00:00
if record . reserved_quant_id :
quants = [ ( record . reserved_quant_id , record . qty ) ]
else :
2014-03-26 08:56:57 +00:00
prefered_domain = [ ( ' reservation_id ' , ' = ' , move . id ) ]
fallback_domain = [ ( ' reservation_id ' , ' = ' , False ) ]
2014-03-25 15:06:38 +00:00
dom = main_domain + self . pool . get ( ' stock.move.operation.link ' ) . get_specific_domain ( cr , uid , record , context = context )
quants = quant_obj . quants_get_prefered_domain ( cr , uid , ops . location_id , move . product_id , record . qty , domain = dom , prefered_domain = prefered_domain ,
2014-02-04 15:49:54 +00:00
fallback_domain = fallback_domain , restrict_lot_id = move . restrict_lot_id . id , restrict_partner_id = move . restrict_partner_id . id , context = context )
2014-03-19 16:33:59 +00:00
if ops . result_package_id . id :
#if a result package is given, all quants go there
quant_dest_package_id = ops . result_package_id . id
elif ops . product_id and ops . package_id :
#if a package and a product is given, we will remove quants from the pack.
quant_dest_package_id = False
2014-03-12 09:45:44 +00:00
else :
2014-03-19 16:33:59 +00:00
#otherwise we keep the current pack of the quant, which may mean None
quant_dest_package_id = ops . package_id . id
quant_obj . quants_move ( cr , uid , quants , move , ops . location_dest_id , lot_id = ops . lot_id . id , owner_id = ops . owner_id . id , src_package_id = ops . package_id . id , dest_package_id = quant_dest_package_id , context = context )
2014-03-12 09:45:44 +00:00
# Handle pack in pack
2014-03-19 16:33:59 +00:00
if not ops . product_id and ops . package_id and ops . result_package_id . id != ops . package_id . parent_id . id :
2014-03-26 08:56:57 +00:00
self . pool . get ( ' stock.quant.package ' ) . write ( cr , SUPERUSER_ID , [ ops . package_id . id ] , { ' parent_id ' : ops . result_package_id . id } , context = context )
2014-02-04 15:49:54 +00:00
move_qty [ move . id ] - = record . qty
#Check for remaining qtys and unreserve/check move_dest_id in
2014-02-04 10:57:43 +00:00
for move in self . browse ( cr , uid , ids , context = context ) :
2014-02-04 15:49:54 +00:00
if move_qty [ move . id ] > 0 : #(=In case no pack operations in picking)
2014-02-04 11:36:53 +00:00
main_domain = [ ( ' qty ' , ' > ' , 0 ) ]
prefered_domain = [ ( ' reservation_id ' , ' = ' , move . id ) ]
fallback_domain = [ ( ' reservation_id ' , ' = ' , False ) ]
2014-02-04 10:57:43 +00:00
self . check_tracking ( cr , uid , move , move . restrict_lot_id . id , context = context )
qty = move_qty [ move . id ]
quants = quant_obj . quants_get_prefered_domain ( cr , uid , move . location_id , move . product_id , qty , domain = main_domain , prefered_domain = prefered_domain , fallback_domain = fallback_domain , restrict_lot_id = move . restrict_lot_id . id , restrict_partner_id = move . restrict_partner_id . id , context = context )
2014-03-11 13:31:09 +00:00
quant_obj . quants_move ( cr , uid , quants , move , move . location_dest_id , lot_id = move . restrict_lot_id . id , owner_id = move . restrict_partner_id . id , context = context )
2013-11-19 15:33:00 +00:00
#unreserve the quants and make them available for other operations/moves
2014-02-04 10:57:43 +00:00
quant_obj . quants_unreserve ( cr , uid , move , context = context )
2014-02-06 14:57:41 +00:00
2014-02-04 10:57:43 +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 )
if move . procurement_id :
procurement_ids . append ( move . procurement_id . id )
2014-02-04 15:49:54 +00:00
# Check the packages have been placed in the correct locations
2014-03-19 16:33:59 +00:00
self . _check_package_from_moves ( cr , uid , ids , context = context )
2014-02-04 10:57:43 +00:00
# Apply on picking
2014-03-14 13:59:38 +00:00
done_date = context . get ( ' force_date ' , time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) )
self . write ( cr , uid , ids , { ' state ' : ' done ' , ' date ' : done_date } , context = context )
2014-02-04 10:57:43 +00:00
self . pool . get ( ' procurement.order ' ) . check ( cr , uid , procurement_ids , context = context )
#check picking state to set the date_done is needed
done_picking = [ ]
for picking in picking_obj . browse ( cr , uid , list ( pickings ) , context = context ) :
if picking . state == ' done ' and not picking . date_done :
done_picking . append ( picking . id )
if done_picking :
picking_obj . write ( cr , uid , done_picking , { ' date_done ' : time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) } , context = context )
return True
2014-02-04 10:32:09 +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
2014-01-15 14:16:59 +00:00
def action_scrap ( self , cr , uid , ids , quantity , location_id , restrict_lot_id = False , restrict_partner_id = False , 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
"""
2013-11-18 13:36:50 +00:00
#quantity should be given 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 ,
2014-01-06 16:09:17 +00:00
' restrict_lot_id ' : restrict_lot_id ,
2014-01-15 14:16:59 +00:00
' restrict_partner_id ' : restrict_partner_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
2014-01-24 15:49:53 +00:00
def split ( self , cr , uid , move , qty , restrict_lot_id = False , restrict_partner_id = False , context = None ) :
2013-12-17 10:16:22 +00:00
""" Splits qty from move move into a new move
: param move : browse record
: param qty : float . quantity to split ( given in product UoM )
2014-03-28 13:51:31 +00:00
: param restrict_lot_id : optional production lot that can be given in order to force the new move to restrict its choice of quants to this lot .
: param restrict_partner_id : optional partner that can be given in order to force the new move to restrict its choice of quants to the ones belonging to this partner .
2013-12-17 10:16:22 +00:00
: param context : dictionay . can contains the special key ' source_location_id ' in order to force the source location when copying the move
2014-02-14 08:46:50 +00:00
returns the ID of the backorder move created
2013-12-17 10:16:22 +00:00
"""
2014-02-14 08:46:50 +00:00
if move . state in ( ' done ' , ' cancel ' ) :
raise osv . except_osv ( _ ( ' Error ' ) , _ ( ' You cannot split a move done ' ) )
if move . state == ' draft ' :
#we restrict the split of a draft move because if not confirmed yet, it may be replaced by several other moves in
#case of phantom bom (with mrp module). And we don't want to deal with this complexity by copying the product that will explode.
raise osv . except_osv ( _ ( ' Error ' ) , _ ( ' You cannot split a draft move. It needs to be confirmed first. ' ) )
2013-12-17 10:16:22 +00:00
if move . product_qty < = qty or qty == 0 :
2013-08-06 07:25:51 +00:00
return move . id
2013-07-30 07:46:15 +00:00
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
2014-02-28 17:32:41 +00:00
uom_qty = uom_obj . _compute_qty_obj ( cr , uid , move . product_id . uom_id , qty , move . product_uom )
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 07:46:15 +00:00
defaults = {
' product_uom_qty ' : uom_qty ,
' product_uos_qty ' : uos_qty ,
' state ' : move . state ,
2013-12-13 13:26:41 +00:00
' procure_method ' : ' make_to_stock ' ,
2014-01-06 16:09:17 +00:00
' restrict_lot_id ' : restrict_lot_id ,
2014-01-28 10:58:09 +00:00
' restrict_partner_id ' : restrict_partner_id ,
' split_from ' : move . id ,
2013-07-30 07:46:15 +00:00
}
2013-12-17 10:16:22 +00:00
if context . get ( ' source_location_id ' ) :
defaults [ ' location_id ' ] = context [ ' source_location_id ' ]
2013-07-30 07:46:15 +00:00
new_move = self . copy ( cr , uid , move . id , defaults )
2006-12-07 13:41:40 +00:00
2013-12-13 13:26:41 +00:00
ctx = context . copy ( )
ctx [ ' do_not_propagate ' ] = True
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-12-13 13:26:41 +00:00
} , context = ctx )
2013-11-18 13:36:50 +00:00
2013-08-29 13:02:07 +00:00
if move . move_dest_id and move . propagate :
2013-09-12 16:05:46 +00:00
new_move_prop = self . split ( cr , uid , move . move_dest_id , qty , context = context )
self . write ( cr , uid , [ new_move ] , { ' move_dest_id ' : new_move_prop } , context = context )
2014-02-14 08:46:50 +00:00
#returning the first element of list returned by action_confirm is ok because we checked it wouldn't be exploded (and
#thus the result of action_confirm should always be a list of 1 element length)
return self . action_confirm ( cr , uid , [ new_move ] , context = context ) [ 0 ]
2013-07-12 16:54:49 +00:00
2014-01-28 15:41:25 +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 "
2010-12-15 06:26:29 +00:00
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
2013-11-08 16:52:17 +00:00
def _get_available_filters ( self , cr , uid , context = None ) :
"""
This function will return the list of filter allowed according to the options checked
in ' Settings \ Warehouse ' .
: rtype : list of tuple
"""
#default available choices
2014-01-20 14:49:58 +00:00
res_filter = [ ( ' none ' , _ ( ' All products of a whole location ' ) ) , ( ' product ' , _ ( ' One product only ' ) ) ]
2013-11-08 16:52:17 +00:00
settings_obj = self . pool . get ( ' stock.config.settings ' )
config_ids = settings_obj . search ( cr , uid , [ ] , limit = 1 , order = ' id DESC ' , context = context )
#If we don't have updated config until now, all fields are by default false and so should be not dipslayed
if not config_ids :
2013-11-05 16:15:17 +00:00
return res_filter
2013-11-08 16:52:17 +00:00
stock_settings = settings_obj . browse ( cr , uid , config_ids [ 0 ] , context = context )
if stock_settings . group_stock_tracking_owner :
2013-11-18 13:36:50 +00:00
res_filter . append ( ( ' owner ' , _ ( ' One owner only ' ) ) )
res_filter . append ( ( ' product_owner ' , _ ( ' One product for a specific owner ' ) ) )
2013-11-08 16:52:17 +00:00
if stock_settings . group_stock_tracking_lot :
2014-01-20 14:49:58 +00:00
res_filter . append ( ( ' lot ' , _ ( ' One Lot/Serial Number ' ) ) )
if stock_settings . group_stock_packaging :
2013-11-18 13:36:50 +00:00
res_filter . append ( ( ' pack ' , _ ( ' A Pack ' ) ) )
2013-11-05 16:15:17 +00:00
return res_filter
2013-08-06 12:30:08 +00:00
2014-03-17 15:04:09 +00:00
def _get_total_qty ( self , cr , uid , ids , field_name , args , context = None ) :
res = { }
for inv in self . browse ( cr , uid , ids , context = context ) :
res [ inv . id ] = sum ( [ x . product_qty for x in inv . line_ids ] )
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. " ) ,
2014-03-28 14:16:52 +00:00
' date ' : fields . datetime ( ' Inventory Date ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , help = " The date that will be used for the validation date of the stock move related to this inventory (and for the valuation accounting entries, if any) " ) ,
2013-08-27 09:49:00 +00:00
' line_ids ' : fields . one2many ( ' stock.inventory.line ' , ' inventory_id ' , ' Inventories ' , readonly = False , states = { ' done ' : [ ( ' readonly ' , True ) ] } , help = " Inventory Lines. " ) ,
2014-01-22 13:27:30 +00:00
' move_ids ' : fields . one2many ( ' stock.move ' , ' inventory_id ' , ' Created Moves ' , help = " Inventory Moves. " , states = { ' done ' : [ ( ' readonly ' , True ) ] } ) ,
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-05 23:23:48 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True , select = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2013-12-19 16:01:23 +00:00
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
2013-09-10 09:06:27 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , help = " Specify Product to focus your inventory on a particular Product. " ) ,
' package_id ' : fields . many2one ( ' stock.quant.package ' , ' Pack ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , help = " Specify Pack to focus your inventory on a particular Pack. " ) ,
' partner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , help = " Specify Owner to focus your inventory on a particular Owner. " ) ,
' lot_id ' : fields . many2one ( ' stock.production.lot ' , ' Lot/Serial Number ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , help = " Specify Lot/Serial Number to focus your inventory on a 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 ' ) ,
2013-11-08 16:52:17 +00:00
' filter ' : fields . selection ( _get_available_filters , ' Selection Filter ' ) ,
2014-03-17 15:04:09 +00:00
' total_qty ' : fields . function ( _get_total_qty , type = " float " ) ,
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 :
2013-11-08 16:52:17 +00:00
warehouse = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , ' stock ' , ' warehouse0 ' )
2013-10-10 12:03:31 +00:00
return warehouse . lot_stock_id . id
2013-08-05 23:23:48 +00:00
except :
return False
2008-07-22 15:11:28 +00:00
_defaults = {
2013-12-11 10:25:11 +00:00
' date ' : fields . datetime . now ,
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-10-25 13:40:23 +00:00
def set_checked_qty ( self , cr , uid , ids , context = None ) :
2013-08-27 10:06:38 +00:00
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 ( )
2014-03-14 13:59:38 +00:00
default . update ( { ' move_ids ' : [ ] } )
2012-01-06 12:59:14 +00:00
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 ) :
2014-01-30 08:07:13 +00:00
for inventory_line in inv . line_ids :
2014-02-13 09:04:27 +00:00
if inventory_line . product_qty < 0 and inventory_line . product_qty != inventory_line . th_qty :
2014-03-12 11:02:33 +00:00
raise osv . except_osv ( _ ( ' Warning ' ) , _ ( ' You cannot set a negative product quantity in an inventory line: \n \t %s - qty: %s ' % ( inventory_line . product_id . name , inventory_line . product_qty ) ) )
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 ( )
2014-03-14 13:59:38 +00:00
self . write ( cr , uid , [ inv . id ] , { ' state ' : ' done ' } , context = context )
2014-03-12 11:02:33 +00:00
#The inventory is posted as a single step which means quants cannot be moved from an internal location to another using an inventory
#as they will be moved to inventory loss, and other quants will be created to the encoded quant location. This is a normal behavior
#as quants cannot be reuse from inventory location (users can still manually move the products before/after the inventory if they want).
2014-03-14 13:59:38 +00:00
ctx = context . copy ( )
ctx [ ' force_date ' ] = inv . date
move_obj . action_done ( cr , uid , [ x . id for x in inv . move_ids ] , context = ctx )
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 ,
2013-09-13 06:42:03 +00:00
' state ' : ' assigned ' ,
2013-09-16 07:52:08 +00:00
' restrict_lot_id ' : todo_line . get ( ' prod_lot_id ' ) ,
' restrict_partner_id ' : todo_line . get ( ' partner_id ' ) ,
2013-08-05 23:23:48 +00:00
}
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 ' ]
2013-09-16 07:52:08 +00:00
return stock_move_obj . create ( cr , uid , vals , context = context )
2013-08-05 23:23:48 +00:00
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
self . action_cancel_draft ( cr , uid , ids , context = context )
2009-08-28 09:40:49 +00:00
2014-03-14 13:59:38 +00:00
def check_inventory_date ( self , cr , uid , inventory , context = None ) :
2014-03-26 13:51:47 +00:00
domain = [ ' | ' , ( ' location_id ' , ' child_of ' , [ inventory . location_id . id ] ) , ( ' location_dest_id ' , ' child_of ' , [ inventory . location_id . id ] ) , ( ' date ' , ' > ' , inventory . date ) , ' | ' , ( ' state ' , ' in ' , [ ' assigned ' , ' done ' ] ) , ' & ' , ( ' state ' , ' in ' , [ ' confirmed ' , ' waiting ' ] ) , ( ' partially_available ' , ' = ' , True ) ]
2014-03-14 13:59:38 +00:00
if inventory . product_id :
domain + = [ ( ' product_id ' , ' = ' , inventory . product_id . id ) ]
return self . pool . get ( ' stock.move ' ) . search ( cr , uid , domain , context = context )
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 ) :
2014-03-14 13:59:38 +00:00
#check inventory start date is allowed
2014-03-17 09:06:56 +00:00
if inventory . date > time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) :
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' It \' s impossible to confirm an inventory in the future. Please change the inventory date to proceed further. ' ) )
2014-03-14 13:59:38 +00:00
conflicting_move_ids = self . check_inventory_date ( cr , uid , inventory , context = context )
error_message = " "
for move in self . pool . get ( ' stock.move ' ) . browse ( cr , uid , conflicting_move_ids , context = context ) :
2014-03-26 14:37:58 +00:00
error_message + = _ ( " \n * Date: %s - %s %s - From: %s To: %s State: %s " ) % ( move . date , move . product_uom_qty , move . product_uom . name , move . location_id . name , move . location_dest_id . name , move . state )
2014-03-14 13:59:38 +00:00
if conflicting_move_ids :
2014-03-26 14:37:58 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' Stock moves exist made/scheduled after the inventory date which are conflicting with its settings. Please unreserve them or change the inventory date to proceed further. \n \n %s ' ) % ( error_message ) )
2013-08-05 23:23:48 +00:00
#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 :
2013-09-05 10:51:07 +00:00
domain + = ' and owner_id = %s '
2013-08-05 23:23:48 +00:00
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 ( '''
2013-09-12 07:38:37 +00:00
SELECT product_id , sum ( qty ) as product_qty , location_id , lot_id as prod_lot_id , package_id , owner_id as partner_id
2013-08-05 23:23:48 +00:00
FROM stock_quant WHERE ''' + domain + '''
2013-09-12 07:38:37 +00:00
GROUP BY product_id , location_id , lot_id , package_id , partner_id
2013-08-05 23:23:48 +00:00
''' , args)
vals = [ ]
for product_line in cr . dictfetchall ( ) :
2013-09-13 06:42:03 +00:00
#replace the None the dictionary by False, because falsy values are tested later on
for key , value in product_line . items ( ) :
2013-10-25 13:40:23 +00:00
if not value :
2013-09-13 06:42:03 +00:00
product_line [ key ] = False
2013-08-05 23:23:48 +00:00
product_line [ ' inventory_id ' ] = inventory . id
2013-09-12 07:38:37 +00:00
product_line [ ' th_qty ' ] = product_line [ ' product_qty ' ]
2013-08-05 23:23:48 +00:00
if product_line [ ' product_id ' ] :
2013-11-08 16:52:17 +00:00
product = product_obj . browse ( cr , uid , product_line [ ' product_id ' ] , context = context )
product_line [ ' product_uom_id ' ] = product . uom_id . id
2013-08-05 23:23:48 +00:00
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 "
2014-03-11 18:12:36 +00:00
_order = " inventory_id, location_name, product_code, product_name, prodlot_name "
2014-01-22 16:33:51 +00:00
def _get_product_name_change ( self , cr , uid , ids , context = None ) :
return self . pool . get ( ' stock.inventory.line ' ) . search ( cr , uid , [ ( ' product_id ' , ' in ' , ids ) ] , context = context )
def _get_location_change ( self , cr , uid , ids , context = None ) :
return self . pool . get ( ' stock.inventory.line ' ) . search ( cr , uid , [ ( ' location_id ' , ' in ' , ids ) ] , context = context )
2014-03-11 18:12:36 +00:00
def _get_prodlot_change ( self , cr , uid , ids , context = None ) :
return self . pool . get ( ' stock.inventory.line ' ) . search ( cr , uid , [ ( ' prod_lot_id ' , ' in ' , ids ) ] , context = context )
2014-03-11 17:45:05 +00:00
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 ) ,
2013-09-12 07:38:37 +00:00
' product_qty ' : fields . float ( ' Checked 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-09-12 07:38:37 +00:00
' th_qty ' : fields . float ( ' Theoretical Quantity ' , readonly = True ) ,
2013-09-05 10:12:44 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , ' Owner ' ) ,
2014-03-12 15:26:25 +00:00
' product_name ' : fields . related ( ' product_id ' , ' name ' , type = ' char ' , string = ' Product Name ' , store = {
2014-01-22 16:33:51 +00:00
' product.product ' : ( _get_product_name_change , [ ' name ' , ' default_code ' ] , 20 ) ,
' stock.inventory.line ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' product_id ' ] , 20 ) , } ) ,
2014-03-12 15:26:25 +00:00
' product_code ' : fields . related ( ' product_id ' , ' default_code ' , type = ' char ' , string = ' Product Code ' , store = {
2014-01-22 16:33:51 +00:00
' product.product ' : ( _get_product_name_change , [ ' name ' , ' default_code ' ] , 20 ) ,
' stock.inventory.line ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' product_id ' ] , 20 ) , } ) ,
2014-03-12 15:26:25 +00:00
' location_name ' : fields . related ( ' location_id ' , ' complete_name ' , type = ' char ' , string = ' Location Name ' , store = {
2014-01-22 16:33:51 +00:00
' stock.location ' : ( _get_location_change , [ ' name ' , ' location_id ' , ' active ' ] , 20 ) ,
' stock.inventory.line ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' location_id ' ] , 20 ) , } ) ,
2014-03-12 15:26:25 +00:00
' prodlot_name ' : fields . related ( ' prod_lot_id ' , ' name ' , type = ' char ' , string = ' Serial Number Name ' , store = {
2014-03-11 18:12:36 +00:00
' stock.production.lot ' : ( _get_prodlot_change , [ ' name ' ] , 20 ) ,
' stock.inventory.line ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' prod_lot_id ' ] , 20 ) , } ) ,
2008-07-22 15:11:28 +00:00
}
2010-03-15 13:16:40 +00:00
2012-08-03 12:23:54 +00:00
_defaults = {
2013-09-16 06:17:02 +00:00
' product_qty ' : 1 ,
2012-08-03 12:23:54 +00:00
}
2013-08-05 23:23:48 +00:00
def _resolve_inventory_line ( self , cr , uid , inventory_line , theorical_lines , context = None ) :
2013-09-13 06:42:03 +00:00
#TODO : package_id management !
2013-08-05 23:23:48 +00:00
found = False
2013-09-12 08:03:44 +00:00
uom_obj = self . pool . get ( ' product.uom ' )
2013-08-05 23:23:48 +00:00
for th_line in theorical_lines :
2013-09-13 06:42:03 +00:00
#We try to match the inventory line with a theorical line with same product, lot, location and owner
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 and th_line [ ' partner_id ' ] == inventory_line . partner_id . id :
uom_reference = inventory_line . product_id . uom_id
real_qty = uom_obj . _compute_qty_obj ( cr , uid , inventory_line . product_uom_id , inventory_line . product_qty , uom_reference )
th_line [ ' product_qty ' ] - = real_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 ,
2013-09-13 06:42:03 +00:00
' partner_id ' : inventory_line . partner_id . id ,
2013-08-05 23:23:48 +00:00
}
theorical_lines . append ( vals )
2012-08-03 12:23:54 +00:00
2013-09-12 07:38:37 +00:00
def on_change_product_id ( self , cr , uid , ids , location_id , product , uom = False , owner_id = False , lot_id = False , package_id = 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-09-12 07:38:37 +00:00
uom_obj = self . pool . get ( ' product.uom ' )
2013-10-25 13:40:23 +00:00
ctx = context . copy ( )
2013-09-12 07:38:37 +00:00
ctx [ ' location ' ] = location_id
ctx [ ' lot_id ' ] = lot_id
ctx [ ' owner_id ' ] = owner_id
ctx [ ' package_id ' ] = package_id
obj_product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product , context = ctx )
th_qty = obj_product . qty_available
if uom and uom != obj_product . uom_id . id :
uom_record = uom_obj . browse ( cr , uid , uom , context = context )
th_qty = uom_obj . _compute_qty_obj ( cr , uid , obj_product . uom_id , th_qty , uom_record )
2013-09-16 06:17:02 +00:00
return { ' value ' : { ' th_qty ' : th_qty , ' product_uom_id ' : uom or obj_product . uom_id . id } }
2009-08-28 09:40:49 +00:00
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 "
2013-10-25 13:40:23 +00:00
2008-07-22 15:11:28 +00:00
_columns = {
2014-02-10 10:07:27 +00:00
' name ' : fields . char ( ' Warehouse Name ' , size = 128 , required = True , select = True ) ,
2010-09-06 13:51:23 +00:00
' 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-10-22 12:06:05 +00:00
' view_location_id ' : fields . many2one ( ' stock.location ' , ' View Location ' , required = True , domain = [ ( ' usage ' , ' = ' , ' view ' ) ] ) ,
2013-07-09 13:22:39 +00:00
' lot_stock_id ' : fields . many2one ( ' stock.location ' , ' Location Stock ' , required = True , domain = [ ( ' usage ' , ' = ' , ' internal ' ) ] ) ,
2013-10-08 12:36:59 +00:00
' code ' : fields . char ( ' Short Name ' , size = 5 , required = True , help = " Short name used to identify your warehouse " ) ,
2013-10-04 14:56:59 +00:00
' route_ids ' : fields . many2many ( ' stock.location.route ' , ' stock_route_warehouse ' , ' warehouse_id ' , ' route_id ' , ' Routes ' , domain = " [( ' warehouse_selectable ' , ' = ' , True)] " , help = ' Defaults routes through the warehouse ' ) ,
' reception_steps ' : fields . selection ( [
( ' one_step ' , ' Receive goods directly in stock (1 step) ' ) ,
( ' two_steps ' , ' Unload in input location then go to stock (2 steps) ' ) ,
( ' three_steps ' , ' Unload in input location, go through a quality control before being admitted in stock (3 steps) ' ) ] , ' Incoming Shipments ' , required = True ) ,
' delivery_steps ' : fields . selection ( [
( ' ship_only ' , ' Ship directly from stock (Ship only) ' ) ,
( ' pick_ship ' , ' Bring goods to output location before shipping (Pick + Ship) ' ) ,
( ' pick_pack_ship ' , ' Make packages into a dedicated location, then bring them to the output location for shipping (Pick + Pack + Ship) ' ) ] , ' Outgoing Shippings ' , required = True ) ,
' wh_input_stock_loc_id ' : fields . many2one ( ' stock.location ' , ' Input Location ' ) ,
' wh_qc_stock_loc_id ' : fields . many2one ( ' stock.location ' , ' Quality Control Location ' ) ,
' wh_output_stock_loc_id ' : fields . many2one ( ' stock.location ' , ' Output Location ' ) ,
' wh_pack_stock_loc_id ' : fields . many2one ( ' stock.location ' , ' Packing Location ' ) ,
' mto_pull_id ' : fields . many2one ( ' procurement.rule ' , ' MTO rule ' ) ,
' pick_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Pick Type ' ) ,
' pack_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Pack Type ' ) ,
' out_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Out Type ' ) ,
' in_type_id ' : fields . many2one ( ' stock.picking.type ' , ' In Type ' ) ,
' int_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Internal Type ' ) ,
' crossdock_route_id ' : fields . many2one ( ' stock.location.route ' , ' Crossdock Route ' ) ,
' reception_route_id ' : fields . many2one ( ' stock.location.route ' , ' Reception Route ' ) ,
' delivery_route_id ' : fields . many2one ( ' stock.location.route ' , ' Delivery Route ' ) ,
2013-10-16 10:03:18 +00:00
' resupply_from_wh ' : fields . boolean ( ' Resupply From Other Warehouses ' ) ,
' resupply_wh_ids ' : fields . many2many ( ' stock.warehouse ' , ' stock_wh_resupply_table ' , ' supplied_wh_id ' , ' supplier_wh_id ' , ' Resupply Warehouses ' ) ,
' resupply_route_ids ' : fields . one2many ( ' stock.location.route ' , ' supplied_wh_id ' , ' Resupply Routes ' ) ,
' default_resupply_wh_id ' : fields . many2one ( ' stock.warehouse ' , ' Default Resupply Warehouse ' ) ,
2008-07-22 15:11:28 +00:00
}
2012-08-03 12:23:54 +00:00
2013-10-25 13:40:23 +00:00
def onchange_filter_default_resupply_wh_id ( self , cr , uid , ids , default_resupply_wh_id , resupply_wh_ids , context = None ) :
resupply_wh_ids = set ( [ x [ ' id ' ] for x in ( self . resolve_2many_commands ( cr , uid , ' resupply_wh_ids ' , resupply_wh_ids , [ ' id ' ] ) ) ] )
2013-11-04 12:00:08 +00:00
if default_resupply_wh_id : #If we are removing the default resupply, we don't have default_resupply_wh_id
resupply_wh_ids . add ( default_resupply_wh_id )
resupply_wh_ids = list ( resupply_wh_ids )
2013-10-25 13:40:23 +00:00
return { ' value ' : { ' resupply_wh_ids ' : resupply_wh_ids } }
2013-10-23 13:40:21 +00:00
2013-10-16 10:03:18 +00:00
def _get_inter_wh_location ( self , cr , uid , warehouse , context = None ) :
''' returns a tuple made of the browse record of customer location and the browse record of supplier location '''
data_obj = self . pool . get ( ' ir.model.data ' )
try :
inter_wh_loc = data_obj . get_object_reference ( cr , uid , ' stock ' , ' stock_location_inter_wh ' ) [ 1 ]
except :
inter_wh_loc = False
return inter_wh_loc
def _get_all_products_to_resupply ( self , cr , uid , warehouse , context = None ) :
return self . pool . get ( ' product.product ' ) . search ( cr , uid , [ ] , context = context )
def _assign_route_on_products ( self , cr , uid , warehouse , inter_wh_route_id , context = None ) :
product_ids = self . _get_all_products_to_resupply ( cr , uid , warehouse , context = context )
self . pool . get ( ' product.product ' ) . write ( cr , uid , product_ids , { ' route_ids ' : [ ( 4 , inter_wh_route_id ) ] } , context = context )
2013-11-05 15:49:23 +00:00
def _unassign_route_on_products ( self , cr , uid , warehouse , inter_wh_route_id , context = None ) :
product_ids = self . _get_all_products_to_resupply ( cr , uid , warehouse , context = context )
self . pool . get ( ' product.product ' ) . write ( cr , uid , product_ids , { ' route_ids ' : [ ( 3 , inter_wh_route_id ) ] } , context = context )
2013-10-16 10:03:18 +00:00
def _get_inter_wh_route ( self , cr , uid , warehouse , wh , context = None ) :
return {
' name ' : _ ( ' %s : Supply Product from %s ' ) % ( warehouse . name , wh . name ) ,
' warehouse_selectable ' : False ,
' product_selectable ' : True ,
' product_categ_selectable ' : True ,
' supplied_wh_id ' : warehouse . id ,
' supplier_wh_id ' : wh . id ,
}
def _create_resupply_routes ( self , cr , uid , warehouse , supplier_warehouses , default_resupply_wh , context = None ) :
location_obj = self . pool . get ( ' stock.location ' )
route_obj = self . pool . get ( ' stock.location.route ' )
pull_obj = self . pool . get ( ' procurement.rule ' )
#create route selectable on the product to resupply the warehouse from another one
inter_wh_location_id = self . _get_inter_wh_location ( cr , uid , warehouse , context = context )
if inter_wh_location_id :
input_loc = warehouse . wh_input_stock_loc_id
if warehouse . reception_steps == ' one_step ' :
input_loc = warehouse . lot_stock_id
inter_wh_location = location_obj . browse ( cr , uid , inter_wh_location_id , context = context )
for wh in supplier_warehouses :
output_loc = wh . wh_output_stock_loc_id
if wh . delivery_steps == ' ship_only ' :
output_loc = wh . lot_stock_id
inter_wh_route_vals = self . _get_inter_wh_route ( cr , uid , warehouse , wh , context = context )
inter_wh_route_id = route_obj . create ( cr , uid , vals = inter_wh_route_vals , context = context )
2013-11-05 16:12:54 +00:00
values = [ ( output_loc , inter_wh_location , wh . out_type_id . id , wh ) , ( inter_wh_location , input_loc , warehouse . in_type_id . id , warehouse ) ]
pull_rules_list = self . _get_supply_pull_rules ( cr , uid , warehouse , values , inter_wh_route_id , context = context )
2013-10-16 10:03:18 +00:00
for pull_rule in pull_rules_list :
pull_obj . create ( cr , uid , vals = pull_rule , context = context )
#if the warehouse is also set as default resupply method, assign this route automatically to all product
if default_resupply_wh and default_resupply_wh . id == wh . id :
self . _assign_route_on_products ( cr , uid , warehouse , inter_wh_route_id , context = context )
2013-11-05 15:49:23 +00:00
#finally, save the route on the warehouse
self . write ( cr , uid , [ warehouse . id ] , { ' route_ids ' : [ ( 4 , inter_wh_route_id ) ] } , context = context )
2013-10-16 10:03:18 +00:00
2013-09-03 09:03:34 +00:00
def _default_stock_id ( self , cr , uid , context = None ) :
2013-10-10 12:03:31 +00:00
#lot_input_stock = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'stock_location_stock')
try :
2013-10-25 13:40:23 +00:00
warehouse = self . pool . get ( ' ir.model.data ' ) . get_object ( cr , uid , ' stock ' , ' warehouse0 ' )
2013-10-10 12:03:31 +00:00
return warehouse . lot_stock_id . id
except :
return False
2012-08-03 12:23:54 +00:00
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 ,
2013-10-04 14:56:59 +00:00
' reception_steps ' : ' one_step ' ,
' delivery_steps ' : ' ship_only ' ,
2009-11-28 09:25:22 +00:00
}
2013-09-26 15:37:25 +00:00
_sql_constraints = [
2013-10-22 06:54:46 +00:00
( ' warehouse_name_uniq ' , ' unique(name, company_id) ' , ' The name of the warehouse must be unique per company! ' ) ,
2013-10-25 13:40:23 +00:00
( ' warehouse_code_uniq ' , ' unique(code, company_id) ' , ' The code of the warehouse must be unique per company! ' ) ,
2013-09-26 15:37:25 +00:00
]
2010-07-06 11:21:27 +00:00
2013-10-04 14:56:59 +00:00
def _get_partner_locations ( self , cr , uid , ids , context = None ) :
''' returns a tuple made of the browse record of customer location and the browse record of supplier location '''
data_obj = self . pool . get ( ' ir.model.data ' )
location_obj = self . pool . get ( ' stock.location ' )
try :
customer_loc = data_obj . get_object_reference ( cr , uid , ' stock ' , ' stock_location_customers ' ) [ 1 ]
supplier_loc = data_obj . get_object_reference ( cr , uid , ' stock ' , ' stock_location_suppliers ' ) [ 1 ]
except :
customer_loc = location_obj . search ( cr , uid , [ ( ' usage ' , ' = ' , ' customer ' ) ] , context = context )
customer_loc = customer_loc and customer_loc [ 0 ] or False
supplier_loc = location_obj . search ( cr , uid , [ ( ' usage ' , ' = ' , ' supplier ' ) ] , context = context )
supplier_loc = supplier_loc and supplier_loc [ 0 ] or False
if not ( customer_loc and supplier_loc ) :
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' Can \' t find any customer or supplier location. ' ) )
return location_obj . browse ( cr , uid , [ customer_loc , supplier_loc ] , context = context )
def switch_location ( self , cr , uid , ids , warehouse , new_reception_step = False , new_delivery_step = False , context = None ) :
location_obj = self . pool . get ( ' stock.location ' )
new_reception_step = new_reception_step or warehouse . reception_steps
new_delivery_step = new_delivery_step or warehouse . delivery_steps
if warehouse . reception_steps != new_reception_step :
location_obj . write ( cr , uid , [ warehouse . wh_input_stock_loc_id . id , warehouse . wh_qc_stock_loc_id . id ] , { ' active ' : False } , context = context )
if new_reception_step != ' one_step ' :
location_obj . write ( cr , uid , warehouse . wh_input_stock_loc_id . id , { ' active ' : True } , context = context )
if new_reception_step == ' three_steps ' :
location_obj . write ( cr , uid , warehouse . wh_qc_stock_loc_id . id , { ' active ' : True } , context = context )
if warehouse . delivery_steps != new_delivery_step :
location_obj . write ( cr , uid , [ warehouse . wh_output_stock_loc_id . id , warehouse . wh_pack_stock_loc_id . id ] , { ' active ' : False } , context = context )
if new_delivery_step != ' ship_only ' :
location_obj . write ( cr , uid , warehouse . wh_output_stock_loc_id . id , { ' active ' : True } , context = context )
if new_delivery_step == ' pick_pack_ship ' :
location_obj . write ( cr , uid , warehouse . wh_pack_stock_loc_id . id , { ' active ' : True } , context = context )
return True
def _get_reception_delivery_route ( self , cr , uid , warehouse , route_name , context = None ) :
return {
' name ' : self . _format_routename ( cr , uid , warehouse , route_name , context = context ) ,
' product_categ_selectable ' : True ,
' product_selectable ' : False ,
2013-10-17 08:40:24 +00:00
' sequence ' : 10 ,
2013-10-04 14:56:59 +00:00
}
2013-11-05 16:12:54 +00:00
def _get_supply_pull_rules ( self , cr , uid , supplied_warehouse , values , new_route_id , context = None ) :
pull_rules_list = [ ]
for from_loc , dest_loc , pick_type_id , warehouse in values :
pull_rules_list . append ( {
' name ' : self . _format_rulename ( cr , uid , warehouse , from_loc , dest_loc , context = context ) ,
' location_src_id ' : from_loc . id ,
' location_id ' : dest_loc . id ,
' route_id ' : new_route_id ,
' action ' : ' move ' ,
' picking_type_id ' : pick_type_id ,
' procure_method ' : ' make_to_order ' ,
' warehouse_id ' : supplied_warehouse . id ,
2013-11-06 08:39:38 +00:00
' propagate_warehouse_id ' : warehouse . id ,
2013-11-05 16:12:54 +00:00
} )
return pull_rules_list
2013-10-04 14:56:59 +00:00
def _get_push_pull_rules ( self , cr , uid , warehouse , active , values , new_route_id , context = None ) :
first_rule = True
push_rules_list = [ ]
pull_rules_list = [ ]
for from_loc , dest_loc , pick_type_id in values :
push_rules_list . append ( {
' name ' : self . _format_rulename ( cr , uid , warehouse , from_loc , dest_loc , context = context ) ,
' location_from_id ' : from_loc . id ,
' location_dest_id ' : dest_loc . id ,
' route_id ' : new_route_id ,
' auto ' : ' manual ' ,
' picking_type_id ' : pick_type_id ,
' active ' : active ,
2013-10-22 12:06:05 +00:00
' warehouse_id ' : warehouse . id ,
2013-10-04 14:56:59 +00:00
} )
pull_rules_list . append ( {
' name ' : self . _format_rulename ( cr , uid , warehouse , from_loc , dest_loc , context = context ) ,
' location_src_id ' : from_loc . id ,
' location_id ' : dest_loc . id ,
' route_id ' : new_route_id ,
' action ' : ' move ' ,
' picking_type_id ' : pick_type_id ,
' procure_method ' : first_rule is True and ' make_to_stock ' or ' make_to_order ' ,
' active ' : active ,
2013-10-22 12:06:05 +00:00
' warehouse_id ' : warehouse . id ,
2013-10-04 14:56:59 +00:00
} )
first_rule = False
return push_rules_list , pull_rules_list
def _get_mto_pull_rule ( self , cr , uid , warehouse , values , context = None ) :
2013-10-25 13:40:23 +00:00
route_obj = self . pool . get ( ' stock.location.route ' )
2013-10-04 14:56:59 +00:00
data_obj = self . pool . get ( ' ir.model.data ' )
try :
mto_route_id = data_obj . get_object_reference ( cr , uid , ' stock ' , ' route_warehouse0_mto ' ) [ 1 ]
except :
2013-12-12 10:11:32 +00:00
mto_route_id = route_obj . search ( cr , uid , [ ( ' name ' , ' like ' , _ ( ' Make To Order ' ) ) ] , context = context )
2013-10-04 14:56:59 +00:00
mto_route_id = mto_route_id and mto_route_id [ 0 ] or False
if not mto_route_id :
2013-12-12 10:11:32 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , _ ( ' Can \' t find any generic Make To Order route. ' ) )
2013-10-04 14:56:59 +00:00
from_loc , dest_loc , pick_type_id = values [ 0 ]
return {
' name ' : self . _format_rulename ( cr , uid , warehouse , from_loc , dest_loc , context = context ) + _ ( ' MTO ' ) ,
' location_src_id ' : from_loc . id ,
' location_id ' : dest_loc . id ,
' route_id ' : mto_route_id ,
' action ' : ' move ' ,
' picking_type_id ' : pick_type_id ,
' procure_method ' : ' make_to_order ' ,
' active ' : True ,
2013-10-22 12:06:05 +00:00
' warehouse_id ' : warehouse . id ,
2013-10-04 14:56:59 +00:00
}
def _get_crossdock_route ( self , cr , uid , warehouse , route_name , context = None ) :
return {
' name ' : self . _format_routename ( cr , uid , warehouse , route_name , context = context ) ,
' warehouse_selectable ' : False ,
' product_selectable ' : True ,
' product_categ_selectable ' : True ,
' active ' : warehouse . delivery_steps != ' ship_only ' and warehouse . reception_steps != ' one_step ' ,
2013-10-22 16:03:12 +00:00
' sequence ' : 20 ,
2013-10-04 14:56:59 +00:00
}
def create_routes ( self , cr , uid , ids , warehouse , context = None ) :
wh_route_ids = [ ]
route_obj = self . pool . get ( ' stock.location.route ' )
pull_obj = self . pool . get ( ' procurement.rule ' )
push_obj = self . pool . get ( ' stock.location.path ' )
routes_dict = self . get_routes_dict ( cr , uid , ids , warehouse , context = context )
#create reception route and rules
route_name , values = routes_dict [ warehouse . reception_steps ]
route_vals = self . _get_reception_delivery_route ( cr , uid , warehouse , route_name , context = context )
reception_route_id = route_obj . create ( cr , uid , route_vals , context = context )
wh_route_ids . append ( ( 4 , reception_route_id ) )
push_rules_list , pull_rules_list = self . _get_push_pull_rules ( cr , uid , warehouse , True , values , reception_route_id , context = context )
#create the push/pull rules
for push_rule in push_rules_list :
push_obj . create ( cr , uid , vals = push_rule , context = context )
for pull_rule in pull_rules_list :
2013-10-24 08:19:01 +00:00
#all pull rules in reception route are mto, because we don't want to wait for the scheduler to trigger an orderpoint on input location
2013-10-23 09:25:08 +00:00
pull_rule [ ' procure_method ' ] = ' make_to_order '
2013-10-04 14:56:59 +00:00
pull_obj . create ( cr , uid , vals = pull_rule , context = context )
2013-12-13 13:26:41 +00:00
#create MTS route and pull rules for delivery and a specific route MTO to be set on the product
2013-10-04 14:56:59 +00:00
route_name , values = routes_dict [ warehouse . delivery_steps ]
route_vals = self . _get_reception_delivery_route ( cr , uid , warehouse , route_name , context = context )
#create the route and its pull rules
delivery_route_id = route_obj . create ( cr , uid , route_vals , context = context )
wh_route_ids . append ( ( 4 , delivery_route_id ) )
dummy , pull_rules_list = self . _get_push_pull_rules ( cr , uid , warehouse , True , values , delivery_route_id , context = context )
for pull_rule in pull_rules_list :
pull_obj . create ( cr , uid , vals = pull_rule , context = context )
#create MTO pull rule and link it to the generic MTO route
mto_pull_vals = self . _get_mto_pull_rule ( cr , uid , warehouse , values , context = context )
mto_pull_id = pull_obj . create ( cr , uid , mto_pull_vals , context = context )
#create a route for cross dock operations, that can be set on products and product categories
route_name , values = routes_dict [ ' crossdock ' ]
crossdock_route_vals = self . _get_crossdock_route ( cr , uid , warehouse , route_name , context = context )
crossdock_route_id = route_obj . create ( cr , uid , vals = crossdock_route_vals , context = context )
wh_route_ids . append ( ( 4 , crossdock_route_id ) )
dummy , pull_rules_list = self . _get_push_pull_rules ( cr , uid , warehouse , warehouse . delivery_steps != ' ship_only ' and warehouse . reception_steps != ' one_step ' , values , crossdock_route_id , context = context )
for pull_rule in pull_rules_list :
pull_obj . create ( cr , uid , vals = pull_rule , context = context )
2013-10-16 10:03:18 +00:00
#create route selectable on the product to resupply the warehouse from another one
self . _create_resupply_routes ( cr , uid , warehouse , warehouse . resupply_wh_ids , warehouse . default_resupply_wh_id , context = context )
2013-11-05 15:49:23 +00:00
#return routes and mto pull rule to store on the warehouse
2013-10-22 06:54:46 +00:00
return {
2013-10-04 14:56:59 +00:00
' route_ids ' : wh_route_ids ,
' mto_pull_id ' : mto_pull_id ,
' reception_route_id ' : reception_route_id ,
' delivery_route_id ' : delivery_route_id ,
' crossdock_route_id ' : crossdock_route_id ,
2013-10-23 13:40:21 +00:00
}
2013-10-04 14:56:59 +00:00
def change_route ( self , cr , uid , ids , warehouse , new_reception_step = False , new_delivery_step = False , context = None ) :
picking_type_obj = self . pool . get ( ' stock.picking.type ' )
pull_obj = self . pool . get ( ' procurement.rule ' )
push_obj = self . pool . get ( ' stock.location.path ' )
route_obj = self . pool . get ( ' stock.location.route ' )
new_reception_step = new_reception_step or warehouse . reception_steps
new_delivery_step = new_delivery_step or warehouse . delivery_steps
#change the default source and destination location and (de)activate picking types
input_loc = warehouse . wh_input_stock_loc_id
if new_reception_step == ' one_step ' :
input_loc = warehouse . lot_stock_id
output_loc = warehouse . wh_output_stock_loc_id
if new_delivery_step == ' ship_only ' :
output_loc = warehouse . lot_stock_id
picking_type_obj . write ( cr , uid , warehouse . in_type_id . id , { ' default_location_dest_id ' : input_loc . id } , context = context )
picking_type_obj . write ( cr , uid , warehouse . out_type_id . id , { ' default_location_src_id ' : output_loc . id } , context = context )
picking_type_obj . write ( cr , uid , warehouse . pick_type_id . id , { ' active ' : new_delivery_step != ' ship_only ' } , context = context )
picking_type_obj . write ( cr , uid , warehouse . pack_type_id . id , { ' active ' : new_delivery_step == ' pick_pack_ship ' } , context = context )
routes_dict = self . get_routes_dict ( cr , uid , ids , warehouse , context = context )
#update delivery route and rules: unlink the existing rules of the warehouse delivery route and recreate it
pull_obj . unlink ( cr , uid , [ pu . id for pu in warehouse . delivery_route_id . pull_ids ] , context = context )
route_name , values = routes_dict [ new_delivery_step ]
route_obj . write ( cr , uid , warehouse . delivery_route_id . id , { ' name ' : self . _format_routename ( cr , uid , warehouse , route_name , context = context ) } , context = context )
dummy , pull_rules_list = self . _get_push_pull_rules ( cr , uid , warehouse , True , values , warehouse . delivery_route_id . id , context = context )
#create the pull rules
for pull_rule in pull_rules_list :
pull_obj . create ( cr , uid , vals = pull_rule , context = context )
#update reception route and rules: unlink the existing rules of the warehouse reception route and recreate it
pull_obj . unlink ( cr , uid , [ pu . id for pu in warehouse . reception_route_id . pull_ids ] , context = context )
push_obj . unlink ( cr , uid , [ pu . id for pu in warehouse . reception_route_id . push_ids ] , context = context )
route_name , values = routes_dict [ new_reception_step ]
route_obj . write ( cr , uid , warehouse . reception_route_id . id , { ' name ' : self . _format_routename ( cr , uid , warehouse , route_name , context = context ) } , context = context )
push_rules_list , pull_rules_list = self . _get_push_pull_rules ( cr , uid , warehouse , True , values , warehouse . reception_route_id . id , context = context )
#create the push/pull rules
for push_rule in push_rules_list :
push_obj . create ( cr , uid , vals = push_rule , context = context )
for pull_rule in pull_rules_list :
2013-10-24 08:19:01 +00:00
#all pull rules in reception route are mto, because we don't want to wait for the scheduler to trigger an orderpoint on input location
pull_rule [ ' procure_method ' ] = ' make_to_order '
2013-10-04 14:56:59 +00:00
pull_obj . create ( cr , uid , vals = pull_rule , context = context )
route_obj . write ( cr , uid , warehouse . crossdock_route_id . id , { ' active ' : new_reception_step != ' one_step ' and new_delivery_step != ' ship_only ' } , context = context )
#change MTO rule
dummy , values = routes_dict [ new_delivery_step ]
mto_pull_vals = self . _get_mto_pull_rule ( cr , uid , warehouse , values , context = context )
pull_obj . write ( cr , uid , warehouse . mto_pull_id . id , mto_pull_vals , context = context )
return True
def create ( self , cr , uid , vals , context = None ) :
if context is None :
context = { }
if vals is None :
vals = { }
data_obj = self . pool . get ( ' ir.model.data ' )
seq_obj = self . pool . get ( ' ir.sequence ' )
picking_type_obj = self . pool . get ( ' stock.picking.type ' )
location_obj = self . pool . get ( ' stock.location ' )
#create view location for warehouse
wh_loc_id = location_obj . create ( cr , uid , {
2014-02-13 09:48:20 +00:00
' name ' : _ ( vals . get ( ' code ' ) ) ,
2013-10-04 14:56:59 +00:00
' usage ' : ' view ' ,
' location_id ' : data_obj . get_object_reference ( cr , uid , ' stock ' , ' stock_location_locations ' ) [ 1 ]
} , context = context )
2013-10-22 12:06:05 +00:00
vals [ ' view_location_id ' ] = wh_loc_id
2013-10-04 14:56:59 +00:00
#create all location
2013-11-08 16:52:17 +00:00
def_values = self . default_get ( cr , uid , { ' reception_steps ' , ' delivery_steps ' } )
2013-11-05 16:15:17 +00:00
reception_steps = vals . get ( ' reception_steps ' , def_values [ ' reception_steps ' ] )
delivery_steps = vals . get ( ' delivery_steps ' , def_values [ ' delivery_steps ' ] )
2013-10-04 14:56:59 +00:00
context_with_inactive = context . copy ( )
context_with_inactive [ ' active_test ' ] = False
sub_locations = [
{ ' name ' : _ ( ' Stock ' ) , ' active ' : True , ' field ' : ' lot_stock_id ' } ,
{ ' name ' : _ ( ' Input ' ) , ' active ' : reception_steps != ' one_step ' , ' field ' : ' wh_input_stock_loc_id ' } ,
{ ' name ' : _ ( ' Quality Control ' ) , ' active ' : reception_steps == ' three_steps ' , ' field ' : ' wh_qc_stock_loc_id ' } ,
{ ' name ' : _ ( ' Output ' ) , ' active ' : delivery_steps != ' ship_only ' , ' field ' : ' wh_output_stock_loc_id ' } ,
{ ' name ' : _ ( ' Packing Zone ' ) , ' active ' : delivery_steps == ' pick_pack_ship ' , ' field ' : ' wh_pack_stock_loc_id ' } ,
]
for values in sub_locations :
location_id = location_obj . create ( cr , uid , {
' name ' : values [ ' name ' ] ,
' usage ' : ' internal ' ,
' location_id ' : wh_loc_id ,
' active ' : values [ ' active ' ] ,
} , context = context_with_inactive )
vals [ values [ ' field ' ] ] = location_id
#create new sequences
2014-01-20 09:58:03 +00:00
in_seq_id = seq_obj . create ( cr , SUPERUSER_ID , values = { ' name ' : vals . get ( ' name ' , ' ' ) + _ ( ' Sequence in ' ) , ' prefix ' : vals . get ( ' code ' , ' ' ) + ' /IN/ ' , ' padding ' : 5 } , context = context )
out_seq_id = seq_obj . create ( cr , SUPERUSER_ID , values = { ' name ' : vals . get ( ' name ' , ' ' ) + _ ( ' Sequence out ' ) , ' prefix ' : vals . get ( ' code ' , ' ' ) + ' /OUT/ ' , ' padding ' : 5 } , context = context )
pack_seq_id = seq_obj . create ( cr , SUPERUSER_ID , values = { ' name ' : vals . get ( ' name ' , ' ' ) + _ ( ' Sequence packing ' ) , ' prefix ' : vals . get ( ' code ' , ' ' ) + ' /PACK/ ' , ' padding ' : 5 } , context = context )
pick_seq_id = seq_obj . create ( cr , SUPERUSER_ID , values = { ' name ' : vals . get ( ' name ' , ' ' ) + _ ( ' Sequence picking ' ) , ' prefix ' : vals . get ( ' code ' , ' ' ) + ' /PICK/ ' , ' padding ' : 5 } , context = context )
int_seq_id = seq_obj . create ( cr , SUPERUSER_ID , values = { ' name ' : vals . get ( ' name ' , ' ' ) + _ ( ' Sequence internal ' ) , ' prefix ' : vals . get ( ' code ' , ' ' ) + ' /INT/ ' , ' padding ' : 5 } , context = context )
2013-10-04 14:56:59 +00:00
#create WH
new_id = super ( stock_warehouse , self ) . create ( cr , uid , vals = vals , context = context )
warehouse = self . browse ( cr , uid , new_id , context = context )
wh_stock_loc = warehouse . lot_stock_id
wh_input_stock_loc = warehouse . wh_input_stock_loc_id
wh_output_stock_loc = warehouse . wh_output_stock_loc_id
wh_pack_stock_loc = warehouse . wh_pack_stock_loc_id
#fetch customer and supplier locations, for references
customer_loc , supplier_loc = self . _get_partner_locations ( cr , uid , new_id , context = context )
#create in, out, internal picking types for warehouse
input_loc = wh_input_stock_loc
if warehouse . reception_steps == ' one_step ' :
input_loc = wh_stock_loc
output_loc = wh_output_stock_loc
if warehouse . delivery_steps == ' ship_only ' :
output_loc = wh_stock_loc
2013-10-16 10:03:18 +00:00
#choose the next available color for the picking types of this warehouse
2013-11-08 16:52:17 +00:00
color = 0
2013-12-20 12:45:47 +00:00
available_colors = [ c % 9 for c in range ( 3 , 12 ) ] # put flashy colors first
all_used_colors = self . pool . get ( ' stock.picking.type ' ) . search_read ( cr , uid , [ ( ' warehouse_id ' , ' != ' , False ) , ( ' color ' , ' != ' , False ) ] , [ ' color ' ] , order = ' color ' )
#don't use sets to preserve the list order
for x in all_used_colors :
if x [ ' color ' ] in available_colors :
available_colors . remove ( x [ ' color ' ] )
if available_colors :
color = available_colors [ 0 ]
2013-10-16 10:03:18 +00:00
2013-12-20 13:09:21 +00:00
#order the picking types with a sequence allowing to have the following suit for each warehouse: reception, internal, pick, pack, ship.
max_sequence = self . pool . get ( ' stock.picking.type ' ) . search_read ( cr , uid , [ ] , [ ' sequence ' ] , order = ' sequence desc ' )
max_sequence = max_sequence and max_sequence [ 0 ] [ ' sequence ' ] or 0
2013-10-04 14:56:59 +00:00
in_type_id = picking_type_obj . create ( cr , uid , vals = {
' name ' : _ ( ' Receptions ' ) ,
' warehouse_id ' : new_id ,
2013-11-20 14:19:12 +00:00
' code ' : ' incoming ' ,
2013-10-04 14:56:59 +00:00
' auto_force_assign ' : True ,
' sequence_id ' : in_seq_id ,
' default_location_src_id ' : supplier_loc . id ,
2013-10-16 10:03:18 +00:00
' default_location_dest_id ' : input_loc . id ,
2013-12-20 13:09:21 +00:00
' sequence ' : max_sequence + 1 ,
2013-10-16 10:03:18 +00:00
' color ' : color } , context = context )
2013-10-04 14:56:59 +00:00
out_type_id = picking_type_obj . create ( cr , uid , vals = {
' name ' : _ ( ' Delivery Orders ' ) ,
' warehouse_id ' : new_id ,
2013-11-20 14:19:12 +00:00
' code ' : ' outgoing ' ,
2013-10-04 14:56:59 +00:00
' sequence_id ' : out_seq_id ,
2014-01-21 10:32:22 +00:00
' return_picking_type_id ' : in_type_id ,
2013-10-04 14:56:59 +00:00
' default_location_src_id ' : output_loc . id ,
2013-10-16 10:03:18 +00:00
' default_location_dest_id ' : customer_loc . id ,
2013-12-20 13:09:21 +00:00
' sequence ' : max_sequence + 4 ,
2013-10-16 10:03:18 +00:00
' color ' : color } , context = context )
2014-01-21 10:32:22 +00:00
picking_type_obj . write ( cr , uid , [ in_type_id ] , { ' return_picking_type_id ' : out_type_id } , context = context )
2013-10-04 14:56:59 +00:00
int_type_id = picking_type_obj . create ( cr , uid , vals = {
' name ' : _ ( ' Internal Transfers ' ) ,
' warehouse_id ' : new_id ,
2013-11-20 14:19:12 +00:00
' code ' : ' internal ' ,
2013-10-04 14:56:59 +00:00
' sequence_id ' : int_seq_id ,
' default_location_src_id ' : wh_stock_loc . id ,
' default_location_dest_id ' : wh_stock_loc . id ,
2013-12-06 16:32:43 +00:00
' active ' : True ,
2013-12-20 13:09:21 +00:00
' sequence ' : max_sequence + 2 ,
2013-10-16 10:03:18 +00:00
' color ' : color } , context = context )
2013-10-04 14:56:59 +00:00
pack_type_id = picking_type_obj . create ( cr , uid , vals = {
' name ' : _ ( ' Pack ' ) ,
' warehouse_id ' : new_id ,
2013-11-20 14:19:12 +00:00
' code ' : ' internal ' ,
2013-10-04 14:56:59 +00:00
' sequence_id ' : pack_seq_id ,
' default_location_src_id ' : wh_pack_stock_loc . id ,
' default_location_dest_id ' : output_loc . id ,
' active ' : delivery_steps == ' pick_pack_ship ' ,
2013-12-20 13:09:21 +00:00
' sequence ' : max_sequence + 3 ,
2013-10-16 10:03:18 +00:00
' color ' : color } , context = context )
2013-10-04 14:56:59 +00:00
pick_type_id = picking_type_obj . create ( cr , uid , vals = {
' name ' : _ ( ' Pick ' ) ,
' warehouse_id ' : new_id ,
2013-11-20 14:19:12 +00:00
' code ' : ' internal ' ,
2013-10-04 14:56:59 +00:00
' sequence_id ' : pick_seq_id ,
' default_location_src_id ' : wh_stock_loc . id ,
' default_location_dest_id ' : wh_pack_stock_loc . id ,
' active ' : delivery_steps != ' ship_only ' ,
2013-12-20 13:09:21 +00:00
' sequence ' : max_sequence + 2 ,
2013-10-16 10:03:18 +00:00
' color ' : color } , context = context )
2013-10-04 14:56:59 +00:00
#write picking types on WH
vals = {
' in_type_id ' : in_type_id ,
' out_type_id ' : out_type_id ,
' pack_type_id ' : pack_type_id ,
' pick_type_id ' : pick_type_id ,
' int_type_id ' : int_type_id ,
}
super ( stock_warehouse , self ) . write ( cr , uid , new_id , vals = vals , context = context )
warehouse . refresh ( )
#create routes and push/pull rules
2013-10-23 13:40:21 +00:00
new_objects_dict = self . create_routes ( cr , uid , new_id , warehouse , context = context )
self . write ( cr , uid , warehouse . id , new_objects_dict , context = context )
2013-10-04 14:56:59 +00:00
return new_id
def _format_rulename ( self , cr , uid , obj , from_loc , dest_loc , context = None ) :
2013-11-07 11:20:53 +00:00
return obj . code + ' : ' + from_loc . name + ' -> ' + dest_loc . name
2013-10-04 14:56:59 +00:00
def _format_routename ( self , cr , uid , obj , name , context = None ) :
return obj . name + ' : ' + name
def get_routes_dict ( self , cr , uid , ids , warehouse , context = None ) :
#fetch customer and supplier locations, for references
customer_loc , supplier_loc = self . _get_partner_locations ( cr , uid , ids , context = context )
return {
' one_step ' : ( _ ( ' Reception in 1 step ' ) , [ ] ) ,
' two_steps ' : ( _ ( ' Reception in 2 steps ' ) , [ ( warehouse . wh_input_stock_loc_id , warehouse . lot_stock_id , warehouse . int_type_id . id ) ] ) ,
' three_steps ' : ( _ ( ' Reception in 3 steps ' ) , [ ( warehouse . wh_input_stock_loc_id , warehouse . wh_qc_stock_loc_id , warehouse . int_type_id . id ) , ( warehouse . wh_qc_stock_loc_id , warehouse . lot_stock_id , warehouse . int_type_id . id ) ] ) ,
' crossdock ' : ( _ ( ' Cross-Dock ' ) , [ ( warehouse . wh_input_stock_loc_id , warehouse . wh_output_stock_loc_id , warehouse . int_type_id . id ) , ( warehouse . wh_output_stock_loc_id , customer_loc , warehouse . out_type_id . id ) ] ) ,
' ship_only ' : ( _ ( ' Ship Only ' ) , [ ( warehouse . lot_stock_id , customer_loc , warehouse . out_type_id . id ) ] ) ,
' pick_ship ' : ( _ ( ' Pick + Ship ' ) , [ ( warehouse . lot_stock_id , warehouse . wh_output_stock_loc_id , warehouse . pick_type_id . id ) , ( warehouse . wh_output_stock_loc_id , customer_loc , warehouse . out_type_id . id ) ] ) ,
2013-12-13 13:26:41 +00:00
' pick_pack_ship ' : ( _ ( ' Pick + Pack + Ship ' ) , [ ( warehouse . lot_stock_id , warehouse . wh_pack_stock_loc_id , warehouse . pick_type_id . id ) , ( warehouse . wh_pack_stock_loc_id , warehouse . wh_output_stock_loc_id , warehouse . pack_type_id . id ) , ( warehouse . wh_output_stock_loc_id , customer_loc , warehouse . out_type_id . id ) ] ) ,
2013-10-04 14:56:59 +00:00
}
2014-02-13 09:48:20 +00:00
def _handle_renaming ( self , cr , uid , warehouse , name , code , context = None ) :
2013-11-05 15:49:23 +00:00
location_obj = self . pool . get ( ' stock.location ' )
route_obj = self . pool . get ( ' stock.location.route ' )
pull_obj = self . pool . get ( ' procurement.rule ' )
push_obj = self . pool . get ( ' stock.location.path ' )
#rename location
location_id = warehouse . lot_stock_id . location_id . id
2014-02-13 09:48:20 +00:00
location_obj . write ( cr , uid , location_id , { ' name ' : code } , context = context )
2013-11-05 15:49:23 +00:00
#rename route and push-pull rules
for route in warehouse . route_ids :
route_obj . write ( cr , uid , route . id , { ' name ' : route . name . replace ( warehouse . name , name , 1 ) } , context = context )
for pull in route . pull_ids :
pull_obj . write ( cr , uid , pull . id , { ' name ' : pull . name . replace ( warehouse . name , name , 1 ) } , context = context )
for push in route . push_ids :
push_obj . write ( cr , uid , push . id , { ' name ' : pull . name . replace ( warehouse . name , name , 1 ) } , context = context )
#change the mto pull rule name
pull_obj . write ( cr , uid , warehouse . mto_pull_id . id , { ' name ' : warehouse . mto_pull_id . name . replace ( warehouse . name , name , 1 ) } , context = context )
2013-10-04 14:56:59 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
if context is None :
context = { }
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
seq_obj = self . pool . get ( ' ir.sequence ' )
route_obj = self . pool . get ( ' stock.location.route ' )
context_with_inactive = context . copy ( )
context_with_inactive [ ' active_test ' ] = False
for warehouse in self . browse ( cr , uid , ids , context = context_with_inactive ) :
#first of all, check if we need to delete and recreate route
if vals . get ( ' reception_steps ' ) or vals . get ( ' delivery_steps ' ) :
#activate and deactivate location according to reception and delivery option
self . switch_location ( cr , uid , warehouse . id , warehouse , vals . get ( ' reception_steps ' , False ) , vals . get ( ' delivery_steps ' , False ) , context = context )
# switch between route
self . change_route ( cr , uid , ids , warehouse , vals . get ( ' reception_steps ' , False ) , vals . get ( ' delivery_steps ' , False ) , context = context_with_inactive )
2014-02-13 09:48:20 +00:00
warehouse . refresh ( )
2013-10-04 14:56:59 +00:00
if vals . get ( ' code ' ) or vals . get ( ' name ' ) :
name = warehouse . name
#rename sequence
if vals . get ( ' name ' ) :
2014-02-13 09:48:20 +00:00
name = vals . get ( ' name ' , warehouse . name )
self . _handle_renaming ( cr , uid , warehouse , name , vals . get ( ' code ' , warehouse . code ) , context = context_with_inactive )
2013-10-04 14:56:59 +00:00
seq_obj . write ( cr , uid , warehouse . in_type_id . sequence_id . id , { ' name ' : name + _ ( ' Sequence in ' ) , ' prefix ' : vals . get ( ' code ' , warehouse . code ) + ' \ IN \\ ' } , context = context )
seq_obj . write ( cr , uid , warehouse . out_type_id . sequence_id . id , { ' name ' : name + _ ( ' Sequence out ' ) , ' prefix ' : vals . get ( ' code ' , warehouse . code ) + ' \ OUT \\ ' } , context = context )
seq_obj . write ( cr , uid , warehouse . pack_type_id . sequence_id . id , { ' name ' : name + _ ( ' Sequence packing ' ) , ' prefix ' : vals . get ( ' code ' , warehouse . code ) + ' \ PACK \\ ' } , context = context )
seq_obj . write ( cr , uid , warehouse . pick_type_id . sequence_id . id , { ' name ' : name + _ ( ' Sequence picking ' ) , ' prefix ' : vals . get ( ' code ' , warehouse . code ) + ' \ PICK \\ ' } , context = context )
seq_obj . write ( cr , uid , warehouse . int_type_id . sequence_id . id , { ' name ' : name + _ ( ' Sequence internal ' ) , ' prefix ' : vals . get ( ' code ' , warehouse . code ) + ' \ INT \\ ' } , context = context )
2013-10-16 10:03:18 +00:00
if vals . get ( ' resupply_wh_ids ' ) and not vals . get ( ' resupply_route_ids ' ) :
for cmd in vals . get ( ' resupply_wh_ids ' ) :
if cmd [ 0 ] == 6 :
new_ids = set ( cmd [ 2 ] )
old_ids = set ( [ wh . id for wh in warehouse . resupply_wh_ids ] )
to_add_wh_ids = new_ids - old_ids
2013-11-05 15:49:23 +00:00
if to_add_wh_ids :
2013-12-13 13:26:41 +00:00
supplier_warehouses = self . browse ( cr , uid , list ( to_add_wh_ids ) , context = context )
2013-11-05 15:49:23 +00:00
self . _create_resupply_routes ( cr , uid , warehouse , supplier_warehouses , warehouse . default_resupply_wh_id , context = context )
2013-10-16 10:03:18 +00:00
to_remove_wh_ids = old_ids - new_ids
2013-11-05 15:49:23 +00:00
if to_remove_wh_ids :
to_remove_route_ids = route_obj . search ( cr , uid , [ ( ' supplied_wh_id ' , ' = ' , warehouse . id ) , ( ' supplier_wh_id ' , ' in ' , list ( to_remove_wh_ids ) ) ] , context = context )
if to_remove_route_ids :
route_obj . unlink ( cr , uid , to_remove_route_ids , context = context )
2013-10-16 10:03:18 +00:00
else :
#not implemented
pass
if ' default_resupply_wh_id ' in vals :
2014-01-29 14:00:05 +00:00
if vals . get ( ' default_resupply_wh_id ' ) == warehouse . id :
2014-01-30 09:32:30 +00:00
raise osv . except_osv ( _ ( ' Warning ' ) , _ ( ' The default resupply warehouse should be different than the warehouse itself! ' ) )
2013-10-16 10:03:18 +00:00
if warehouse . default_resupply_wh_id :
2013-11-05 15:49:23 +00:00
#remove the existing resupplying route on all products
2013-10-16 10:03:18 +00:00
to_remove_route_ids = route_obj . search ( cr , uid , [ ( ' supplied_wh_id ' , ' = ' , warehouse . id ) , ( ' supplier_wh_id ' , ' = ' , warehouse . default_resupply_wh_id . id ) ] , context = context )
2013-11-05 15:49:23 +00:00
for inter_wh_route_id in to_remove_route_ids :
self . _unassign_route_on_products ( cr , uid , warehouse , inter_wh_route_id , context = context )
2013-10-16 10:03:18 +00:00
if vals . get ( ' default_resupply_wh_id ' ) :
2013-11-05 15:49:23 +00:00
#assign the new resupplying route on all products
to_assign_route_ids = route_obj . search ( cr , uid , [ ( ' supplied_wh_id ' , ' = ' , warehouse . id ) , ( ' supplier_wh_id ' , ' = ' , vals . get ( ' default_resupply_wh_id ' ) ) ] , context = context )
for inter_wh_route_id in to_assign_route_ids :
self . _assign_route_on_products ( cr , uid , warehouse , inter_wh_route_id , context = context )
2013-10-16 10:03:18 +00:00
2013-10-04 14:56:59 +00:00
return super ( stock_warehouse , self ) . write ( cr , uid , ids , vals = vals , context = context )
def unlink ( self , cr , uid , ids , context = None ) :
#TODO try to delete location and route and if not possible, put them in inactive
return super ( stock_warehouse , self ) . unlink ( cr , uid , ids , context = context )
2013-10-17 12:13:49 +00:00
def get_all_routes_for_wh ( self , cr , uid , warehouse , context = None ) :
2013-11-05 15:49:23 +00:00
all_routes = [ route . id for route in warehouse . route_ids ]
2013-10-17 12:13:49 +00:00
all_routes + = [ warehouse . mto_pull_id . route_id . id ]
return all_routes
def view_all_routes_for_wh ( self , cr , uid , ids , context = None ) :
all_routes = [ ]
for wh in self . browse ( cr , uid , ids , context = context ) :
all_routes + = self . get_all_routes_for_wh ( cr , uid , wh , context = context )
2013-10-25 13:40:23 +00:00
2013-10-17 12:13:49 +00:00
domain = [ ( ' id ' , ' in ' , all_routes ) ]
return {
' name ' : _ ( ' Warehouse \' s Routes ' ) ,
' domain ' : domain ,
' res_model ' : ' stock.location.route ' ,
' type ' : ' ir.actions.act_window ' ,
' view_id ' : False ,
' view_mode ' : ' tree,form ' ,
' view_type ' : ' form ' ,
' limit ' : 20
}
2013-10-04 14:56:59 +00:00
class stock_location_path ( osv . osv ) :
_name = " stock.location.path "
_description = " Pushed Flows "
_order = " name "
def _get_route ( self , cr , uid , ids , context = None ) :
#WARNING TODO route_id is not required, so a field related seems a bad idea >-<
if context is None :
context = { }
result = { }
if context is None :
context = { }
context_with_inactive = context . copy ( )
2013-11-05 15:49:23 +00:00
context_with_inactive [ ' active_test ' ] = False
2013-10-04 14:56:59 +00:00
for route in self . pool . get ( ' stock.location.route ' ) . browse ( cr , uid , ids , context = context_with_inactive ) :
for push_rule in route . push_ids :
result [ push_rule . id ] = True
return result . keys ( )
2013-12-11 10:25:11 +00:00
def _get_rules ( self , cr , uid , ids , context = None ) :
res = [ ]
2014-01-16 10:55:56 +00:00
for route in self . browse ( cr , uid , ids , context = context ) :
2013-12-11 10:25:11 +00:00
res + = [ x . id for x in route . push_ids ]
return res
2013-10-04 14:56:59 +00:00
_columns = {
' name ' : fields . char ( ' Operation Name ' , size = 64 , required = True ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' ) ,
' route_id ' : fields . many2one ( ' stock.location.route ' , ' Route ' ) ,
' location_from_id ' : fields . many2one ( ' stock.location ' , ' Source Location ' , ondelete = ' cascade ' , select = 1 , required = True ) ,
' location_dest_id ' : fields . many2one ( ' stock.location ' , ' Destination Location ' , ondelete = ' cascade ' , select = 1 , required = True ) ,
' delay ' : fields . integer ( ' Delay (days) ' , help = " Number of days to do this transition " ) ,
' invoice_state ' : fields . selection ( [
( " invoiced " , " Invoiced " ) ,
( " 2binvoiced " , " To Be Invoiced " ) ,
( " none " , " Not Applicable " ) ] , " Invoice Status " ,
required = True , ) ,
' picking_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Type of the new Operation ' , required = True , help = " This is the picking type associated with the different pickings " ) ,
' auto ' : fields . selection (
[ ( ' auto ' , ' Automatic Move ' ) , ( ' manual ' , ' Manual Operation ' ) , ( ' transparent ' , ' Automatic No Step Added ' ) ] ,
' Automatic Move ' ,
required = True , select = 1 ,
help = " This is used to define paths the product has to follow within the location tree. \n " \
" The ' Automatic Move ' value will create a stock move after the current one that will be " \
" validated automatically. With ' Manual Operation ' , the stock move has to be validated " \
" by a worker. With ' Automatic No Step Added ' , the location is replaced in the original move. "
) ,
' propagate ' : fields . boolean ( ' Propagate cancel and split ' , help = ' If checked, when the previous move is cancelled or split, the move generated by this move will too ' ) ,
' active ' : fields . related ( ' route_id ' , ' active ' , type = ' boolean ' , string = ' Active ' , store = {
' stock.location.route ' : ( _get_route , [ ' active ' ] , 20 ) ,
' stock.location.path ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' route_id ' ] , 20 ) , } ,
help = " If the active field is set to False, it will allow you to hide the rule without removing it. " ) ,
2013-10-22 12:06:05 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' ) ,
2013-12-11 10:25:11 +00:00
' route_sequence ' : fields . related ( ' route_id ' , ' sequence ' , string = ' Route Sequence ' ,
store = {
' stock.location.route ' : ( _get_rules , [ ' sequence ' ] , 10 ) ,
' stock.location.path ' : ( lambda self , cr , uid , ids , c = { } : ids , [ ' route_id ' ] , 10 ) ,
} ) ,
' sequence ' : fields . integer ( ' Sequence ' ) ,
2013-10-04 14:56:59 +00:00
}
_defaults = {
' auto ' : ' auto ' ,
' delay ' : 1 ,
' invoice_state ' : ' none ' ,
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' procurement.order ' , context = c ) ,
' propagate ' : True ,
' active ' : True ,
}
2014-02-14 08:46:50 +00:00
2013-10-04 14:56:59 +00:00
def _apply ( self , cr , uid , rule , move , context = None ) :
move_obj = self . pool . get ( ' stock.move ' )
2013-12-11 16:48:34 +00:00
newdate = ( datetime . strptime ( move . date_expected , DEFAULT_SERVER_DATETIME_FORMAT ) + relativedelta . relativedelta ( days = rule . delay or 0 ) ) . strftime ( DEFAULT_SERVER_DATETIME_FORMAT )
2013-12-11 10:25:11 +00:00
if rule . auto == ' transparent ' :
old_dest_location = move . location_dest_id . id
2013-10-04 14:56:59 +00:00
move_obj . write ( cr , uid , [ move . id ] , {
' date ' : newdate ,
2013-12-11 14:44:20 +00:00
' date_expected ' : newdate ,
2013-10-04 14:56:59 +00:00
' location_dest_id ' : rule . location_dest_id . id
} )
2013-12-11 10:25:11 +00:00
move . refresh ( )
#avoid looping if a push rule is not well configured
if rule . location_dest_id . id != old_dest_location :
#call again push_apply to see if a next step is defined
move_obj . _push_apply ( cr , uid , [ move ] , context = context )
2013-10-04 14:56:59 +00:00
else :
move_id = move_obj . copy ( cr , uid , move . id , {
' location_id ' : move . location_dest_id . id ,
' location_dest_id ' : rule . location_dest_id . id ,
2013-12-11 14:44:20 +00:00
' date ' : newdate ,
2013-10-04 14:56:59 +00:00
' company_id ' : rule . company_id and rule . company_id . id or False ,
' date_expected ' : newdate ,
' picking_id ' : False ,
' picking_type_id ' : rule . picking_type_id and rule . picking_type_id . id or False ,
2013-12-11 10:25:11 +00:00
' propagate ' : rule . propagate ,
' push_rule_id ' : rule . id ,
2013-10-22 12:06:05 +00:00
' warehouse_id ' : rule . warehouse_id and rule . warehouse_id . id or False ,
2013-10-04 14:56:59 +00:00
} )
move_obj . write ( cr , uid , [ move . id ] , {
' move_dest_id ' : move_id ,
} )
move_obj . action_confirm ( cr , uid , [ move_id ] , context = None )
2010-07-06 11:21:27 +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
2013-11-20 14:23:29 +00:00
report_sxw . report_sxw ( ' report.stock.quant.package.barcode ' , ' stock.quant.package ' , ' addons/stock/report/package_barcode.rml ' )
2013-07-08 10:34:00 +00:00
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-09-06 09:57:26 +00:00
_parent_name = " parent_id "
_parent_store = True
_parent_order = ' name '
_order = ' parent_left '
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-09-06 09:57:26 +00:00
# TODO: Problem when package is empty!
#
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
2013-09-06 09:57:26 +00:00
res [ pack . id ] [ ' owner_id ' ] = pack . quant_ids [ 0 ] . owner_id and pack . quant_ids [ 0 ] . owner_id . id or False
2013-08-02 09:04:46 +00:00
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-09-06 09:57:26 +00:00
res [ pack . id ] [ ' location_id ' ] = pack . children_ids [ 0 ] . location_id and pack . children_ids [ 0 ] . location_id . id or False
res [ pack . id ] [ ' owner_id ' ] = pack . children_ids [ 0 ] . owner_id and pack . children_ids [ 0 ] . owner_id . id or False
res [ pack . id ] [ ' company_id ' ] = pack . children_ids [ 0 ] . company_id and pack . children_ids [ 0 ] . company_id . id or False
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 ) ,
2013-09-06 09:57:26 +00:00
' stock.quant.package ' : ( _get_packages_to_relocate , [ ' quant_ids ' , ' children_ids ' , ' parent_id ' ] , 10 ) ,
2013-08-02 08:00:07 +00:00
} , readonly = True ) ,
2014-02-04 15:49:54 +00:00
' quant_ids ' : fields . one2many ( ' stock.quant ' , ' package_id ' , ' Bulk Content ' , readonly = True ) ,
' parent_id ' : fields . many2one ( ' stock.quant.package ' , ' Parent Package ' , help = " The package containing this item " , ondelete = ' restrict ' , readonly = True ) ,
' children_ids ' : fields . one2many ( ' stock.quant.package ' , ' parent_id ' , ' Contained Packages ' , readonly = True ) ,
2013-09-06 09:57:26 +00:00
' company_id ' : fields . function ( _get_package_info , type = " many2one " , relation = ' res.company ' , string = ' Company ' , multi = " package " ,
store = {
' stock.quant ' : ( _get_packages , [ ' company_id ' ] , 10 ) ,
' stock.quant.package ' : ( _get_packages_to_relocate , [ ' quant_ids ' , ' children_ids ' , ' parent_id ' ] , 10 ) ,
} , readonly = True ) ,
' owner_id ' : fields . function ( _get_package_info , type = ' many2one ' , relation = ' res.partner ' , string = ' Owner ' , multi = " package " ,
store = {
' stock.quant ' : ( _get_packages , [ ' owner_id ' ] , 10 ) ,
' stock.quant.package ' : ( _get_packages_to_relocate , [ ' quant_ids ' , ' children_ids ' , ' parent_id ' ] , 10 ) ,
} , readonly = True ) ,
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-08-02 08:00:07 +00:00
2014-03-19 16:33:59 +00:00
def _check_location_constraint ( self , cr , uid , ids , context = None ) :
2014-02-06 15:31:28 +00:00
''' checks that all quants in a package are stored in the same location. This function cannot be used
as a constraint because it needs to be checked on pack operations ( they may not call write on the
package )
'''
2014-03-19 16:33:59 +00:00
quant_obj = self . pool . get ( ' stock.quant ' )
for pack in self . browse ( cr , uid , ids , context = context ) :
2014-02-06 15:31:28 +00:00
parent = pack
while parent . parent_id :
parent = parent . parent_id
2014-03-19 16:33:59 +00:00
quant_ids = self . get_content ( cr , uid , [ parent . id ] , context = context )
quants = quant_obj . browse ( cr , uid , quant_ids , context = context )
2014-02-06 15:31:28 +00:00
location_id = quants and quants [ 0 ] . location_id . id or False
2014-02-14 10:24:26 +00:00
if not all ( [ quant . location_id . id == location_id for quant in quants if quant . qty > 0 ] ) :
2014-02-06 15:31:28 +00:00
raise osv . except_osv ( _ ( ' Error ' ) , _ ( ' Everything inside a package should be in the same location ' ) )
return True
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-11-18 09:50:21 +00:00
def _get_all_products_quantities ( self , cr , uid , package_id , context = None ) :
2013-11-15 17:55:05 +00:00
''' This function computes the different product quantities for the given package
'''
quant_obj = self . pool . get ( ' stock.quant ' )
res = { }
2013-11-18 10:57:17 +00:00
for quant in quant_obj . browse ( cr , uid , self . get_content ( cr , uid , package_id , context = context ) ) :
2013-11-15 17:55:05 +00:00
if quant . product_id . id not in res :
res [ quant . product_id . id ] = 0
res [ quant . product_id . id ] + = quant . qty
return res
2013-06-26 13:10:04 +00:00
2014-02-13 09:40:54 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
if default is None :
default = { }
if not default . get ( ' name ' ) :
default [ ' name ' ] = self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.quant.package ' ) or _ ( ' Unknown Pack ' )
return super ( stock_package , self ) . copy ( cr , uid , id , default , context = context )
def copy_pack ( self , cr , uid , id , default_pack_values = None , default = None , context = None ) :
stock_pack_operation_obj = self . pool . get ( ' stock.pack.operation ' )
if default is None :
default = { }
new_package_id = self . copy ( cr , uid , id , default_pack_values , context = context )
default [ ' result_package_id ' ] = new_package_id
op_ids = stock_pack_operation_obj . search ( cr , uid , [ ( ' result_package_id ' , ' = ' , id ) ] , context = context )
for op_id in op_ids :
stock_pack_operation_obj . copy ( cr , uid , op_id , default , context = context )
2013-06-20 09:31:02 +00:00
class stock_pack_operation ( osv . osv ) :
_name = " stock.pack.operation "
_description = " Packing Operation "
2013-10-17 16:07:25 +00:00
2013-11-18 09:50:21 +00:00
def _get_remaining_prod_quantities ( self , cr , uid , operation , context = None ) :
2013-11-18 08:30:26 +00:00
''' Get the remaining quantities per product on an operation with a package. This function returns a dictionary '''
#if the operation doesn't concern a package, it's not relevant to call this function
2013-11-19 15:33:00 +00:00
if not operation . package_id or operation . product_id :
2013-11-18 09:50:21 +00:00
return { operation . product_id . id : operation . remaining_qty }
2013-11-18 08:30:26 +00:00
#get the total of products the package contains
2013-11-18 09:50:21 +00:00
res = self . pool . get ( ' stock.quant.package ' ) . _get_all_products_quantities ( cr , uid , operation . package_id . id , context = context )
2013-11-18 08:30:26 +00:00
#reduce by the quantities linked to a move
2013-11-15 17:55:05 +00:00
for record in operation . linked_move_operation_ids :
2013-11-18 10:57:17 +00:00
if record . move_id . product_id . id not in res :
res [ record . move_id . product_id . id ] = 0
res [ record . move_id . product_id . id ] - = record . qty
2013-11-18 08:30:26 +00:00
return res
2013-11-15 17:55:05 +00:00
2013-10-29 07:48:15 +00:00
def _get_remaining_qty ( self , cr , uid , ids , name , args , context = None ) :
2013-11-18 09:50:21 +00:00
uom_obj = self . pool . get ( ' product.uom ' )
2013-09-24 14:32:20 +00:00
res = { }
for ops in self . browse ( cr , uid , ids , context = context ) :
2013-11-19 15:33:00 +00:00
res [ ops . id ] = 0
2014-02-18 09:12:22 +00:00
if ops . package_id and not ops . product_id :
2013-11-15 17:55:05 +00:00
#dont try to compute the remaining quantity for packages because it's not relevant (a package could include different products).
2013-11-18 09:50:21 +00:00
#should use _get_remaining_prod_quantities instead
2013-11-15 17:55:05 +00:00
continue
2014-02-18 09:12:22 +00:00
else :
2013-11-19 15:33:00 +00:00
qty = ops . product_qty
if ops . product_uom_id :
2014-02-28 17:32:41 +00:00
qty = uom_obj . _compute_qty_obj ( cr , uid , ops . product_uom_id , ops . product_qty , ops . product_id . uom_id , context = context )
2013-11-18 09:50:21 +00:00
for record in ops . linked_move_operation_ids :
qty - = record . qty
2013-11-15 17:55:05 +00:00
#converting the remaining quantity in the pack operation UoM
2013-11-19 15:33:00 +00:00
if ops . product_uom_id :
2014-02-28 17:32:41 +00:00
qty = uom_obj . _compute_qty_obj ( cr , uid , ops . product_id . uom_id , qty , ops . product_uom_id , context = context )
2013-11-18 09:50:21 +00:00
res [ ops . id ] = qty
2013-09-24 14:32:20 +00:00
return res
2013-10-17 16:07:25 +00:00
2013-11-12 14:26:56 +00:00
def product_id_change ( self , cr , uid , ids , product_id , product_uom_id , product_qty , context = None ) :
2013-11-12 16:15:02 +00:00
res = self . on_change_tests ( cr , uid , ids , product_id , product_uom_id , product_qty , context = context )
if product_id and not product_uom_id :
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
res [ ' value ' ] [ ' product_uom_id ' ] = product . uom_id . id
return res
def on_change_tests ( self , cr , uid , ids , product_id , product_uom_id , product_qty , context = None ) :
2013-11-12 14:26:56 +00:00
res = { ' value ' : { } }
uom_obj = self . pool . get ( ' product.uom ' )
if product_id :
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
2013-11-12 16:15:02 +00:00
product_uom_id = product_uom_id or product . uom_id . id
2013-11-12 14:26:56 +00:00
selected_uom = uom_obj . browse ( cr , uid , product_uom_id , context = context )
if selected_uom . category_id . id != product . uom_id . category_id . id :
res [ ' warning ' ] = {
' title ' : _ ( ' Warning: wrong UoM! ' ) ,
' message ' : _ ( ' The selected UoM for product %s is not compatible with the UoM set on the product form. \n Please choose an UoM within the same UoM category. ' ) % ( product . name )
}
if product_qty and ' warning ' not in res :
rounded_qty = uom_obj . _compute_qty ( cr , uid , product_uom_id , product_qty , product_uom_id , round = True )
if rounded_qty != product_qty :
res [ ' warning ' ] = {
' title ' : _ ( ' Warning: wrong quantity! ' ) ,
' message ' : _ ( ' The chosen quantity for product %s is not compatible with the UoM rounding. It will be automatically converted at confirmation ' ) % ( product . name )
}
2013-09-24 14:32:20 +00:00
return res
2013-10-17 16:07:25 +00:00
2013-06-20 09:31:02 +00:00
_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 ) ,
2014-03-06 13:38:55 +00:00
' qty_done ' : fields . float ( ' Quantity Processed ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) ) ,
2013-06-24 09:41:50 +00:00
' package_id ' : fields . many2one ( ' stock.quant.package ' , ' Package ' ) , # 2
2013-11-12 14:26:56 +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 ' ) ,
2013-11-15 17:55:05 +00:00
' linked_move_operation_ids ' : fields . one2many ( ' stock.move.operation.link ' , ' operation_id ' , string = ' Linked Moves ' , readonly = True , help = ' Moves impacted by this operation for the computation of the remaining quantities ' ) ,
2013-09-24 14:32:20 +00:00
' remaining_qty ' : fields . function ( _get_remaining_qty , type = ' float ' , string = ' Remaining Qty ' ) ,
2014-03-11 13:31:09 +00:00
' location_id ' : fields . many2one ( ' stock.location ' , ' Location From ' , required = True ) ,
' location_dest_id ' : fields . many2one ( ' stock.location ' , ' Location To ' , required = True ) ,
2014-03-06 13:38:55 +00:00
' processed ' : fields . selection ( [ ( ' true ' , ' Yes ' ) , ( ' false ' , ' No ' ) ] , ' Has been processed? ' , required = True ) ,
2013-07-23 15:34:24 +00:00
}
_defaults = {
2013-11-12 14:26:56 +00:00
' date ' : fields . date . context_today ,
2014-03-06 13:38:55 +00:00
' qty_done ' : 0 ,
' processed ' : lambda * a : ' false ' ,
2013-06-20 09:31:02 +00:00
}
2014-02-12 10:55:53 +00:00
def write ( self , cr , uid , ids , vals , context = None ) :
2014-03-11 15:29:04 +00:00
context = context or { }
2014-02-12 10:55:53 +00:00
res = super ( stock_pack_operation , self ) . write ( cr , uid , ids , vals , context = context )
2014-02-13 10:59:13 +00:00
if isinstance ( ids , ( int , long ) ) :
ids = [ ids ]
2014-03-10 15:11:33 +00:00
if not context . get ( " no_recompute " ) :
2014-03-20 18:03:10 +00:00
pickings = vals . get ( ' picking_id ' ) and [ vals [ ' picking_id ' ] ] or list ( set ( [ x . picking_id . id for x in self . browse ( cr , uid , ids , context = context ) ] ) )
2014-03-19 16:33:59 +00:00
self . pool . get ( " stock.picking " ) . do_recompute_remaining_quantities ( cr , uid , pickings , context = context )
2014-02-12 10:55:53 +00:00
return res
def create ( self , cr , uid , vals , context = None ) :
2014-03-11 15:29:04 +00:00
context = context or { }
2014-02-12 10:55:53 +00:00
res_id = super ( stock_pack_operation , self ) . create ( cr , uid , vals , context = context )
2014-03-10 15:11:33 +00:00
if vals . get ( " picking_id " ) and not context . get ( " no_recompute " ) :
self . pool . get ( " stock.picking " ) . do_recompute_remaining_quantities ( cr , uid , [ vals [ ' picking_id ' ] ] , context = context )
2014-02-12 10:55:53 +00:00
return res_id
2013-06-26 14:32:17 +00:00
2014-03-07 13:25:23 +00:00
def action_drop_down ( self , cr , uid , ids , context = None ) :
''' Used by barcode interface to say that pack_operation has been moved from src location
to destination location , if qty_done is less than product_qty than we have to split the
operation in two to process the one with the qty moved
'''
processed_ids = [ ]
for pack_op in self . browse ( cr , uid , ids , context = None ) :
op = pack_op . id
if pack_op . qty_done < pack_op . product_qty :
# we split the operation in two
op = self . copy ( cr , uid , pack_op . id , { ' product_qty ' : pack_op . qty_done , ' qty_done ' : pack_op . qty_done } , context = context )
self . write ( cr , uid , ids , { ' product_qty ' : pack_op . product_qty - pack_op . qty_done , ' qty_done ' : 0 } , context = context )
processed_ids . append ( op )
self . write ( cr , uid , processed_ids , { ' processed ' : ' true ' } , context = context )
2014-03-18 10:40:25 +00:00
def create_and_assign_lot ( self , cr , uid , id , context = None ) :
''' Used by barcode interface to create a new lot and assign it to the operation
'''
obj = self . browse ( cr , uid , id , context )
product_id = obj . product_id . id
if not obj . lot_id :
new_lot_id = self . pool . get ( ' stock.production.lot ' ) . create ( cr , uid , { ' product_id ' : product_id } , context = context )
self . write ( cr , uid , id , { ' lot_id ' : new_lot_id } , context = context )
2013-07-23 15:34:24 +00:00
#TODO: this function can be refactored
2014-03-24 10:46:21 +00:00
def _search_and_increment ( self , cr , uid , picking_id , domain , filter_visible = False , visible_op_ids = False , increment = True , context = None ) :
2013-11-20 10:42:33 +00:00
''' Search for an operation with given ' domain ' in a picking, if it exists increment the qty (+1) otherwise create it
2013-06-24 15:40:18 +00:00
2013-11-20 10:42:33 +00:00
: param domain : list of tuple directly reusable as 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-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-11-20 10:42:33 +00:00
existing_operation_ids = self . search ( cr , uid , [ ( ' picking_id ' , ' = ' , picking_id ) ] + domain + package_clause , context = context )
2014-03-24 10:46:21 +00:00
todo_operation_ids = [ ]
2013-06-24 15:40:18 +00:00
if existing_operation_ids :
2014-03-24 10:46:21 +00:00
if filter_visible :
todo_operation_ids = [ val for val in existing_operation_ids if val in visible_op_ids ]
else :
todo_operation_ids = existing_operation_ids
if todo_operation_ids :
2013-11-20 10:42:33 +00:00
#existing operation found for the given domain and picking => increment its quantity
2014-03-24 10:46:21 +00:00
operation_id = todo_operation_ids [ 0 ]
op_obj = self . browse ( cr , uid , operation_id , context = context )
qty = op_obj . qty_done
2014-03-18 10:40:25 +00:00
if increment :
qty + = 1
else :
qty - = 1 if qty > = 1 else 0
2014-03-24 10:46:21 +00:00
if qty == 0 and op_obj . product_qty == 0 :
#we have a line with 0 qty set, so delete it
self . unlink ( cr , uid , [ operation_id ] , context = context )
return False
2014-03-06 13:38:55 +00:00
self . write ( cr , uid , [ operation_id ] , { ' qty_done ' : qty } , context = context )
2013-06-25 09:00:27 +00:00
else :
2013-11-20 10:42:33 +00:00
#no existing operation found for the given domain and picking => create a new one
2013-06-26 13:10:04 +00:00
values = {
2013-06-25 09:00:27 +00:00
' picking_id ' : picking_id ,
2014-03-24 10:46:21 +00:00
' product_qty ' : 0 ,
2014-03-06 13:38:55 +00:00
' qty_done ' : 1 ,
2013-06-26 13:10:04 +00:00
}
2013-11-20 10:42:33 +00:00
for key in domain :
var_name , dummy , value = key
uom_id = False
if var_name == ' product_id ' :
uom_id = self . pool . get ( ' product.product ' ) . browse ( cr , uid , value , context = context ) . uom_id . id
update_dict = { var_name : value }
if uom_id :
update_dict [ ' product_uom_id ' ] = uom_id
values . update ( update_dict )
2013-06-26 13:10:04 +00:00
operation_id = self . create ( cr , uid , values , context = context )
2014-03-10 16:57:07 +00:00
return operation_id
2013-06-24 15:40:18 +00:00
2013-11-15 17:55:05 +00:00
class stock_move_operation_link ( osv . osv ) :
"""
Table making the link between stock . moves and stock . pack . operations to compute the remaining quantities on each of these objects
"""
_name = " stock.move.operation.link "
_description = " Link between stock moves and pack operations "
_columns = {
' qty ' : fields . float ( ' Quantity ' , help = " Quantity of products to consider when talking about the contribution of this pack operation towards the remaining quantity of the move (and inverse). Given in the product main uom. " ) ,
2013-11-18 09:50:21 +00:00
' operation_id ' : fields . many2one ( ' stock.pack.operation ' , ' Operation ' , required = True , ondelete = " cascade " ) ,
2013-11-15 17:55:05 +00:00
' move_id ' : fields . many2one ( ' stock.move ' , ' Move ' , required = True , ondelete = " cascade " ) ,
2014-03-25 15:06:38 +00:00
' reserved_quant_id ' : fields . many2one ( ' stock.quant ' , ' Reserved Quant ' , help = " Technical field containing the quant that created this link between an operation and a stock move. Used at the stock_move_obj.action_done() time to avoid seeking a matching quant again " ) ,
2013-11-15 17:55:05 +00:00
}
2013-11-19 15:33:00 +00:00
def get_specific_domain ( self , cr , uid , record , context = None ) :
''' Returns the specific domain to consider for quant selection in action_assign() or action_done() of stock.move,
having the record given as parameter making the link between the stock move and a pack operation '''
op = record . operation_id
domain = [ ]
2014-02-28 11:00:48 +00:00
if op . package_id and op . product_id :
#if removing a product from a box, we restrict the choice of quants to this box
domain . append ( ( ' package_id ' , ' = ' , op . package_id . id ) )
elif op . package_id :
#if moving a box, we allow to take everything from inside boxes as well
domain . append ( ( ' package_id ' , ' child_of ' , [ op . package_id . id ] ) )
else :
#if not given any information about package, we don't open boxes
domain . append ( ( ' package_id ' , ' = ' , False ) )
#if lot info is given, we restrict choice to this lot otherwise we can take any
2013-11-19 15:33:00 +00:00
if op . lot_id :
domain . append ( ( ' lot_id ' , ' = ' , op . lot_id . id ) )
2014-02-28 11:00:48 +00:00
#if owner info is given, we restrict to this owner otherwise we restrict to no owner
2013-11-19 15:33:00 +00:00
if op . owner_id :
domain . append ( ( ' owner_id ' , ' = ' , op . owner_id . id ) )
else :
domain . append ( ( ' owner_id ' , ' = ' , False ) )
return domain
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 "
2014-04-03 14:52:16 +00:00
def subtract_procurements ( self , cr , uid , orderpoint , context = None ) :
''' This function returns quantity of product that needs to be deducted from the orderpoint computed quantity because there ' s already a procurement created with aim to fulfill it.
'''
qty = 0
for procurement in orderpoint . procurement_ids :
if procurement in ( ' cancel ' , ' done ' ) :
continue
for move in procurement . move_ids :
if move . state not in ( ' draft ' , ' cancel ' ) :
qty + = move . product_qty
return qty
2013-06-30 22:26:46 +00:00
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
2014-04-03 14:52:16 +00:00
def action_view_proc_to_process ( self , cr , uid , ids , context = None ) :
2013-11-05 16:15:17 +00:00
act_obj = self . pool . get ( ' ir.actions.act_window ' )
mod_obj = self . pool . get ( ' ir.model.data ' )
2014-04-03 14:52:16 +00:00
proc_ids = self . pool . get ( ' procurement.order ' ) . search ( cr , uid , [ ( ' orderpoint_id ' , ' in ' , ids ) , ( ' state ' , ' not in ' , ( ' done ' , ' cancel ' ) ) ] , context = context )
2013-11-05 16:15:17 +00:00
result = mod_obj . get_object_reference ( cr , uid , ' procurement ' , ' do_view_procurements ' )
2013-11-08 16:52:17 +00:00
if not result :
return False
2014-04-03 14:52:16 +00:00
2013-11-08 16:52:17 +00:00
result = act_obj . read ( cr , uid , [ result [ 1 ] ] , context = context ) [ 0 ]
2014-04-03 14:52:16 +00:00
result [ ' domain ' ] = " [( ' id ' , ' in ' , [ " + ' , ' . join ( map ( str , proc_ids ) ) + " ])] "
2013-11-05 16:15:17 +00:00
return result
2013-11-08 16:52:17 +00:00
2013-06-30 22:26:46 +00:00
_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. " ) ,
2014-04-03 14:52:16 +00:00
' procurement_ids ' : fields . one2many ( ' procurement.order ' , ' orderpoint_id ' , ' Created Procurements ' ) ,
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True )
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 )
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-12-20 13:09:21 +00:00
_order = ' sequence '
2013-07-28 12:42:01 +00:00
2014-02-26 16:10:03 +00:00
def open_barcode_interface ( self , cr , uid , ids , context = None ) :
2014-03-24 15:56:28 +00:00
final_url = " /barcode/web/#action=stock.ui&picking_type_id= " + str ( ids [ 0 ] ) if len ( ids ) else ' 0 '
2014-02-26 16:10:03 +00:00
return { ' type ' : ' ir.actions.act_url ' , ' url ' : final_url , ' target ' : ' self ' , }
2014-01-06 14:30:02 +00:00
def _get_tristate_values ( self , cr , uid , ids , field_name , arg , context = None ) :
picking_obj = self . pool . get ( ' stock.picking ' )
res = dict . fromkeys ( ids , [ ] )
for picking_type_id in ids :
#get last 10 pickings of this type
picking_ids = picking_obj . search ( cr , uid , [ ( ' picking_type_id ' , ' = ' , picking_type_id ) , ( ' state ' , ' = ' , ' done ' ) ] , order = ' date_done desc ' , limit = 10 , context = context )
tristates = [ ]
for picking in picking_obj . browse ( cr , uid , picking_ids , context = context ) :
if picking . date_done > picking . date :
tristates . insert ( 0 , { ' tooltip ' : picking . name + _ ( ' : Late ' ) , ' value ' : - 1 } )
elif picking . backorder_id :
tristates . insert ( 0 , { ' tooltip ' : picking . name + _ ( ' : Backorder exists ' ) , ' value ' : 0 } )
else :
tristates . insert ( 0 , { ' tooltip ' : picking . name + _ ( ' : OK ' ) , ' value ' : 1 } )
res [ picking_type_id ] = tristates
return res
2013-07-28 12:42:01 +00:00
def _get_picking_count ( self , cr , uid , ids , field_names , arg , context = None ) :
obj = self . pool . get ( ' stock.picking ' )
domains = {
2013-09-16 14:38:49 +00:00
' count_picking_draft ' : [ ( ' state ' , ' = ' , ' draft ' ) ] ,
2014-02-03 09:31:15 +00:00
' count_picking_waiting ' : [ ( ' state ' , ' = ' , ' confirmed ' ) ] ,
' count_picking_ready ' : [ ( ' state ' , ' in ' , ( ' assigned ' , ' partially_available ' ) ) ] ,
' count_picking ' : [ ( ' state ' , ' in ' , ( ' assigned ' , ' waiting ' , ' confirmed ' , ' partially_available ' ) ) ] ,
' count_picking_late ' : [ ( ' min_date ' , ' < ' , time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) ) , ( ' state ' , ' in ' , ( ' assigned ' , ' waiting ' , ' confirmed ' , ' partially_available ' ) ) ] ,
' count_picking_backorders ' : [ ( ' backorder_id ' , ' != ' , False ) , ( ' state ' , ' in ' , ( ' confirmed ' , ' assigned ' , ' waiting ' , ' partially_available ' ) ) ] ,
2013-07-28 12:42:01 +00:00
}
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 ] +
2014-02-03 09:31:15 +00:00
[ ( ' state ' , ' not in ' , ( ' done ' , ' cancel ' ) ) , ( ' picking_type_id ' , ' in ' , ids ) ] ,
2013-07-28 12:42:01 +00:00
[ ' 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 ' ] :
2014-02-03 09:31:15 +00:00
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 ' ]
2013-07-28 13:16:47 +00:00
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 }
2013-09-05 13:33:49 +00:00
def _get_name ( self , cr , uid , ids , field_names , arg , context = None ) :
return dict ( self . name_get ( cr , uid , ids , context = context ) )
2013-09-03 08:01:21 +00:00
def name_get ( self , cr , uid , ids , context = None ) :
""" Overides orm name_get method to display ' Warehouse_name: PickingType_name ' """
2013-09-27 12:21:48 +00:00
if context is None :
context = { }
2013-09-03 08:01:21 +00:00
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-27 12:21:48 +00:00
if context . get ( ' special_shortened_wh_name ' ) :
if record . warehouse_id :
name = record . warehouse_id . name
else :
name = _ ( ' Customer ' ) + ' ( ' + record . 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 = {
2014-02-12 14:13:02 +00:00
' name ' : fields . char ( ' Picking Type Name ' , translate = True , required = True ) ,
2013-09-05 13:33:49 +00:00
' complete_name ' : fields . function ( _get_name , type = ' char ' , string = ' Name ' ) ,
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-11-08 16:52:17 +00:00
' color ' : fields . integer ( ' Color ' ) ,
2013-12-20 13:09:21 +00:00
' sequence ' : fields . integer ( ' Sequence ' , help = " Used to order the ' All Operations ' kanban view " ) ,
2013-11-04 14:23:40 +00:00
' sequence_id ' : fields . many2one ( ' ir.sequence ' , ' Reference 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-12-18 13:33:06 +00:00
' code ' : fields . selection ( [ ( ' incoming ' , ' Suppliers ' ) , ( ' outgoing ' , ' Customers ' ) , ( ' internal ' , ' Internal ' ) ] , ' Type of Operation ' , required = True ) ,
2013-08-07 09:24:33 +00:00
' return_picking_type_id ' : fields . many2one ( ' stock.picking.type ' , ' Picking Type for Returns ' ) ,
2013-11-08 16:52:17 +00:00
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , ondelete = ' cascade ' ) ,
2013-10-02 14:56:07 +00:00
' active ' : fields . boolean ( ' Active ' ) ,
2013-07-28 12:42:01 +00:00
# Statistics for the kanban view
2014-01-06 14:30:02 +00:00
' last_done_picking ' : fields . function ( _get_tristate_values ,
2013-07-29 21:44:43 +00:00
type = ' string ' ,
2014-01-06 14:30:02 +00:00
string = ' Last 10 Done Pickings ' ) ,
2013-07-28 12:42:01 +00:00
2013-09-16 14:38:49 +00:00
' count_picking_draft ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
' count_picking_ready ' : fields . function ( _get_picking_count ,
type = ' integer ' , multi = ' _get_picking_count ' ) ,
2013-07-28 12:42:01 +00:00
' 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-10-02 14:56:07 +00:00
' active ' : True ,
2013-09-03 08:01:21 +00:00
}
2013-07-23 09:44:36 +00:00
2013-07-09 13:22:39 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: