2010-03-26 09:25:02 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
2011-09-26 00:36:12 +00:00
# Copyright (C) 2004-TODAY OpenERP SA (<http://openerp.com>).
2010-03-26 09:25:02 +00:00
#
# This program is free software: you can redistribute it and/or modify
# 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.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
2010-08-04 11:07:44 +00:00
2010-03-26 09:25:02 +00:00
import time
2012-03-20 12:46:12 +00:00
from lxml import etree
2011-09-26 00:36:12 +00:00
from osv import fields , osv
from tools . misc import DEFAULT_SERVER_DATETIME_FORMAT
2011-11-22 10:09:07 +00:00
import decimal_precision as dp
2011-12-29 13:57:12 +00:00
from tools . translate import _
2010-03-26 09:25:02 +00:00
2011-09-26 00:36:12 +00:00
class stock_partial_picking_line ( osv . TransientModel ) :
2011-11-10 06:16:39 +00:00
def _tracking ( self , cursor , user , ids , name , arg , context = None ) :
res = { }
for tracklot in self . browse ( cursor , user , ids , context = context ) :
tracking = False
if ( tracklot . move_id . picking_id . type == ' in ' and tracklot . product_id . track_incoming == True ) or \
( tracklot . move_id . picking_id . type == ' out ' and tracklot . product_id . track_outgoing == True ) :
tracking = True
res [ tracklot . id ] = tracking
return res
2011-09-26 00:36:12 +00:00
_name = " stock.partial.picking.line "
2011-07-19 09:28:39 +00:00
_rec_name = ' product_id '
_columns = {
2011-12-29 16:36:45 +00:00
' product_id ' : fields . many2one ( ' product.product ' , string = " Product " , required = True , ondelete = ' CASCADE ' ) ,
2011-11-22 10:09:07 +00:00
' quantity ' : fields . float ( " Quantity " , digits_compute = dp . get_precision ( ' Product UoM ' ) , required = True ) ,
2011-09-26 00:36:12 +00:00
' product_uom ' : fields . many2one ( ' product.uom ' , ' Unit of Measure ' , required = True , ondelete = ' CASCADE ' ) ,
' prodlot_id ' : fields . many2one ( ' stock.production.lot ' , ' Production Lot ' , ondelete = ' CASCADE ' ) ,
2011-12-12 11:48:11 +00:00
' location_id ' : fields . many2one ( ' stock.location ' , ' Location ' , required = True , ondelete = ' CASCADE ' , domain = [ ( ' usage ' , ' <> ' , ' view ' ) ] ) ,
' location_dest_id ' : fields . many2one ( ' stock.location ' , ' Dest. Location ' , required = True , ondelete = ' CASCADE ' , domain = [ ( ' usage ' , ' <> ' , ' view ' ) ] ) ,
2011-09-26 00:36:12 +00:00
' move_id ' : fields . many2one ( ' stock.move ' , " Move " , ondelete = ' CASCADE ' ) ,
' wizard_id ' : fields . many2one ( ' stock.partial.picking ' , string = " Wizard " , ondelete = ' CASCADE ' ) ,
' update_cost ' : fields . boolean ( ' Need cost update ' ) ,
2011-07-19 09:28:39 +00:00
' cost ' : fields . float ( " Cost " , help = " Unit Cost for this product line " ) ,
2011-09-26 00:36:12 +00:00
' currency ' : fields . many2one ( ' res.currency ' , string = " Currency " , help = " Currency in which Unit cost is expressed " , ondelete = ' CASCADE ' ) ,
2011-12-29 13:57:12 +00:00
' tracking ' : fields . function ( _tracking , string = ' Tracking ' , type = ' boolean ' ) ,
2011-07-19 09:28:39 +00:00
}
2010-03-26 09:25:02 +00:00
class stock_partial_picking ( osv . osv_memory ) :
_name = " stock.partial.picking "
2011-09-26 00:36:12 +00:00
_description = " Partial Picking Processing Wizard "
2011-12-29 13:57:12 +00:00
def _hide_tracking ( self , cursor , user , ids , name , arg , context = None ) :
res = { }
for wizard in self . browse ( cursor , user , ids , context = context ) :
res [ wizard . id ] = any ( [ not ( x . tracking ) for x in wizard . move_ids ] )
return res
2010-03-26 09:25:02 +00:00
_columns = {
2011-01-17 10:01:22 +00:00
' date ' : fields . datetime ( ' Date ' , required = True ) ,
2011-09-26 00:36:12 +00:00
' move_ids ' : fields . one2many ( ' stock.partial.picking.line ' , ' wizard_id ' , ' Product Moves ' ) ,
' picking_id ' : fields . many2one ( ' stock.picking ' , ' Picking ' , required = True , ondelete = ' CASCADE ' ) ,
2011-12-29 13:57:12 +00:00
' hide_tracking ' : fields . function ( _hide_tracking , string = ' Tracking ' , type = ' boolean ' , help = ' This field is for internal purpose. It is used to decide if the column prodlot has to be shown on the move_ids field or not ' ) ,
2010-03-26 09:25:02 +00:00
}
2012-03-15 09:01:55 +00:00
2012-03-20 09:38:14 +00:00
def fields_view_get ( self , cr , uid , view_id = None , view_type = ' form ' , context = None , toolbar = False , submenu = False ) :
2012-03-15 09:01:55 +00:00
if context is None :
context = { }
2012-03-20 09:38:14 +00:00
res = super ( stock_partial_picking , self ) . fields_view_get ( cr , uid , view_id = view_id , view_type = view_type , context = context , toolbar = toolbar , submenu = submenu )
2012-05-04 15:36:13 +00:00
type = context . get ( ' active_model ' , ' ' ) . split ( ' . ' ) [ - 1 ]
2012-03-20 09:38:14 +00:00
if type :
doc = etree . XML ( res [ ' arch ' ] )
2012-05-04 15:36:13 +00:00
for node in doc . xpath ( " //group/button[@name= ' do_partial ' ] " ) :
2012-03-20 09:38:14 +00:00
if type == ' in ' :
2012-03-23 05:44:03 +00:00
node . set ( ' string ' , _ ( ' _Receive ' ) )
2012-03-20 09:38:14 +00:00
elif type == ' out ' :
2012-03-23 05:44:03 +00:00
node . set ( ' string ' , _ ( ' _Deliver ' ) )
2012-05-04 15:36:13 +00:00
for node in doc . xpath ( " //separator[@name= ' product_separator ' ] " ) :
2012-03-20 09:38:14 +00:00
if type == ' in ' :
2012-03-23 05:44:03 +00:00
node . set ( ' string ' , _ ( ' Receive Products ' ) )
2012-03-20 09:38:14 +00:00
elif type == ' out ' :
2012-03-23 05:44:03 +00:00
node . set ( ' string ' , _ ( ' Deliver Products ' ) )
2012-03-20 09:38:14 +00:00
res [ ' arch ' ] = etree . tostring ( doc )
2012-03-15 09:01:55 +00:00
return res
2011-07-19 09:28:39 +00:00
2011-01-10 11:55:25 +00:00
def default_get ( self , cr , uid , fields , context = None ) :
2011-09-26 00:36:12 +00:00
if context is None : context = { }
2011-01-10 11:55:25 +00:00
res = super ( stock_partial_picking , self ) . default_get ( cr , uid , fields , context = context )
picking_ids = context . get ( ' active_ids ' , [ ] )
2011-09-26 00:36:12 +00:00
if not picking_ids or ( not context . get ( ' active_model ' ) == ' stock.picking ' ) \
or len ( picking_ids ) != 1 :
# Partial Picking Processing may only be done for one picking at a time
2011-01-10 11:55:25 +00:00
return res
2011-09-26 00:36:12 +00:00
picking_id , = picking_ids
if ' picking_id ' in fields :
res . update ( picking_id = picking_id )
if ' move_ids ' in fields :
picking = self . pool . get ( ' stock.picking ' ) . browse ( cr , uid , picking_id , context = context )
2011-10-20 10:21:26 +00:00
moves = [ self . _partial_move_for ( cr , uid , m ) for m in picking . move_lines if m . state not in ( ' done ' , ' cancel ' ) ]
2011-09-26 00:36:12 +00:00
res . update ( move_ids = moves )
2011-01-10 11:55:25 +00:00
if ' date ' in fields :
2011-09-26 00:36:12 +00:00
res . update ( date = time . strftime ( DEFAULT_SERVER_DATETIME_FORMAT ) )
2010-06-21 18:40:58 +00:00
return res
2011-01-17 15:08:32 +00:00
2011-09-26 00:36:12 +00:00
def _product_cost_for_average_update ( self , cr , uid , move ) :
""" Returns product cost and currency ID for the given move, suited for re-computing
the average product cost .
2011-12-29 13:57:12 +00:00
2011-09-26 00:36:12 +00:00
: return : map of the form : :
2011-01-07 13:02:46 +00:00
2011-09-26 00:36:12 +00:00
{ ' cost ' : 123.34 ,
' currency ' : 42 }
"""
# Currently, the cost on the product form is supposed to be expressed in the currency
# of the company owning the product. If not set, we fall back to the picking's company,
# which should work in simple cases.
return { ' cost ' : move . product_id . standard_price ,
' currency ' : move . product_id . company_id . currency_id . id \
or move . picking_id . company_id . currency_id . id \
or False }
2010-03-26 09:25:02 +00:00
2011-09-26 00:36:12 +00:00
def _partial_move_for ( self , cr , uid , move ) :
partial_move = {
' product_id ' : move . product_id . id ,
2012-03-28 11:31:07 +00:00
' quantity ' : move . state in ( ' assigned ' , ' draft ' , ' confirmed ' ) and move . product_qty or 0 ,
2011-09-26 00:36:12 +00:00
' product_uom ' : move . product_uom . id ,
' prodlot_id ' : move . prodlot_id . id ,
' move_id ' : move . id ,
' location_id ' : move . location_id . id ,
' location_dest_id ' : move . location_dest_id . id ,
2010-11-11 21:58:22 +00:00
}
2011-09-26 00:36:12 +00:00
if move . picking_id . type == ' in ' and move . product_id . cost_method == ' average ' :
partial_move . update ( update_cost = True , * * self . _product_cost_for_average_update ( cr , uid , move ) )
return partial_move
2010-11-11 21:58:22 +00:00
2010-11-22 10:37:53 +00:00
def do_partial ( self , cr , uid , ids , context = None ) :
2011-09-26 00:36:12 +00:00
assert len ( ids ) == 1 , ' Partial picking processing may only be done one at a time '
stock_picking = self . pool . get ( ' stock.picking ' )
stock_move = self . pool . get ( ' stock.move ' )
2011-11-10 06:16:39 +00:00
uom_obj = self . pool . get ( ' product.uom ' )
2010-11-22 10:37:53 +00:00
partial = self . browse ( cr , uid , ids [ 0 ] , context = context )
2011-09-26 00:36:12 +00:00
partial_data = {
2010-06-21 18:40:58 +00:00
' delivery_date ' : partial . date
2010-03-26 09:25:02 +00:00
}
2011-09-26 00:36:12 +00:00
picking_type = partial . picking_id . type
2011-12-29 13:57:12 +00:00
for wizard_line in partial . move_ids :
line_uom = wizard_line . product_uom
move_id = wizard_line . move_id . id
2011-11-10 06:16:39 +00:00
#Quantiny must be Positive
2011-12-29 13:57:12 +00:00
if wizard_line . quantity < 0 :
2011-11-10 06:16:39 +00:00
raise osv . except_osv ( _ ( ' Warning! ' ) , _ ( ' Please provide Proper Quantity ! ' ) )
2011-11-16 12:32:51 +00:00
2011-12-29 16:36:45 +00:00
#Compute the quantity for respective wizard_line in the line uom (this jsut do the rounding if necessary)
qty_in_line_uom = uom_obj . _compute_qty ( cr , uid , line_uom . id , wizard_line . quantity , line_uom . id )
2011-11-16 12:32:51 +00:00
2011-12-29 16:36:45 +00:00
if line_uom . factor and line_uom . factor < > 0 :
2011-12-29 13:57:12 +00:00
if qty_in_line_uom < > wizard_line . quantity :
raise osv . except_osv ( _ ( ' Warning ' ) , _ ( ' The uom rounding does not allow you to ship " %s %s " , only roundings of " %s %s " is accepted by the uom. ' ) % ( wizard_line . quantity , line_uom . name , line_uom . rounding , line_uom . name ) )
2011-12-29 16:36:45 +00:00
if move_id :
#Check rounding Quantity.ex.
#picking: 1kg, uom kg rounding = 0.01 (rounding to 10g),
#partial delivery: 253g
#=> result= refused, as the qty left on picking would be 0.747kg and only 0.75 is accepted by the uom.
initial_uom = wizard_line . move_id . product_uom
#Compute the quantity for respective wizard_line in the initial uom
qty_in_initial_uom = uom_obj . _compute_qty ( cr , uid , line_uom . id , wizard_line . quantity , initial_uom . id )
2011-12-29 13:57:12 +00:00
without_rounding_qty = ( wizard_line . quantity / line_uom . factor ) * initial_uom . factor
if qty_in_initial_uom < > without_rounding_qty :
raise osv . except_osv ( _ ( ' Warning ' ) , _ ( ' The rounding of the initial uom does not allow you to ship " %s %s " , as it would let a quantity of " %s %s " to ship and only roundings of " %s %s " is accepted by the uom. ' ) % ( wizard_line . quantity , line_uom . name , wizard_line . move_id . product_qty - without_rounding_qty , initial_uom . name , initial_uom . rounding , initial_uom . name ) )
2011-12-29 16:36:45 +00:00
else :
2011-09-26 00:36:12 +00:00
seq_obj_name = ' stock.picking. ' + picking_type
move_id = stock_move . create ( cr , uid , { ' name ' : self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , seq_obj_name ) ,
2011-12-29 13:57:12 +00:00
' product_id ' : wizard_line . product_id . id ,
' product_qty ' : wizard_line . quantity ,
' product_uom ' : wizard_line . product_uom . id ,
' prodlot_id ' : wizard_line . prodlot_id . id ,
' location_id ' : wizard_line . location_id . id ,
' location_dest_id ' : wizard_line . location_dest_id . id ,
2011-09-26 00:36:12 +00:00
' picking_id ' : partial . picking_id . id
} , context = context )
2011-11-09 16:23:50 +00:00
stock_move . action_confirm ( cr , uid , [ move_id ] , context )
2011-09-26 00:36:12 +00:00
partial_data [ ' move %s ' % ( move_id ) ] = {
2011-12-29 13:57:12 +00:00
' product_id ' : wizard_line . product_id . id ,
' product_qty ' : wizard_line . quantity ,
' product_uom ' : wizard_line . product_uom . id ,
' prodlot_id ' : wizard_line . prodlot_id . id ,
2011-09-26 00:36:12 +00:00
}
2011-12-29 13:57:12 +00:00
if ( picking_type == ' in ' ) and ( wizard_line . product_id . cost_method == ' average ' ) :
partial_data [ ' move %s ' % ( wizard_line . move_id . id ) ] . update ( product_price = wizard_line . cost ,
product_currency = wizard_line . currency . id )
2011-09-26 00:36:12 +00:00
stock_picking . do_partial ( cr , uid , [ partial . picking_id . id ] , partial_data , context = context )
2011-02-28 10:38:41 +00:00
return { ' type ' : ' ir.actions.act_window_close ' }
2010-06-21 18:40:58 +00:00
2011-11-09 15:23:27 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: