2006-12-07 13:41:40 +00:00
##############################################################################
#
# Copyright (c) 2004-2006 TINY SPRL. (http://tiny.be) All Rights Reserved.
#
# $Id: stock.py 1005 2005-07-25 08:41:42Z nicoe $
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# 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
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
import datetime
import time
import netsvc
from osv import fields , osv
import ir
#----------------------------------------------------------
# Incoterms
#----------------------------------------------------------
class stock_incoterms ( osv . osv ) :
_name = " stock.incoterms "
_description = " Incoterms "
_columns = {
' name ' : fields . char ( ' Name ' , size = 64 , required = True ) ,
' code ' : fields . char ( ' Code ' , size = 3 , required = True ) ,
' active ' : fields . boolean ( ' Active ' ) ,
}
_defaults = {
' active ' : lambda * a : True ,
}
stock_incoterms ( )
class stock_lot ( osv . osv ) :
_name = " stock.lot "
_description = " Lot "
_columns = {
' name ' : fields . char ( ' Lot Name ' , size = 64 , required = True ) ,
' active ' : fields . boolean ( ' Active ' ) ,
' tracking ' : fields . char ( ' Tracking ' , size = 64 ) ,
' move_ids ' : fields . one2many ( ' stock.move ' , ' lot_id ' , ' Move lines ' ) ,
}
_defaults = {
' active ' : lambda * a : True ,
}
stock_lot ( )
#----------------------------------------------------------
# Stock Location
#----------------------------------------------------------
class stock_location ( osv . osv ) :
_name = " stock.location "
_description = " Location "
_columns = {
' name ' : fields . char ( ' Location Name ' , size = 64 , required = True ) ,
' active ' : fields . boolean ( ' Active ' ) ,
' usage ' : fields . selection ( [ ( ' supplier ' , ' Supplier Location ' ) , ( ' internal ' , ' Internal Location ' ) , ( ' customer ' , ' Customer Location ' ) , ( ' inventory ' , ' Inventory ' ) , ( ' procurement ' , ' Procurement ' ) , ( ' production ' , ' Production ' ) ] , ' Location type ' ) ,
' allocation_method ' : fields . selection ( [ ( ' fifo ' , ' FIFO ' ) , ( ' lifo ' , ' LIFO ' ) , ( ' nearest ' , ' Nearest ' ) ] , ' Allocation Method ' , required = True ) ,
' account_id ' : fields . many2one ( ' account.account ' , string = ' Inventory Account ' , domain = [ ( ' type ' , ' = ' , ' stock_inventory ' ) ] ) ,
' location_id ' : fields . many2one ( ' stock.location ' , ' Parent Location ' , select = True ) ,
' child_ids ' : fields . one2many ( ' stock.location ' , ' location_id ' , ' Contains ' ) ,
' comment ' : fields . text ( ' Additional Information ' ) ,
' posx ' : fields . integer ( ' Position X ' , required = True ) ,
' posy ' : fields . integer ( ' Position Y ' , required = True ) ,
' posz ' : fields . integer ( ' Position Z ' , required = True )
}
_defaults = {
' active ' : lambda * a : 1 ,
' usage ' : lambda * a : ' internal ' ,
' allocation_method ' : lambda * a : ' fifo ' ,
' posx ' : lambda * a : 0 ,
' posy ' : lambda * a : 0 ,
' posz ' : lambda * a : 0 ,
}
def _product_get_all_report ( self , cr , uid , ids , product_ids = False , context = { } ) :
return self . _product_get_report ( cr , uid , ids , product_ids , context , recursive = True )
def _product_get_report ( self , cr , uid , ids , product_ids = False , context = { } , recursive = False ) :
if not product_ids :
product_ids = self . pool . get ( ' product.product ' ) . search ( cr , uid , [ ] )
result = [ ]
for id in ids :
for prod_id in product_ids :
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , [ prod_id ] ) [ 0 ]
fnc = self . _product_get
if recursive :
fnc = self . _product_all_get
qty = fnc ( cr , uid , id , [ prod_id ] , { ' uom ' : product . uom_id . id } )
if qty [ prod_id ] :
result . append ( {
' price ' : product . list_price ,
' name ' : product . name ,
' code ' : product . default_code , # used by lot_overview_all report !
' variants ' : product . variants or ' ' ,
' uom ' : product . uom_id . name ,
' amount ' : qty [ prod_id ]
} )
return result
def _product_get_multi_location ( self , cr , uid , ids , product_ids = False , context = { } , states = [ ' done ' ] , what = ( ' in ' , ' out ' ) ) :
states_str = ' , ' . join ( map ( lambda s : " ' %s ' " % s , states ) )
if not product_ids :
product_ids = self . pool . get ( ' product.product ' ) . search ( cr , uid , [ ] )
res = { }
for id in product_ids :
res [ id ] = 0.0
if not ids :
return res
prod_ids_str = ' , ' . join ( map ( str , product_ids ) )
location_ids_str = ' , ' . join ( map ( str , ids ) )
results = [ ]
if ' in ' in what :
# all moves from a location out of the set to a location in the set
cr . execute (
' select sum(product_qty), product_id, product_uom ' \
' from stock_move ' \
' where location_id not in ( ' + location_ids_str + ' ) ' \
' and location_dest_id in ( ' + location_ids_str + ' ) ' \
' and product_id in ( ' + prod_ids_str + ' ) ' \
' and state in ( ' + states_str + ' ) ' \
' group by product_id,product_uom '
)
results + = cr . fetchall ( )
if ' out ' in what :
# all moves from a location in the set to a location out of the set
cr . execute (
' select -sum(product_qty), product_id, product_uom ' \
' from stock_move ' \
' where location_id in ( ' + location_ids_str + ' ) ' \
' and location_dest_id not in ( ' + location_ids_str + ' ) ' \
' and product_id in ( ' + prod_ids_str + ' ) ' \
' and state in ( ' + states_str + ' ) ' \
' group by product_id,product_uom '
)
results + = cr . fetchall ( )
uom_obj = self . pool . get ( ' product.uom ' )
for amount , prod_id , prod_uom in results :
amount = uom_obj . _compute_qty ( cr , uid , prod_uom , amount , context . get ( ' uom ' , False ) )
res [ prod_id ] + = amount
return res
def _product_get ( self , cr , uid , id , product_ids = False , context = { } , states = [ ' done ' ] ) :
ids = id and [ id ] or [ ]
return self . _product_get_multi_location ( cr , uid , ids , product_ids , context , states )
def _product_all_get ( self , cr , uid , id , product_ids = False , context = { } , states = [ ' done ' ] ) :
# build the list of ids of children of the location given by id
ids = id and [ id ] or [ ]
location_ids = self . search ( cr , uid , [ ( ' location_id ' , ' child_of ' , ids ) ] )
return self . _product_get_multi_location ( cr , uid , location_ids , product_ids , context , states )
def _product_virtual_get ( self , cr , uid , id , product_ids = False , context = { } , states = [ ' done ' ] ) :
return self . _product_all_get ( cr , uid , id , product_ids , context , [ ' confirmed ' , ' waiting ' , ' assigned ' , ' done ' ] )
#
# TODO:
# Improve this function
#
# Returns:
# [ (tracking_id, product_qty, location_id) ]
#
def _product_reserve ( self , cr , uid , ids , product_id , product_qty , context = { } ) :
result = [ ]
amount = 0.0
for id in self . search ( cr , uid , [ ( ' location_id ' , ' child_of ' , ids ) ] ) :
cr . execute ( " select product_uom,sum(product_qty) as product_qty from stock_move where location_dest_id= %d and product_id= %d and state= ' done ' group by product_uom " , ( id , product_id ) )
results = cr . dictfetchall ( )
cr . execute ( " select product_uom,-sum(product_qty) as product_qty from stock_move where location_id= %d and product_id= %d and state in ( ' done ' , ' assigned ' ) group by product_uom " , ( id , product_id ) )
results + = cr . dictfetchall ( )
total = 0.0
results2 = 0.0
for r in results :
amount = self . pool . get ( ' product.uom ' ) . _compute_qty ( cr , uid , r [ ' product_uom ' ] , r [ ' product_qty ' ] , context . get ( ' uom ' , False ) )
results2 + = amount
total + = amount
if total < = 0.0 :
continue
amount = results2
if amount > 0 :
if amount > min ( total , product_qty ) :
amount = min ( product_qty , total )
result . append ( ( amount , id ) )
product_qty - = amount
total - = amount
if product_qty < = 0.0 :
return result
if total < = 0.0 :
continue
return False
stock_location ( )
#----------------------------------------------------------
# Stock Move
#----------------------------------------------------------
class stock_move_lot ( osv . osv ) :
_name = " stock.move.lot "
_description = " Move Lot "
_columns = {
' name ' : fields . char ( ' Move Description ' , size = 64 , required = True ) ,
' active ' : fields . boolean ( ' Active ' ) ,
' state ' : fields . selection ( ( ( ' draft ' , ' Draft ' ) , ( ' done ' , ' Moved ' ) ) , ' State ' , readonly = True ) ,
' serial ' : fields . char ( ' Tracking Number ' , size = 32 ) ,
' date_planned ' : fields . date ( ' Planned Date ' ) ,
' date_moved ' : fields . date ( ' Actual Date ' ) ,
' lot_id ' : fields . many2one ( ' stock.lot ' , ' Lot ' , required = True ) ,
' loc_dest_id ' : fields . many2one ( ' stock.location ' , ' Destination Location ' , required = True ) ,
' address_id ' : fields . many2one ( ' res.partner.address ' , ' Destination Address ' ) ,
' origin ' : fields . char ( ' Origin ' , size = 64 ) ,
}
_defaults = {
' active ' : lambda * a : 1 ,
' state ' : lambda * a : ' draft ' ,
' date_planned ' : lambda * a : time . strftime ( ' % Y- % m- %d ' ) ,
}
#
# TODO: test if valid
# ERROR: does this function should call action_done instead of doing him self on
# stock.move
#
def action_move ( self , cr , uid , ids , context = { } ) :
for move in self . browse ( cr , uid , ids , context ) :
lot_remove = [ ]
for m in move . lot_id . move_ids :
new_id = self . pool . get ( ' stock.move ' ) . copy ( cr , uid , m . id , { ' location_id ' : m . location_dest_id . id , ' location_dest_id ' : move . loc_dest_id . id , ' date_moved ' : time . strftime ( ' % Y- % m- %d ' ) , ' picking_id ' : False , ' state ' : ' draft ' , ' prodlot_id ' : False , ' tracking_id ' : False , ' lot_id ' : False , ' move_history_ids ' : [ ] , ' move_history_ids2 ' : [ ] } )
self . pool . get ( ' stock.move ' ) . action_done ( cr , uid , [ new_id ] , context )
cr . execute ( ' insert into stock_move_history_ids (parent_id,child_id) values ( %d , %d ) ' , ( m . id , new_id ) )
lot_remove . append ( m . id )
self . pool . get ( ' stock.move ' ) . write ( cr , uid , lot_remove , { ' lot_id ' : False } )
self . write ( cr , uid , ids , { ' state ' : ' done ' , ' date_moved ' : time . strftime ( ' % Y- % m- %d ' ) } )
return True
stock_move_lot ( )
class stock_tracking ( osv . osv ) :
_name = " stock.tracking "
_description = " Stock Tracking Lots "
def checksum ( sscc ) :
salt = ' 31 ' * 8 + ' 3 '
sum = 0
for sscc_part , salt_part in zip ( sscc , salt ) :
sum + = int ( sscc_part ) * int ( salt_part )
return ( 10 - ( sum % 10 ) ) % 10
checksum = staticmethod ( checksum )
def make_sscc ( self , cr , uid , context = { } ) :
sequence = self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.lot.tracking ' )
return sequence + str ( self . checksum ( sequence ) )
_columns = {
' name ' : fields . char ( ' Tracking ' , size = 64 , required = True ) ,
' active ' : fields . boolean ( ' Active ' ) ,
' serial ' : fields . char ( ' Reference ' , size = 64 ) ,
' move_ids ' : fields . one2many ( ' stock.move ' , ' tracking_id ' , ' Moves tracked ' ) ,
' date ' : fields . datetime ( ' Date create ' , required = True ) ,
}
_defaults = {
' active ' : lambda * a : 1 ,
# 'name': lambda x,y,z,c: x.pool.get('ir.sequence').get(y,z,'stock.lot.tracking'),
' name ' : make_sscc ,
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
}
#_sql = 'ALTER TABLE stock_tracking ADD CONSTRAINT stock_lot_tracking_unique UNIQUE (name,serial)'
def name_search ( self , cr , user , name , args = [ ] , operator = ' ilike ' , context = { } ) :
ids = self . search ( cr , user , [ ( ' serial ' , ' = ' , name ) ] + args )
ids + = self . search ( cr , user , [ ( ' name ' , operator , name ) ] + args )
return self . name_get ( cr , user , ids )
def name_get ( self , cr , uid , ids , context = { } ) :
if not len ( ids ) :
return [ ]
res = [ ( r [ ' id ' ] , r [ ' name ' ] + ' [ ' + ( r [ ' serial ' ] or ' ' ) + ' ] ' ) for r in self . read ( cr , uid , ids , [ ' name ' , ' serial ' ] , context ) ]
return res
def unlink ( self , cr , uid , ids ) :
raise ' You can not remove a lot line ! '
stock_tracking ( )
#----------------------------------------------------------
# Stock Picking
#----------------------------------------------------------
class stock_picking ( osv . osv ) :
_name = " stock.picking "
_description = " Picking list "
_columns = {
' name ' : fields . char ( ' Picking Name ' , size = 64 , required = True , select = True ) ,
' origin ' : fields . char ( ' Origin ' , size = 64 ) ,
' type ' : fields . selection ( [ ( ' out ' , ' Sending Goods ' ) , ( ' in ' , ' Getting Goods ' ) , ( ' internal ' , ' Internal ' ) ] , ' Shipping Type ' , required = True , select = True ) ,
' active ' : fields . boolean ( ' Active ' ) ,
' note ' : fields . text ( ' Notes ' ) ,
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' ) ,
' location_dest_id ' : fields . many2one ( ' stock.location ' , ' Dest. Location ' ) ,
' move_type ' : fields . selection ( [ ( ' direct ' , ' Direct Delivery ' ) , ( ' one ' , ' All at once ' ) ] , ' Delivery Method ' , required = True ) ,
' state ' : fields . selection ( [
( ' draft ' , ' draft ' ) ,
( ' auto ' , ' waiting ' ) ,
( ' confirmed ' , ' confirmed ' ) ,
( ' assigned ' , ' assigned ' ) ,
( ' done ' , ' done ' ) ,
( ' cancel ' , ' cancel ' ) ,
] , ' State ' , readonly = True ) ,
' date ' : fields . datetime ( ' Date create ' ) ,
' move_lines ' : fields . one2many ( ' stock.move ' , ' picking_id ' , ' Move Lines ' ) ,
' auto_picking ' : fields . boolean ( ' Auto-Picking ' ) ,
' work ' : fields . boolean ( ' Work todo ' ) ,
' loc_move_id ' : fields . many2one ( ' stock.location ' , ' Move to Location ' ) ,
' address_id ' : fields . many2one ( ' res.partner.address ' , ' Partner Address ' ) ,
' lot_id ' : fields . many2one ( ' stock.lot ' , ' Consumer Lot Created ' ) ,
' move_lot_id ' : fields . many2one ( ' stock.move.lot ' , ' Moves Created ' ) ,
' invoice_state ' : fields . selection ( [
( " invoiced " , " Invoiced " ) ,
( " 2binvoiced " , " To be invoiced " ) ,
( " none " , " No invoice " ) ] , " Invoice state " ) ,
}
_defaults = {
' name ' : lambda * a : ' Unconfirmed picking ' ,
' work ' : lambda * a : 0 ,
' active ' : lambda * a : 1 ,
' state ' : lambda * a : ' draft ' ,
' move_type ' : lambda * a : ' direct ' ,
' type ' : lambda * a : ' in ' ,
' invoice_state ' : lambda * a : ' none ' ,
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
}
def action_confirm ( self , cr , uid , ids , * args ) :
2006-12-18 12:25:45 +00:00
print ' Confirmed ' , ids
2006-12-07 13:41:40 +00:00
self . write ( cr , uid , ids , { ' state ' : ' confirmed ' } )
todo = [ ]
for picking in self . browse ( cr , uid , ids ) :
number = self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' stock.picking. %s ' % picking . type )
self . write ( cr , uid , [ picking . id ] , { ' name ' : number } )
for r in picking . move_lines :
if r . state == ' draft ' :
todo . append ( r . id )
if len ( todo ) :
self . pool . get ( ' stock.move ' ) . action_confirm ( cr , uid , todo )
return True
def test_auto_picking ( self , cr , uid , ids ) :
# TODO: Check locations to see if in the same location ?
return True
def action_assign ( self , cr , uid , ids , * args ) :
for pick in self . browse ( cr , uid , ids ) :
move_ids = [ x . id for x in pick . move_lines if x . state == ' confirmed ' ]
self . pool . get ( ' stock.move ' ) . action_assign ( cr , uid , move_ids )
return True
def force_assign ( self , cr , uid , ids , * args ) :
wf_service = netsvc . LocalService ( " workflow " )
for pick in self . browse ( cr , uid , ids ) :
# move_ids = [x.id for x in pick.move_lines if x.state == 'confirmed']
move_ids = [ x . id for x in pick . move_lines ]
self . pool . get ( ' stock.move ' ) . force_assign ( cr , uid , move_ids )
wf_service . trg_write ( uid , ' stock.picking ' , pick . id , cr )
return True
2006-12-18 14:10:01 +00:00
def cancel_assign ( self , cr , uid , ids , * args ) :
wf_service = netsvc . LocalService ( " workflow " )
for pick in self . browse ( cr , uid , ids ) :
move_ids = [ x . id for x in pick . move_lines ]
self . pool . get ( ' stock.move ' ) . cancel_assign ( cr , uid , move_ids )
wf_service . trg_write ( uid , ' stock.picking ' , pick . id , cr )
return True
2006-12-07 13:41:40 +00:00
def action_assign_wkf ( self , cr , uid , ids ) :
self . write ( cr , uid , ids , { ' state ' : ' assigned ' } )
return True
def test_finnished ( self , cr , uid , ids ) :
for id in ids :
cr . execute ( ' select id,state from stock_move where picking_id= %d ' , ( id , ) )
for x in cr . fetchall ( ) :
if x [ 1 ] not in ( ' done ' , ' cancel ' ) :
return False
return True
def test_assigned ( self , cr , uid , ids ) :
ok = True
for pick in self . browse ( cr , uid , ids ) :
mt = pick . move_type
for move in pick . move_lines :
if ( move . state in ( ' confirmed ' , ' draft ' ) ) and ( mt == ' one ' ) :
return False
2006-12-18 06:33:52 +00:00
if ( mt == ' direct ' ) and ( move . state == ' assigned ' ) and ( move . product_qty ) :
2006-12-07 13:41:40 +00:00
return True
ok = ok and ( move . state in ( ' cancel ' , ' done ' , ' assigned ' ) )
return ok
def action_cancel ( self , cr , uid , ids , context = { } ) :
for pick in self . browse ( cr , uid , ids ) :
ids2 = [ move . id for move in pick . move_lines ]
self . pool . get ( ' stock.move ' ) . action_cancel ( cr , uid , ids2 , context )
self . write ( cr , uid , ids , { ' state ' : ' cancel ' , ' invoice_state ' : ' none ' } )
return True
#
# TODO: change and create a move if not parents
#
def action_done ( self , cr , uid , ids , * args ) :
for pick in self . browse ( cr , uid , ids ) :
if pick . move_type == ' one ' and pick . loc_move_id :
if pick . lot_id :
id = self . pool . get ( ' stock.move.lot ' ) . create ( cr , uid , {
' name ' : ' MOVE: ' + pick . name ,
' origin ' : ' PICK: ' + str ( pick . id ) ,
' lot_id ' : pick . lot_id . id ,
' loc_dest_id ' : pick . loc_move_id . id ,
' address_id ' : pick . address_id . id
} )
self . write ( cr , uid , [ pick . id ] , { ' move_lot_id ' : id } )
self . write ( cr , uid , ids , { ' state ' : ' done ' } )
return True
def action_move ( self , cr , uid , ids , context = { } ) :
for pick in self . browse ( cr , uid , ids ) :
todo = [ ]
for move in pick . move_lines :
if move . state == ' assigned ' :
todo . append ( move . id )
if len ( todo ) :
self . pool . get ( ' stock.move ' ) . action_done ( cr , uid , todo )
lot_id = self . pool . get ( ' stock.lot ' ) . create ( cr , uid , { ' name ' : ' PICK: ' + pick . name } )
self . pool . get ( ' stock.move ' ) . write ( cr , uid , todo , { ' lot_id ' : lot_id } )
self . write ( cr , uid , [ pick . id ] , { ' lot_id ' : lot_id } )
if pick . move_type == ' direct ' and pick . loc_move_id :
id = self . pool . get ( ' stock.move.lot ' ) . create ( cr , uid , {
' name ' : ' MOVE: ' + pick . name ,
' origin ' : ' PICK: ' + str ( pick . id ) ,
' lot_id ' : lot_id ,
' loc_dest_id ' : pick . loc_move_id . id ,
' address_id ' : pick . address_id . id
} )
self . write ( cr , uid , [ pick . id ] , { ' move_lot_id ' : id } )
return True
def action_invoice_create ( self , cr , uid , ids , journal_id = False , group = False , type = ' out_invoice ' , context = { } ) :
res = { }
pgroup = { }
get_ids = lambda y : map ( lambda x : x . id , y or [ ] )
sales = { }
for p in self . browse ( cr , uid , ids , context ) :
if p . invoice_state < > ' 2binvoiced ' :
continue
a = p . address_id . partner_id . property_account_receivable [ 0 ]
if p . address_id . partner_id and p . address_id . partner_id . property_payment_term :
pay_term = p . address_id . partner_id . property_payment_term [ 0 ]
else :
pay_term = False
if p . sale_id :
pinv_id = p . sale_id . partner_invoice_id . id
pcon_id = p . sale_id . partner_order_id . id
if p . sale_id . id not in sales :
sales [ p . sale_id . id ] = [ x . id for x in p . sale_id . invoice_ids ]
else :
#
# ideal: get_address('invoice') on partner
#
pinv_id = p . address_id . id
pcon_id = p . address_id . id
val = {
' name ' : p . name ,
' origin ' : p . name + ' : ' + p . origin ,
' type ' : type ,
' reference ' : " P %d SO %d " % ( p . address_id . partner_id . id , p . id ) ,
' account_id ' : a ,
' partner_id ' : p . address_id . partner_id . id ,
' address_invoice_id ' : pinv_id ,
' address_contact_id ' : pcon_id ,
' project_id ' : ( p . sale_id and p . sale_id . project_id . id ) or False ,
' comment ' : ( p . note or ' ' ) + ' \n ' + ( p . sale_id and p . sale_id . note or ' ' ) ,
' payment_term ' : pay_term ,
}
if p . sale_id :
val [ ' currency_id ' ] = ( p . sale_id and p . sale_id . pricelist_id . currency_id . id ) or False
if journal_id :
val [ ' journal_id ' ] = journal_id
if group and p . address_id . partner_id . id in pgroup :
invoice_id = pgroup [ p . address_id . partner_id . id ]
else :
invoice_id = self . pool . get ( ' account.invoice ' ) . create ( cr , uid , val , context = context )
pgroup [ p . address_id . partner_id . id ] = invoice_id
res [ p . id ] = invoice_id
for line in p . move_lines :
tax_ids = map ( lambda x : x . id , line . product_id . taxes_id )
a = line . product_id . product_tmpl_id . property_account_income
if not a :
a = line . product_id . categ_id . property_account_income_categ
account_id = a [ 0 ]
punit = line . sale_line_id and line . sale_line_id . price_unit or line . product_id . list_price
if type in ( ' in_invoice ' , ' in_refund ' ) :
punit = line . product_id . standard_price
iline_id = self . pool . get ( ' account.invoice.line ' ) . create ( cr , uid , {
' name ' : p . name + ' - ' + line . name ,
' invoice_id ' : invoice_id ,
' uos_id ' : line . product_uos . id ,
' product_id ' : line . product_id . id ,
' account_id ' : account_id ,
' price_unit ' : line . sale_line_id and line . sale_line_id . price_unit or line . product_id . list_price ,
2006-12-18 06:33:52 +00:00
' discount ' : line . sale_line_id and line . sale_line_id . discount or 0.0 ,
2006-12-07 13:41:40 +00:00
' quantity ' : line . product_uos_qty ,
' invoice_line_tax_id ' : [ ( 6 , 0 , tax_ids ) ]
} )
if line . sale_line_id :
self . pool . get ( ' sale.order.line ' ) . write ( cr , uid , [ line . sale_line_id . id ] , {
' invoice_line_ids ' : [ ( 6 , 0 , [ iline_id ] ) ]
} )
self . pool . get ( ' stock.picking ' ) . write ( cr , uid , [ p . id ] , { ' invoice_state ' : ' invoiced ' } )
if p . sale_id :
sids = sales [ p . sale_id . id ]
if invoice_id not in sids :
sales [ p . sale_id . id ] . append ( invoice_id )
self . pool . get ( ' sale.order ' ) . write ( cr , uid , [ p . sale_id . id ] , {
' invoice_ids ' : [ ( 6 , 0 , sales [ p . sale_id . id ] ) ]
} )
self . pool . get ( ' sale.order ' ) . write ( cr , uid , [ p . sale_id . id ] , {
' invoiced ' : True
} )
return res
stock_picking ( )
class stock_production_lot ( osv . osv ) :
def name_get ( self , cr , uid , ids , context = { } ) :
if not ids :
return [ ]
reads = self . read ( cr , uid , ids , [ ' name ' , ' ref ' ] , context )
res = [ ]
for record in reads :
name = record [ ' name ' ]
if record [ ' ref ' ] :
name = name + ' / ' + record [ ' ref ' ]
res . append ( ( record [ ' id ' ] , name ) )
return res
_name = ' stock.production.lot '
_description = ' Production lot '
_columns = {
' name ' : fields . char ( ' Code ' , size = 64 , required = True ) ,
' ref ' : fields . char ( ' Reference ' , size = 64 ) ,
' date ' : fields . datetime ( ' Date create ' , required = True ) ,
' revisions ' : fields . one2many ( ' stock.production.lot.revision ' , ' lot_id ' , ' Revisions ' ) ,
}
_defaults = {
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' name ' : lambda x , y , z , c : x . pool . get ( ' ir.sequence ' ) . get ( y , z , ' stock.lot.serial ' ) ,
}
stock_production_lot ( )
class stock_production_lot_revision ( osv . osv ) :
_name = ' stock.production.lot.revision '
_description = ' Production lot revisions '
_columns = {
' name ' : fields . char ( ' Revision name ' , size = 64 , required = True ) ,
' description ' : fields . text ( ' Description ' ) ,
' date ' : fields . date ( ' Revision date ' ) ,
' indice ' : fields . char ( ' Revision ' , size = 16 ) ,
' author_id ' : fields . many2one ( ' res.users ' , ' Author ' ) ,
' lot_id ' : fields . many2one ( ' stock.production.lot ' , ' Production lot ' , select = True ) ,
}
_defaults = {
' author_id ' : lambda x , y , z , c : z ,
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d ' ) ,
}
stock_production_lot_revision ( )
# ----------------------------------------------------
# Move
# ----------------------------------------------------
#
# Fields:
# location_dest_id is only used for predicting futur stocks
#
class stock_move ( osv . osv ) :
def _getSSCC ( self , cr , uid , context = { } ) :
cr . execute ( ' select id from stock_tracking where create_uid= %d order by id desc limit 1 ' , ( uid , ) )
res = cr . fetchone ( )
return ( res and res [ 0 ] ) or False
_name = " stock.move "
_description = " Stock Move "
_columns = {
' name ' : fields . char ( ' Name ' , size = 64 , required = True , select = True ) ,
' priority ' : fields . selection ( [ ( ' 0 ' , ' Not urgent ' ) , ( ' 1 ' , ' Urgent ' ) ] , ' Priority ' ) ,
' date ' : fields . datetime ( ' Date Created ' ) ,
' date_planned ' : fields . date ( ' Planned date ' , required = True ) ,
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True ) ,
' product_qty ' : fields . float ( ' Quantity (UOM) ' , required = True ) ,
' product_uom ' : fields . many2one ( ' product.uom ' , ' Product UOM ' , required = True ) ,
' product_uos_qty ' : fields . float ( ' Quantity (UOS) ' ) ,
' product_uos ' : fields . many2one ( ' product.uom ' , ' Product UOS ' ) ,
' product_packaging ' : fields . many2one ( ' product.packaging ' , ' Product packaging ' ) ,
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True ) ,
' location_dest_id ' : fields . many2one ( ' stock.location ' , ' Dest. Location ' , required = True ) ,
' address_id ' : fields . many2one ( ' res.partner.address ' , ' Dest. Address ' ) ,
' prodlot_id ' : fields . many2one ( ' stock.production.lot ' , ' Production lot ' ) ,
' tracking_id ' : fields . many2one ( ' stock.tracking ' , ' Lot Tracking ' , select = True ) ,
' lot_id ' : fields . many2one ( ' stock.lot ' , ' Lot ' , select = True ) ,
' move_dest_id ' : fields . many2one ( ' stock.move ' , ' Dest. Move ' ) ,
' move_history_ids ' : fields . many2many ( ' stock.move ' , ' stock_move_history_ids ' , ' parent_id ' , ' child_id ' , ' Move History ' ) ,
' move_history_ids2 ' : fields . many2many ( ' stock.move ' , ' stock_move_history_ids ' , ' child_id ' , ' parent_id ' , ' Move History ' ) ,
' picking_id ' : fields . many2one ( ' stock.picking ' , ' Picking list ' , select = True ) ,
' note ' : fields . text ( ' Notes ' ) ,
' state ' : fields . selection ( [ ( ' draft ' , ' Draft ' ) , ( ' waiting ' , ' Waiting ' ) , ( ' confirmed ' , ' Confirmed ' ) , ( ' assigned ' , ' Assigned ' ) , ( ' done ' , ' Done ' ) , ( ' cancel ' , ' cancel ' ) ] , ' State ' , readonly = True , select = True ) ,
}
_defaults = {
' state ' : lambda * a : ' draft ' ,
' priority ' : lambda * a : ' 1 ' ,
' product_qty ' : lambda * a : 1.0 ,
' date_planned ' : lambda * a : time . strftime ( ' % Y- % m- %d ' ) ,
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
}
def onchange_product_id ( self , cr , uid , context , prod_id = False , loc_id = False , loc_dest_id = False ) :
if not prod_id :
return { }
product = self . pool . get ( ' product.product ' ) . browse ( cr , uid , [ prod_id ] ) [ 0 ]
result = {
' name ' : product . name ,
' product_uom ' : product . uom_id . id ,
}
if loc_id :
result [ ' location_id ' ] = loc_id
if loc_dest_id :
result [ ' location_dest_id ' ] = loc_dest_id
return { ' value ' : result }
def action_confirm ( self , cr , uid , ids , context = { } ) :
2006-12-18 12:25:45 +00:00
print ' Confirmed state ' , ids
2006-12-07 13:41:40 +00:00
self . write ( cr , uid , ids , { ' state ' : ' confirmed ' } )
return True
def action_assign ( self , cr , uid , ids , * args ) :
todo = [ ]
for move in self . browse ( cr , uid , ids ) :
if move . state in ( ' confirmed ' , ' waiting ' ) :
todo . append ( move . id )
res = self . check_assign ( cr , uid , todo )
return res
def force_assign ( self , cr , uid , ids , context = { } ) :
self . write ( cr , uid , ids , { ' state ' : ' assigned ' } )
return True
2006-12-18 14:10:01 +00:00
def cancel_assign ( self , cr , uid , ids , context = { } ) :
self . write ( cr , uid , ids , { ' state ' : ' confirmed ' } )
return True
2006-12-07 13:41:40 +00:00
#
# Duplicate stock.move
#
def check_assign ( self , cr , uid , ids , context = { } ) :
done = [ ]
2006-12-13 08:53:39 +00:00
count = 0
2006-12-07 13:41:40 +00:00
pickings = { }
for move in self . browse ( cr , uid , ids ) :
if move . product_id . type == ' consu ' :
done . append ( move . id )
continue
if move . state in ( ' confirmed ' , ' waiting ' ) :
res = self . pool . get ( ' stock.location ' ) . _product_reserve ( cr , uid , [ move . location_id . id ] , move . product_id . id , move . product_qty , { ' uom ' : move . product_uom . id } )
if res :
done . append ( move . id )
pickings [ move . picking_id . id ] = 1
r = res . pop ( 0 )
cr . execute ( ' update stock_move set location_id= %d , product_qty= %f where id= %d ' , ( r [ 1 ] , r [ 0 ] , move . id ) )
while res :
r = res . pop ( 0 )
move_id = self . copy ( cr , uid , move . id , { ' product_qty ' : r [ 0 ] , ' location_id ' : r [ 1 ] } )
done . append ( move_id )
#cr.execute('insert into stock_move_history_ids values (%d,%d)', (move.id,move_id))
2006-12-13 08:53:39 +00:00
if done :
count + = len ( done )
self . write ( cr , uid , done , { ' state ' : ' assigned ' } )
done = [ ]
2006-12-07 13:41:40 +00:00
2006-12-13 08:53:39 +00:00
if count :
2006-12-07 13:41:40 +00:00
for pick_id in pickings :
wf_service = netsvc . LocalService ( " workflow " )
wf_service . trg_write ( uid , ' stock.picking ' , pick_id , cr )
2006-12-13 08:53:39 +00:00
return count
2006-12-07 13:41:40 +00:00
#
# Cancel move => cancel others move and pickings
#
def action_cancel ( self , cr , uid , ids , context = { } ) :
if not len ( ids ) :
return True
pickings = { }
for move in self . browse ( cr , uid , ids ) :
if move . state in ( ' confirmed ' , ' waiting ' , ' assigned ' , ' draft ' ) :
if move . picking_id :
pickings [ move . picking_id . id ] = True
self . write ( cr , uid , ids , { ' state ' : ' cancel ' } )
ids_lst = ' , ' . join ( map ( str , ids ) )
for pick_id in pickings :
wf_service = netsvc . LocalService ( " workflow " )
wf_service . trg_validate ( uid , ' stock.picking ' , pick_id , ' button_cancel ' , cr )
ids2 = [ ]
for res in self . read ( cr , uid , ids , [ ' move_dest_id ' ] ) :
if res [ ' move_dest_id ' ] :
ids2 . append ( res [ ' move_dest_id ' ] [ 0 ] )
wf_service = netsvc . LocalService ( " workflow " )
for id in ids :
wf_service . trg_trigger ( uid , ' stock.move ' , id , cr )
self . action_cancel ( cr , uid , ids2 , context )
return True
def action_done ( self , cr , uid , ids , * args ) :
for move in self . browse ( cr , uid , ids ) :
if move . move_dest_id . id and ( move . state != ' done ' ) :
mid = move . move_dest_id . id
if move . move_dest_id . id :
cr . execute ( ' insert into stock_move_history_ids (parent_id,child_id) values ( %d , %d ) ' , ( move . id , move . move_dest_id . id ) )
if move . move_dest_id . state in ( ' waiting ' , ' confirmed ' ) :
self . write ( cr , uid , [ move . move_dest_id . id ] , { ' state ' : ' assigned ' } )
if move . move_dest_id . picking_id :
wf_service = netsvc . LocalService ( " workflow " )
wf_service . trg_write ( uid , ' stock.picking ' , move . move_dest_id . picking_id . id , cr )
else :
pass
# self.action_done(cr, uid, [move.move_dest_id.id])
#
# Accounting Entries
#
# acc_src = None
# acc_dest = None
# if move.location_id.account_id:
# acc_src = move.location_id.account_id.id
# if move.location_dest_id.account_id:
# acc_dest = move.location_dest_id.account_id.id
# if acc_src or acc_dest:
# test = [('product.product', move.product_id.id)]
# if move.product_id.categ_id:
# test.append( ('product.category', move.product_id.categ_id.id) )
# if not acc_src:
# acc_src = move.product_id.product_tmpl_id.property_account_expense[0]
# if not acc_dest:
# acc_dest = move.product_id.product_tmpl_id.property_account_income[0]
# if acc_src != acc_dest:
# amount = move.product_qty * move.product_id.standard_price
# lines = [
# (0,0,{'name': 'Stock', 'amount': -amount, 'account_id': acc_src}),
# (0,0,{'name': 'Stock', 'amount': amount, 'account_id': acc_dest})
# ]
# self.pool.get('account.move').create(cr, uid, {'name': 'Stock', 'type':'undefined', 'line_id': lines})
self . write ( cr , uid , ids , { ' state ' : ' done ' } )
wf_service = netsvc . LocalService ( " workflow " )
for id in ids :
wf_service . trg_trigger ( uid , ' stock.move ' , id , cr )
return True
stock_move ( )
class stock_inventory ( osv . osv ) :
_name = " stock.inventory "
_description = " Inventory "
_columns = {
' name ' : fields . char ( ' Inventory ' , size = 64 , required = True ) ,
' date ' : fields . datetime ( ' Date create ' , required = True ) ,
' date_done ' : fields . datetime ( ' Date done ' ) ,
' inventory_line_id ' : fields . one2many ( ' stock.inventory.line ' , ' inventory_id ' , ' Inventories ' ) ,
' move_ids ' : fields . many2many ( ' stock.move ' , ' stock_inventory_move_rel ' , ' inventory_id ' , ' move_id ' , ' Created Moves ' ) ,
' state ' : fields . selection ( ( ( ' draft ' , ' Draft ' ) , ( ' done ' , ' Done ' ) ) , ' State ' , readonly = True ) ,
}
_defaults = {
' date ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' state ' : lambda * a : ' draft ' ,
}
#
# Update to support tracking
#
def action_done ( self , cr , uid , ids , * args ) :
for inv in self . browse ( cr , uid , ids ) :
move_ids = [ ]
move_line = [ ]
for line in inv . inventory_line_id :
pid = line . product_id . id
price = line . product_id . standard_price or 0.0
amount = self . pool . get ( ' stock.location ' ) . _product_get ( cr , uid , line . location_id . id , [ pid ] , { ' uom ' : line . product_uom . id } ) [ pid ]
change = line . product_qty - amount
if change :
location_id = line . product_id . product_tmpl_id . property_stock_inventory [ 0 ]
value = {
' name ' : ' INV: ' + str ( line . inventory_id . id ) + ' : ' + line . inventory_id . name ,
' product_id ' : line . product_id . id ,
' product_uom ' : line . product_uom . id ,
' date ' : inv . date ,
' date_planned ' : inv . date ,
' state ' : ' assigned '
}
if change > 0 :
value . update ( {
' product_qty ' : change ,
' location_id ' : location_id ,
' location_dest_id ' : line . location_id . id ,
} )
else :
value . update ( {
' product_qty ' : - change ,
' location_id ' : line . location_id . id ,
' location_dest_id ' : location_id ,
} )
move_ids . append ( self . pool . get ( ' stock.move ' ) . create ( cr , uid , value ) )
if len ( move_ids ) :
self . pool . get ( ' stock.move ' ) . action_done ( cr , uid , move_ids )
self . write ( cr , uid , [ inv . id ] , { ' state ' : ' done ' , ' date_done ' : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) , ' move_ids ' : [ ( 6 , 0 , move_ids ) ] } )
return True
def action_cancel ( self , cr , uid , ids , context = { } ) :
for inv in self . browse ( cr , uid , ids ) :
self . pool . get ( ' stock.move ' ) . action_cancel ( cr , uid , [ x . id for x in inv . move_ids ] , context )
self . write ( cr , uid , [ inv . id ] , { ' state ' : ' draft ' } )
return True
stock_inventory ( )
class stock_inventory_line ( osv . osv ) :
_name = " stock.inventory.line "
_description = " Inventory line "
_columns = {
' inventory_id ' : fields . many2one ( ' stock.inventory ' , ' Inventory ' , ondelete = ' cascade ' , select = True ) ,
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True ) ,
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True ) ,
' product_uom ' : fields . many2one ( ' product.uom ' , ' Product UOM ' , required = True ) ,
' product_qty ' : fields . float ( ' Quantity ' )
}
def on_change_product_id ( self , cr , uid , ids , location_id , product , uom = False ) :
if not product :
return { }
if not uom :
prod = self . pool . get ( ' product.product ' ) . browse ( cr , uid , [ product ] , { ' uom ' : uom } ) [ 0 ]
uom = prod . uom_id . id
amount = self . pool . get ( ' stock.location ' ) . _product_get ( cr , uid , location_id , [ product ] , { ' uom ' : uom } ) [ product ]
result = { ' product_qty ' : amount , ' product_uom ' : uom }
return { ' value ' : result }
stock_inventory_line ( )
#----------------------------------------------------------
# Stock Warehouse
#----------------------------------------------------------
class stock_warehouse ( osv . osv ) :
_name = " stock.warehouse "
_description = " Warehouse "
_columns = {
' name ' : fields . char ( ' Name ' , size = 60 , required = True ) ,
# 'partner_id': fields.many2one('res.partner', 'Owner'),
' partner_address_id ' : fields . many2one ( ' res.partner.address ' , ' Owner Address ' ) ,
' lot_input_id ' : fields . many2one ( ' stock.location ' , ' Location Input ' , required = True ) ,
' lot_stock_id ' : fields . many2one ( ' stock.location ' , ' Location Stock ' , required = True ) ,
' lot_output_id ' : fields . many2one ( ' stock.location ' , ' Location Output ' , required = True ) ,
}
stock_warehouse ( )
# Product
class product_product ( osv . osv ) :
_inherit = " product.product "
#
# Utiliser browse pour limiter les queries !
#
def _get_product_available_func ( states , what ) :
def _product_available ( self , cr , uid , ids , name , arg , context = { } ) :
if context . get ( ' shop ' , False ) :
cr . execute ( ' select warehouse_id from sale_shop where id= %d ' , ( int ( context [ ' shop ' ] ) , ) )
res2 = cr . fetchone ( )
if res2 :
context [ ' warehouse ' ] = res2 [ 0 ]
if context . get ( ' warehouse ' , False ) :
cr . execute ( ' select lot_stock_id from stock_warehouse where id= %d ' , ( int ( context [ ' warehouse ' ] ) , ) )
res2 = cr . fetchone ( )
if res2 :
context [ ' location ' ] = res2 [ 0 ]
if context . get ( ' location ' , False ) :
location_ids = [ context [ ' location ' ] ]
else :
# get the list of ids of the stock location of all warehouses
cr . execute ( " select lot_stock_id from stock_warehouse " )
location_ids = [ id for ( id , ) in cr . fetchall ( ) ]
# build the list of ids of children of the location given by id
location_ids = self . pool . get ( ' stock.location ' ) . search ( cr , uid , [ ( ' location_id ' , ' child_of ' , location_ids ) ] )
res = self . pool . get ( ' stock.location ' ) . _product_get_multi_location ( cr , uid , location_ids , ids , context , states , what )
for id in ids :
res . setdefault ( id , 0.0 )
return res
return _product_available
_product_qty_available = _get_product_available_func ( ( ' done ' , ) , ( ' in ' , ' out ' ) )
_product_virtual_available = _get_product_available_func ( ( ' confirmed ' , ' waiting ' , ' assigned ' , ' done ' ) , ( ' in ' , ' out ' ) )
_product_outgoing_qty = _get_product_available_func ( ( ' confirmed ' , ' waiting ' , ' assigned ' ) , ( ' out ' , ) )
_product_incoming_qty = _get_product_available_func ( ( ' confirmed ' , ' waiting ' , ' assigned ' ) , ( ' in ' , ) )
_columns = {
' qty_available ' : fields . function ( _product_qty_available , method = True , type = ' float ' , string = ' Real Stock ' ) ,
' virtual_available ' : fields . function ( _product_virtual_available , method = True , type = ' float ' , string = ' Virtual Stock ' ) ,
' incoming_qty ' : fields . function ( _product_incoming_qty , method = True , type = ' float ' , string = ' Incoming ' ) ,
' outgoing_qty ' : fields . function ( _product_outgoing_qty , method = True , type = ' float ' , string = ' Outgoing ' ) ,
}
product_product ( )