2010-03-11 13:05:02 +00:00
# -*- encoding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
# $Id$
#
# 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 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from osv import fields , osv
import pooler
from tools import config
import time
import netsvc
import math
import mx . DateTime
from mx . DateTime import RelativeDateTime , now , DateTime , localtime
from tools . translate import _
2010-03-15 08:58:28 +00:00
def rounding ( fl , round_value ) :
if not round_value :
return fl
return round ( fl / round_value ) * round_value
# Object creating periods quickly
# changed that stop_date is created with hour 23:59:00 when it was 00:00:00 stop date was excluded from period
class stock_period_createlines ( osv . osv_memory ) :
_name = " stock.period.createlines "
def _get_new_period_start ( self , cr , uid , context = { } ) :
2010-03-11 13:05:02 +00:00
cr . execute ( " select max(date_stop) from stock_period " )
2010-03-15 08:58:28 +00:00
result = cr . fetchone ( )
last_date = result and result [ 0 ] or False
if last_date :
period_start = mx . DateTime . strptime ( last_date , " % Y- % m- %d % H: % M: % S " ) + RelativeDateTime ( days = 1 )
period_start = period_start - RelativeDateTime ( hours = period_start . hour , minutes = period_start . minute , seconds = period_start . second )
else :
period_start = mx . DateTime . today ( )
return period_start . strftime ( ' % Y- % m- %d ' )
2010-03-11 13:05:02 +00:00
_columns = {
' name ' : fields . char ( ' Period Name ' , size = 64 ) ,
' date_start ' : fields . date ( ' Start Date ' , required = True ) ,
' date_stop ' : fields . date ( ' End Date ' , required = True ) ,
' period_ids ' : fields . one2many ( ' stock.period ' , ' planning_id ' , ' Periods ' ) ,
}
_defaults = {
2010-03-15 08:58:28 +00:00
' date_start ' : _get_new_period_start ,
2010-03-11 13:05:02 +00:00
}
def create_period_weekly ( self , cr , uid , ids , context = { } ) :
res = self . create_period ( cr , uid , ids , context , 6 , ' Weekly ' )
return {
' view_type ' : ' form ' ,
" view_mode " : ' tree ' ,
' res_model ' : ' stock.period ' ,
' type ' : ' ir.actions.act_window ' ,
}
def create_period_monthly ( self , cr , uid , ids , context = { } , interval = 1 ) :
for p in self . browse ( cr , uid , ids , context ) :
dt = p . date_start
ds = mx . DateTime . strptime ( p . date_start , ' % Y- % m- %d ' )
while ds . strftime ( ' % Y- % m- %d ' ) < p . date_stop :
2010-03-15 08:58:28 +00:00
de = ds + RelativeDateTime ( months = interval , minutes = - 1 )
2010-03-11 13:05:02 +00:00
self . pool . get ( ' stock.period ' ) . create ( cr , uid , {
' name ' : ds . strftime ( ' % Y/ % m ' ) ,
' date_start ' : ds . strftime ( ' % Y- % m- %d ' ) ,
2010-03-15 08:58:28 +00:00
' date_stop ' : de . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2010-03-11 13:05:02 +00:00
} )
ds = ds + RelativeDateTime ( months = interval )
return {
' view_type ' : ' form ' ,
" view_mode " : ' tree ' ,
' res_model ' : ' stock.period ' ,
' type ' : ' ir.actions.act_window ' ,
}
def create_period ( self , cr , uid , ids , context = { } , interval = 0 , name = ' Daily ' ) :
for p in self . browse ( cr , uid , ids , context ) :
dt = p . date_start
ds = mx . DateTime . strptime ( p . date_start , ' % Y- % m- %d ' )
while ds . strftime ( ' % Y- % m- %d ' ) < p . date_stop :
2010-03-15 08:58:28 +00:00
de = ds + RelativeDateTime ( days = interval , minutes = - 1 )
2010-03-11 13:05:02 +00:00
if name == ' Daily ' :
new_name = de . strftime ( ' % Y- % m- %d ' )
if name == " Weekly " :
new_name = de . strftime ( ' % Y, week % W ' )
self . pool . get ( ' stock.period ' ) . create ( cr , uid , {
' name ' : new_name ,
' date_start ' : ds . strftime ( ' % Y- % m- %d ' ) ,
2010-03-15 08:58:28 +00:00
' date_stop ' : de . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2010-03-11 13:05:02 +00:00
} )
ds = ds + RelativeDateTime ( days = interval ) + 1
return {
' view_type ' : ' form ' ,
" view_mode " : ' tree ' ,
' res_model ' : ' stock.period ' ,
' type ' : ' ir.actions.act_window ' ,
}
2010-03-15 08:58:28 +00:00
stock_period_createlines ( )
2010-03-11 13:05:02 +00:00
2010-03-15 08:58:28 +00:00
# Periods have no company_id field as they can be shared across similar companies.
# If somone thinks different it can be improved.
2010-03-11 13:05:02 +00:00
class stock_period ( osv . osv ) :
_name = " stock.period "
_columns = {
' name ' : fields . char ( ' Period Name ' , size = 64 ) ,
' date_start ' : fields . datetime ( ' Start Date ' , required = True ) ,
' date_stop ' : fields . datetime ( ' End Date ' , required = True ) ,
' state ' : fields . selection ( [ ( ' draft ' , ' Draft ' ) , ( ' open ' , ' Open ' ) , ( ' close ' , ' Close ' ) ] , ' State ' )
}
_defaults = {
' state ' : lambda * a : ' draft '
}
stock_period ( )
2010-03-15 08:58:28 +00:00
# Creates forecasts records for products from selected Product Category for selected 'Warehouse - Period'
# Object added by contributor in ver 1.1
class stock_sale_forecast_createlines ( osv . osv_memory ) :
_name = " stock.sale.forecast.createlines "
2010-03-11 13:05:02 +00:00
2010-03-15 08:58:28 +00:00
# FIXME Add some period sugestion like below
# def _get_latest_period(self,cr,uid,context={}):
# cr.execute("select max(date_stop) from stock_period")
# result=cr.fetchone()
# return result and result[0] or False
_columns = {
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True , select = 1 ) ,
' warehouse_id1 ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , required = True , \
help = ' Warehouse which forecasts will concern. ' \
' If during stock planning you will need sales forecast for all warehouses choose any warehouse now. ' ) ,
' period_id1 ' : fields . many2one ( ' stock.period ' , ' Period ' , required = True , help = ' Period which forecasts will concern. ' ) ,
' product_categ_id1 ' : fields . many2one ( ' product.category ' , ' Product Category ' , required = True , \
help = ' Product Category of products which created forecasts will concern. ' ) ,
' copy_forecast ' : fields . boolean ( ' Copy Last Forecast ' , help = " Copy quantities from last Stock and Sale Forecast. " ) ,
}
_defaults = {
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.sale.forecast.createlines ' , context = c ) ,
}
def create_forecast ( self , cr , uid , ids , context = { } ) :
product_obj = self . pool . get ( ' product.product ' )
forecast_obj = self . pool . get ( ' stock.sale.forecast ' )
2010-05-18 06:12:23 +00:00
mod_obj = self . pool . get ( ' ir.model.data ' )
2010-03-15 08:58:28 +00:00
for f in self . browse ( cr , uid , ids , context ) :
prod_categ_obj = self . pool . get ( ' product.category ' )
template_obj = self . pool . get ( ' product.template ' )
categ_ids = f . product_categ_id1 . id and [ f . product_categ_id1 . id ] or [ ]
prod_categ_ids = prod_categ_obj . search ( cr , uid , [ ( ' parent_id ' , ' child_of ' , categ_ids ) ] )
templates_ids = template_obj . search ( cr , uid , [ ( ' categ_id ' , ' in ' , prod_categ_ids ) ] )
products_ids = product_obj . search ( cr , uid , [ ( ' product_tmpl_id ' , ' in ' , templates_ids ) ] )
if len ( products_ids ) == 0 :
raise osv . except_osv ( _ ( ' Error ! ' ) , _ ( ' No products in selected category ! ' ) )
copy = f . copy_forecast
for p in product_obj . browse ( cr , uid , products_ids , { } ) :
if len ( forecast_obj . search ( cr , uid , [ ( ' product_id ' , ' = ' , p . id ) , \
( ' period_id ' , ' = ' , f . period_id1 . id ) , \
( ' user_id ' , ' = ' , uid ) , \
( ' warehouse_id ' , ' = ' , f . warehouse_id1 . id ) ] ) ) == 0 :
forecast_qty = 0.0
# Not sure if it is expected quantity for this feature (copying forecast from previous period)
# because it can take incidental forecast of this warehouse, this product and this user (creating, writing or validating forecast).
# It takes only one forecast line (no sum). If user creates only one forecast per period it will be OK. If not I have no idea how to count it.
prod_uom = False
if copy :
cr . execute ( " SELECT period.date_stop, forecast.product_qty, forecast.product_uom \
FROM stock_sale_forecast AS forecast \
LEFT JOIN stock_period AS period \
ON forecast . period_id = period . id \
WHERE ( forecast . user_id = % s OR forecast . create_uid = % s OR forecast . write_uid = % s ) \
AND forecast . warehouse_id = % s AND forecast . product_id = % s \
AND period . date_stop < % s \
ORDER BY period . date_stop DESC " ,
( uid , uid , uid , f . warehouse_id1 . id , p . id , f . period_id1 . date_stop ) )
ret = cr . fetchone ( )
if ret :
forecast_qty = ret [ 1 ]
prod_uom = ret [ 2 ]
prod_uom = prod_uom or p . uom_id . id
prod_uos_categ = False
if p . uos_id :
prod_uos_categ = p . uos_id . category_id . id
forecast_obj . create ( cr , uid , {
' company_id ' : f . warehouse_id1 . company_id . id ,
' period_id ' : f . period_id1 . id ,
' warehouse_id ' : f . warehouse_id1 . id ,
' product_id ' : p . id ,
' product_qty ' : forecast_qty ,
' product_amt ' : 0.0 ,
' product_uom ' : prod_uom ,
' active_uom ' : prod_uom ,
' product_uom_categ ' : p . uom_id . category_id . id ,
' product_uos_categ ' : prod_uos_categ ,
} )
2010-05-18 06:12:23 +00:00
result = mod_obj . _get_id ( cr , uid , ' stock_planning ' , ' view_stock_sale_forecast_filter ' )
id = mod_obj . read ( cr , uid , result , [ ' res_id ' ] , context = context )
2010-03-15 08:58:28 +00:00
return {
' view_type ' : ' form ' ,
" view_mode " : ' tree ' ,
' res_model ' : ' stock.sale.forecast ' ,
2010-05-18 06:12:23 +00:00
' type ' : ' ir.actions.act_window ' ,
' search_view_id ' : id [ ' res_id ' ] ,
2010-03-15 08:58:28 +00:00
}
stock_sale_forecast_createlines ( )
# Stock and Sales Forecast object. Previously stock_planning_sale_prevision.
# A lot of changes in 1.1
class stock_sale_forecast ( osv . osv ) :
_name = " stock.sale.forecast "
2010-03-11 13:05:02 +00:00
_columns = {
2010-03-15 08:58:28 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
' create_uid ' : fields . many2one ( ' res.users ' , ' Responsible ' ) ,
' name ' : fields . char ( ' Name ' , size = 64 , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
' user_id ' : fields . many2one ( ' res.users ' , ' Created/Validated by ' , readonly = True , \
help = ' Shows who created this forecast, or who validated. ' ) ,
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , \
help = ' Shows which warehouse this forecast concerns. ' \
' If during stock planning you will need sales forecast for all warehouses choose any warehouse now. ' ) ,
' period_id ' : fields . many2one ( ' stock.period ' , ' Period ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , \
help = ' Shows which period this forecast concerns. ' ) ,
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , readonly = True , required = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , \
help = ' Shows which product this forecast concerns. ' ) ,
' product_qty ' : fields . float ( ' Product Quantity ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , \
help = ' Forecasted quantity. ' ) ,
' product_amt ' : fields . float ( ' Product Amount ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , \
help = ' Forecast value which will be converted to Product Quantity according to prices. ' ) ,
' product_uom_categ ' : fields . many2one ( ' product.uom.categ ' , ' Product UoM Category ' ) , # Invisible field for product_uom domain
' product_uom ' : fields . many2one ( ' product.uom ' , ' Product UoM ' , required = True , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , \
help = " Unit of Measure used to show the quanities of stock calculation. " \
" You can use units form default category or from second category (UoS category). " ) ,
' product_uos_categ ' : fields . many2one ( ' product.uom.categ ' , ' Product UoS Category ' ) , # Invisible field for product_uos domain
# Field used in onchange_uom to check what uom was before change and recalculate quantities acording to old uom (active_uom) and new uom.
' active_uom ' : fields . many2one ( ' product.uom ' , string = " Active UoM " ) ,
2010-03-11 13:05:02 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' Draft ' ) , ( ' validated ' , ' Validated ' ) ] , ' State ' , readonly = True ) ,
2010-03-15 08:58:28 +00:00
' analyzed_period1_id ' : fields . many2one ( ' stock.period ' , ' Period1 ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , ) ,
' analyzed_period2_id ' : fields . many2one ( ' stock.period ' , ' Period2 ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , ) ,
' analyzed_period3_id ' : fields . many2one ( ' stock.period ' , ' Period3 ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , ) ,
' analyzed_period4_id ' : fields . many2one ( ' stock.period ' , ' Period4 ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , ) ,
' analyzed_period5_id ' : fields . many2one ( ' stock.period ' , ' Period5 ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , ) ,
' analyzed_user_id ' : fields . many2one ( ' res.users ' , ' This User ' , required = False , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , ) ,
' analyzed_dept_id ' : fields . many2one ( ' hr.department ' , ' This Department ' , required = False , \
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , ) ,
' analyzed_warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' This Warehouse ' , required = False , \
readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } ) ,
' analyze_company ' : fields . boolean ( ' Per Company ' , readonly = True , states = { ' draft ' : [ ( ' readonly ' , False ) ] } , \
help = " Check this box to see the sales for whole company. " ) ,
' analyzed_period1_per_user ' : fields . float ( ' This User Period1 ' , readonly = True ) ,
' analyzed_period2_per_user ' : fields . float ( ' This User Period2 ' , readonly = True ) ,
' analyzed_period3_per_user ' : fields . float ( ' This User Period3 ' , readonly = True ) ,
' analyzed_period4_per_user ' : fields . float ( ' This User Period4 ' , readonly = True ) ,
' analyzed_period5_per_user ' : fields . float ( ' This User Period5 ' , readonly = True ) ,
' analyzed_period1_per_dept ' : fields . float ( ' This Dept Period1 ' , readonly = True ) ,
' analyzed_period2_per_dept ' : fields . float ( ' This Dept Period2 ' , readonly = True ) ,
' analyzed_period3_per_dept ' : fields . float ( ' This Dept Period3 ' , readonly = True ) ,
' analyzed_period4_per_dept ' : fields . float ( ' This Dept Period4 ' , readonly = True ) ,
' analyzed_period5_per_dept ' : fields . float ( ' This Dept Period5 ' , readonly = True ) ,
' analyzed_period1_per_warehouse ' : fields . float ( ' This Warehouse Period1 ' , readonly = True ) ,
' analyzed_period2_per_warehouse ' : fields . float ( ' This Warehouse Period2 ' , readonly = True ) ,
' analyzed_period3_per_warehouse ' : fields . float ( ' This Warehouse Period3 ' , readonly = True ) ,
' analyzed_period4_per_warehouse ' : fields . float ( ' This Warehouse Period4 ' , readonly = True ) ,
' analyzed_period5_per_warehouse ' : fields . float ( ' This Warehouse Period5 ' , readonly = True ) ,
' analyzed_period1_per_company ' : fields . float ( ' This Copmany Period1 ' , readonly = True ) ,
' analyzed_period2_per_company ' : fields . float ( ' This Company Period2 ' , readonly = True ) ,
' analyzed_period3_per_company ' : fields . float ( ' This Company Period3 ' , readonly = True ) ,
' analyzed_period4_per_company ' : fields . float ( ' This Company Period4 ' , readonly = True ) ,
' analyzed_period5_per_company ' : fields . float ( ' This Company Period5 ' , readonly = True ) ,
2010-03-11 13:05:02 +00:00
}
_defaults = {
2010-03-15 08:58:28 +00:00
' user_id ' : lambda obj , cr , uid , context : uid ,
' state ' : lambda * args : ' draft ' ,
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.sale.forecast ' , context = c ) ,
2010-03-11 13:05:02 +00:00
}
2010-03-15 08:58:28 +00:00
2010-03-11 13:05:02 +00:00
def action_validate ( self , cr , uid , ids , * args ) :
2010-03-15 08:58:28 +00:00
self . write ( cr , uid , ids , { ' state ' : ' validated ' , ' user_id ' : uid } )
2010-03-11 13:05:02 +00:00
return True
def unlink ( self , cr , uid , ids , context = { } ) :
2010-03-15 08:58:28 +00:00
forecasts = self . read ( cr , uid , ids , [ ' state ' ] )
2010-03-11 13:05:02 +00:00
unlink_ids = [ ]
2010-03-15 08:58:28 +00:00
for t in forecasts :
2010-03-11 13:05:02 +00:00
if t [ ' state ' ] in ( ' draft ' ) :
unlink_ids . append ( t [ ' id ' ] )
else :
2010-03-15 08:58:28 +00:00
raise osv . except_osv ( _ ( ' Invalid action ! ' ) , _ ( ' Cannot delete Validated Sale Forecasts ! ' ) )
2010-03-11 13:05:02 +00:00
osv . osv . unlink ( self , cr , uid , unlink_ids , context = context )
return True
2010-03-15 08:58:28 +00:00
def onchange_company ( self , cr , uid , ids , company_id ) :
2010-03-11 13:05:02 +00:00
result = { }
2010-03-15 08:58:28 +00:00
if company_id :
result [ ' warehouse_id ' ] = False
result [ ' analyzed_user_id ' ] = False
result [ ' analyzed_dept_id ' ] = False
result [ ' analyzed_warehouse_id ' ] = False
2010-03-11 13:05:02 +00:00
return { ' value ' : result }
2010-03-15 08:58:28 +00:00
def product_id_change ( self , cr , uid , ids , product_id ) :
ret = { }
if product_id :
product_rec = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id )
ret [ ' product_uom ' ] = product_rec . uom_id . id
ret [ ' product_uom_categ ' ] = product_rec . uom_id . category_id . id
ret [ ' product_uos_categ ' ] = product_rec . uos_id and product_rec . uos_id . category_id . id or False
ret [ ' active_uom ' ] = product_rec . uom_id . id
else :
ret [ ' product_uom ' ] = False
ret [ ' product_uom_categ ' ] = False
ret [ ' product_uos_categ ' ] = False
res = { ' value ' : ret }
return res
def onchange_uom ( self , cr , uid , ids , product_uom = False , product_qty = 0.0 , active_uom = False ) :
ret = { }
val1 = self . browse ( cr , uid , ids )
val = val1 [ 0 ]
coeff_uom2def = self . _to_default_uom_factor ( cr , uid , val , active_uom , { } )
coeff_def2uom , round_value = self . _from_default_uom_factor ( cr , uid , val , product_uom , { } )
coeff = coeff_uom2def * coeff_def2uom
ret [ ' product_qty ' ] = rounding ( coeff * product_qty , round_value )
ret [ ' active_uom ' ] = product_uom
return { ' value ' : ret }
def product_amt_change ( self , cr , uid , ids , product_amt = 0.0 , product_uom = False ) :
ret = { }
if product_amt :
coeff_def2uom = 1
val1 = self . browse ( cr , uid , ids )
val = val1 [ 0 ]
if ( product_uom != val . product_id . uom_id . id ) :
coeff_def2uom , rounding = self . _from_default_uom_factor ( cr , uid , val , product_uom , { } )
ret [ ' product_qty ' ] = rounding ( coeff_def2uom * product_amt / ( val . product_id . product_tmpl_id . list_price ) , round_value )
res = { ' value ' : ret }
return res
def _to_default_uom_factor ( self , cr , uid , val , uom_id , context ) :
uom_obj = self . pool . get ( ' product.uom ' )
uom = uom_obj . browse ( cr , uid , uom_id , context = context )
coef = uom . factor
if uom . category_id . id < > val . product_id . uom_id . category_id . id :
coef = coef / val . product_id . uos_coeff
return val . product_id . uom_id . factor / coef
def _from_default_uom_factor ( self , cr , uid , val , uom_id , context ) :
uom_obj = self . pool . get ( ' product.uom ' )
uom = uom_obj . browse ( cr , uid , uom_id , context = context )
res = uom . factor
if uom . category_id . id < > val . product_id . uom_id . category_id . id :
res = res / val . product_id . uos_coeff
return res / val . product_id . uom_id . factor , uom . rounding
def _sales_per_users ( self , cr , uid , so , so_line , company , users ) :
cr . execute ( " SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
" WHERE (sol.id IN ( %s )) AND (s.state NOT IN ( \' draft \' , \' cancel \' )) AND (s.id IN ( %s )) AND (s.company_id= %s ) " \
" AND (s.user_id IN ( %s )) " % ( so_line , so , company , users ) )
ret = cr . fetchone ( ) [ 0 ] or 0.0
return ret
def _sales_per_warehouse ( self , cr , uid , so , so_line , company , shops ) :
cr . execute ( " SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
" WHERE (sol.id IN ( %s )) AND (s.state NOT IN ( \' draft \' , \' cancel \' )) AND (s.id IN ( %s ))AND (s.company_id= %s ) " \
" AND (s.shop_id IN ( %s )) " % ( so_line , so , company , shops ) )
ret = cr . fetchone ( ) [ 0 ] or 0.0
return ret
def _sales_per_company ( self , cr , uid , so , so_line , company , ) :
cr . execute ( " SELECT sum(sol.product_uom_qty) FROM sale_order_line AS sol LEFT JOIN sale_order AS s ON (s.id = sol.order_id) " \
" WHERE (sol.id IN ( %s )) AND (s.state NOT IN ( \' draft \' , \' cancel \' )) AND (s.id IN ( %s )) AND (s.company_id= %s ) " % ( so_line , so , company ) )
ret = cr . fetchone ( ) [ 0 ] or 0.0
return ret
2010-03-11 13:05:02 +00:00
2010-03-15 08:58:28 +00:00
def calculate_sales_history ( self , cr , uid , ids , context , * args ) :
sales = [ [ 0 , 0 , 0 , 0 ] , [ 0 , 0 , 0 , 0 ] , [ 0 , 0 , 0 , 0 ] , [ 0 , 0 , 0 , 0 ] , [ 0 , 0 , 0 , 0 ] , ]
for obj in self . browse ( cr , uid , ids ) :
periods = obj . analyzed_period1_id , obj . analyzed_period2_id , obj . analyzed_period3_id , obj . analyzed_period4_id , obj . analyzed_period5_id
so_obj = self . pool . get ( ' sale.order ' )
so_line_obj = self . pool . get ( ' sale.order.line ' )
so_line_product_ids = so_line_obj . search ( cr , uid , [ ( ' product_id ' , ' = ' , obj . product_id . id ) ] , context = context )
if so_line_product_ids :
so_line_product_set = ' , ' . join ( map ( str , so_line_product_ids ) )
if obj . analyzed_warehouse_id :
shops = self . pool . get ( ' sale.shop ' ) . search ( cr , uid , [ ( ' warehouse_id ' , ' = ' , obj . analyzed_warehouse_id . id ) ] , context = context )
shops_set = ' , ' . join ( map ( str , shops ) )
else :
shops = False
if obj . analyzed_dept_id :
dept_obj = self . pool . get ( ' hr.department ' )
dept_id = obj . analyzed_dept_id . id and [ obj . analyzed_dept_id . id ] or [ ]
dept_ids = dept_obj . search ( cr , uid , [ ( ' parent_id ' , ' child_of ' , dept_id ) ] )
dept_ids_set = ' , ' . join ( map ( str , dept_ids ) )
cr . execute ( " SELECT user_id FROM hr_department_user_rel WHERE (department_id IN ( %s )) " % ( dept_ids_set ) )
dept_users = [ x for x , in cr . fetchall ( ) ]
dept_users_set = ' , ' . join ( map ( str , dept_users ) )
else :
dept_users = False
factor , round_value = self . _from_default_uom_factor ( cr , uid , obj , obj . product_uom . id , context = context )
for i , period in enumerate ( periods ) :
if period :
so_period_ids = so_obj . search ( cr , uid , [ ( ' date_order ' , ' >= ' , period . date_start ) , ( ' date_order ' , ' <= ' , period . date_stop ) ] , context = context )
if so_period_ids :
so_period_set = ' , ' . join ( map ( str , so_period_ids ) )
if obj . analyzed_user_id :
user_set = str ( obj . analyzed_user_id . id )
sales [ i ] [ 0 ] = self . _sales_per_users ( cr , uid , so_period_set , so_line_product_set , obj . company_id . id , user_set )
sales [ i ] [ 0 ] * = factor
if dept_users :
sales [ i ] [ 1 ] = self . _sales_per_users ( cr , uid , so_period_set , so_line_product_set , obj . company_id . id , dept_users_set )
sales [ i ] [ 1 ] * = factor
if shops :
sales [ i ] [ 2 ] = self . _sales_per_warehouse ( cr , uid , so_period_set , so_line_product_set , obj . company_id . id , shops_set )
sales [ i ] [ 2 ] * = factor
if obj . analyze_company :
sales [ i ] [ 3 ] = self . _sales_per_company ( cr , uid , so_period_set , so_line_product_set , obj . company_id . id , )
sales [ i ] [ 3 ] * = factor
self . write ( cr , uid , ids , {
' analyzed_period1_per_user ' : sales [ 0 ] [ 0 ] ,
' analyzed_period2_per_user ' : sales [ 1 ] [ 0 ] ,
' analyzed_period3_per_user ' : sales [ 2 ] [ 0 ] ,
' analyzed_period4_per_user ' : sales [ 3 ] [ 0 ] ,
' analyzed_period5_per_user ' : sales [ 4 ] [ 0 ] ,
' analyzed_period1_per_dept ' : sales [ 0 ] [ 1 ] ,
' analyzed_period2_per_dept ' : sales [ 1 ] [ 1 ] ,
' analyzed_period3_per_dept ' : sales [ 2 ] [ 1 ] ,
' analyzed_period4_per_dept ' : sales [ 3 ] [ 1 ] ,
' analyzed_period5_per_dept ' : sales [ 4 ] [ 1 ] ,
' analyzed_period1_per_warehouse ' : sales [ 0 ] [ 2 ] ,
' analyzed_period2_per_warehouse ' : sales [ 1 ] [ 2 ] ,
' analyzed_period3_per_warehouse ' : sales [ 2 ] [ 2 ] ,
' analyzed_period4_per_warehouse ' : sales [ 3 ] [ 2 ] ,
' analyzed_period5_per_warehouse ' : sales [ 4 ] [ 2 ] ,
' analyzed_period1_per_company ' : sales [ 0 ] [ 3 ] ,
' analyzed_period2_per_company ' : sales [ 1 ] [ 3 ] ,
' analyzed_period3_per_company ' : sales [ 2 ] [ 3 ] ,
' analyzed_period4_per_company ' : sales [ 3 ] [ 3 ] ,
' analyzed_period5_per_company ' : sales [ 4 ] [ 3 ] ,
} )
return True
stock_sale_forecast ( )
# Creates stock planning records for products from selected Product Category for selected 'Warehouse - Period'
# Object added by contributor in ver 1.1
class stock_planning_createlines ( osv . osv_memory ) :
_name = " stock.planning.createlines "
2010-03-11 13:05:02 +00:00
2010-03-15 08:58:28 +00:00
def onchange_company ( self , cr , uid , ids , company_id ) :
result = { }
if company_id :
result [ ' warehouse_id2 ' ] = False
return { ' value ' : result }
_columns = {
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
' period_id2 ' : fields . many2one ( ' stock.period ' , ' Period ' , required = True , help = ' Period which planning will concern. ' ) ,
' warehouse_id2 ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , required = True , help = ' Warehouse which planning will concern. ' ) ,
' product_categ_id2 ' : fields . many2one ( ' product.category ' , ' Product Category ' , \
help = ' Planning will be created for products from Product Category selected by this field. ' \
' This field is ignored when you check \" All Forecasted Product \" box. ' ) ,
' forecasted_products ' : fields . boolean ( ' All Products with Forecast ' , \
help = " Check this box to create planning for all products having any forecast for selected Warehouse and Period. " \
" Product Category field will be ignored. " ) ,
}
_defaults = {
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.planning ' , context = c ) ,
}
def create_planning ( self , cr , uid , ids , context = { } ) :
product_obj = self . pool . get ( ' product.product ' )
planning_obj = self . pool . get ( ' stock.planning ' )
2010-05-18 06:12:23 +00:00
mod_obj = self . pool . get ( ' ir.model.data ' )
2010-03-15 08:58:28 +00:00
for f in self . browse ( cr , uid , ids , context = context ) :
if f . forecasted_products :
cr . execute ( " SELECT product_id \
FROM stock_sale_forecast \
WHERE ( period_id = % s ) AND ( warehouse_id = % s ) " , (f.period_id2.id, f.warehouse_id2.id))
products_id1 = [ x for x , in cr . fetchall ( ) ]
else :
prod_categ_obj = self . pool . get ( ' product.category ' )
template_obj = self . pool . get ( ' product.template ' )
categ_ids = f . product_categ_id2 . id and [ f . product_categ_id2 . id ] or [ ]
prod_categ_ids = prod_categ_obj . search ( cr , uid , [ ( ' parent_id ' , ' child_of ' , categ_ids ) ] )
templates_ids = template_obj . search ( cr , uid , [ ( ' categ_id ' , ' in ' , prod_categ_ids ) ] )
products_id1 = product_obj . search ( cr , uid , [ ( ' product_tmpl_id ' , ' in ' , templates_ids ) ] )
if len ( products_id1 ) == 0 :
raise osv . except_osv ( _ ( ' Error ! ' ) , _ ( ' No forecasts for selected period or no products in selected category ! ' ) )
for p in product_obj . browse ( cr , uid , products_id1 , context = context ) :
if len ( planning_obj . search ( cr , uid , [ ( ' product_id ' , ' = ' , p . id ) ,
( ' period_id ' , ' = ' , f . period_id2 . id ) ,
( ' warehouse_id ' , ' = ' , f . warehouse_id2 . id ) ] ) ) == 0 :
cr . execute ( " SELECT period.date_stop, planning.product_uom, planning.planned_outgoing, planning.to_procure, \
planning . stock_only , planning . procure_to_stock , planning . confirmed_forecasts_only , \
planning . supply_warehouse_id , planning . stock_supply_location \
FROM stock_planning AS planning \
LEFT JOIN stock_period AS period \
ON planning . period_id = period . id \
WHERE ( planning . create_uid = % s OR planning . write_uid = % s ) \
AND planning . warehouse_id = % s AND planning . product_id = % s \
AND period . date_stop < % s \
ORDER BY period . date_stop DESC " ,
( uid , uid , f . warehouse_id2 . id , p . id , f . period_id2 . date_stop ) )
ret = cr . fetchone ( )
# forecast_qty = ret and ret[0] or 0.0
if ret :
# raise osv.except_osv(_('Error !'), _('ret is %s %s %s %s %s %s')%(ret[0],ret[2],ret[3],ret[4],ret[5],ret[6],))
prod_uom = ret [ 1 ]
planned_out = ret [ 2 ]
to_procure = ret [ 3 ]
stock_only = ret [ 4 ]
procure_to_stock = ret [ 5 ]
confirmed_forecasts_only = ret [ 6 ]
supply_warehouse_id = ret [ 7 ]
stock_supply_location = ret [ 8 ]
else :
prod_uom = p . uom_id . id
planned_out = False
to_procure = False
stock_only = False
procure_to_stock = False
confirmed_forecasts_only = False
supply_warehouse_id = False
stock_supply_location = False
prod_uos_categ = False
if p . uos_id :
prod_uos_categ = p . uos_id . category_id . id
planning_obj . create ( cr , uid , {
' company_id ' : f . warehouse_id2 . company_id . id ,
' period_id ' : f . period_id2 . id ,
' warehouse_id ' : f . warehouse_id2 . id ,
' product_id ' : p . id ,
' product_uom ' : prod_uom ,
' product_uom_categ ' : p . uom_id . category_id . id ,
' product_uos_categ ' : prod_uos_categ ,
' active_uom ' : prod_uom ,
' planned_outgoing ' : planned_out ,
' to_procure ' : to_procure ,
' stock_only ' : stock_only ,
' procure_to_stock ' : procure_to_stock ,
' confirmed_forecasts_only ' : confirmed_forecasts_only ,
' supply_warehouse_id ' : supply_warehouse_id ,
' stock_supply_location ' : stock_supply_location ,
} )
2010-05-18 06:12:23 +00:00
result = mod_obj . _get_id ( cr , uid , ' stock_planning ' , ' view_stock_planning_filter ' )
id = mod_obj . read ( cr , uid , result , [ ' res_id ' ] , context = context )
2010-03-15 08:58:28 +00:00
return {
' view_type ' : ' form ' ,
" view_mode " : ' tree ' ,
' res_model ' : ' stock.planning ' ,
2010-05-18 06:12:23 +00:00
' type ' : ' ir.actions.act_window ' ,
' search_view_id ' : id [ ' res_id ' ] ,
2010-03-15 08:58:28 +00:00
}
stock_planning_createlines ( )
# The main Stock Planning object
# A lot of changes by contributor in ver 1.1
2010-03-11 13:05:02 +00:00
class stock_planning ( osv . osv ) :
_name = " stock.planning "
2010-03-15 08:58:28 +00:00
def _get_in_out ( self , cr , uid , val , date_start , date_stop , direction , done , context ) :
# res = {}
# if not context:
# context = {}
mapping = { ' in ' : {
' field ' : " incoming_qty " ,
' adapter ' : lambda x : x ,
} ,
' out ' : {
' field ' : " outgoing_qty " ,
' adapter ' : lambda x : - x ,
} ,
2010-03-11 13:05:02 +00:00
}
2010-03-15 08:58:28 +00:00
context [ ' from_date ' ] = date_start
context [ ' to_date ' ] = date_stop
locations = [ val . warehouse_id . lot_stock_id . id , ]
if not val . stock_only :
locations . extend ( [ val . warehouse_id . lot_input_id . id , val . warehouse_id . lot_output_id . id ] )
context [ ' location ' ] = locations
context [ ' compute_child ' ] = True
product_obj = self . pool . get ( ' product.product ' )
prod_id = val . product_id . id
if done :
c1 = context . copy ( )
c1 . update ( { ' states ' : ( ' done ' , ) , ' what ' : ( direction , ) } )
prod_ids = [ prod_id ]
st = product_obj . get_product_available ( cr , uid , prod_ids , context = c1 )
res = mapping [ direction ] [ ' adapter ' ] ( st . get ( prod_id , 0.0 ) )
else :
product = product_obj . read ( cr , uid , prod_id , [ ] , context )
product_qty = product [ mapping [ direction ] [ ' field ' ] ]
res = mapping [ direction ] [ ' adapter ' ] ( product_qty )
# res[val.id] = product_obj['incoming_qty']
2010-03-11 13:05:02 +00:00
return res
2010-03-15 08:58:28 +00:00
def _get_outgoing_before ( self , cr , uid , val , date_start , date_stop , context ) :
cr . execute ( " SELECT sum(planning.planned_outgoing), planning.product_uom \
FROM stock_planning AS planning \
LEFT JOIN stock_period AS period \
ON ( planning . period_id = period . id ) \
WHERE ( period . date_stop > = % s ) AND ( period . date_stop < = % s ) \
AND ( planning . product_id = % s ) AND ( planning . company_id = % s ) \
GROUP BY planning . product_uom " , \
( date_start , date_stop , val . product_id . id , val . company_id . id , ) )
planning_qtys = cr . fetchall ( )
res = self . _to_planning_uom ( cr , uid , val , planning_qtys , context )
2010-03-11 13:05:02 +00:00
return res
2010-03-15 08:58:28 +00:00
def _to_planning_uom ( self , cr , uid , val , qtys , context ) :
res_qty = 0
if qtys :
uom_obj = self . pool . get ( ' product.uom ' )
for qty , prod_uom in qtys :
coef = self . _to_default_uom_factor ( cr , uid , val , prod_uom , context = context )
res_coef , round_value = self . _from_default_uom_factor ( cr , uid , val , val . product_uom . id , context = context )
coef = coef * res_coef
res_qty + = rounding ( qty * coef , round_value )
return res_qty
def _get_forecast ( self , cr , uid , ids , field_names , arg , context ) :
2010-03-11 13:05:02 +00:00
res = { }
for val in self . browse ( cr , uid , ids ) :
2010-03-15 08:58:28 +00:00
res [ val . id ] = { }
valid_part = val . confirmed_forecasts_only and " AND state = ' validated ' " or " "
cr . execute ( ' SELECT sum(product_qty), product_uom \
FROM stock_sale_forecast \
WHERE product_id = % s AND period_id = % s AND company_id = % s ' +valid_part+ \
' GROUP BY product_uom ' , \
( val . product_id . id , val . period_id . id , val . company_id . id ) )
company_qtys = cr . fetchall ( )
res [ val . id ] [ ' company_forecast ' ] = self . _to_planning_uom ( cr , uid , val , company_qtys , context )
cr . execute ( ' SELECT sum(product_qty), product_uom \
FROM stock_sale_forecast \
WHERE product_id = % s and period_id = % s AND warehouse_id = % s ' + valid_part + \
' GROUP BY product_uom ' , \
( val . product_id . id , val . period_id . id , val . warehouse_id . id ) )
warehouse_qtys = cr . fetchall ( )
res [ val . id ] [ ' warehouse_forecast ' ] = self . _to_planning_uom ( cr , uid , val , warehouse_qtys , context )
res [ val . id ] [ ' warehouse_forecast ' ] = rounding ( res [ val . id ] [ ' warehouse_forecast ' ] , val . product_id . uom_id . rounding )
2010-03-11 13:05:02 +00:00
return res
2010-03-15 08:58:28 +00:00
def _get_stock_start ( self , cr , uid , val , date , context ) :
context [ ' from_date ' ] = None
context [ ' to_date ' ] = date
locations = [ val . warehouse_id . lot_stock_id . id , ]
if not val . stock_only :
locations . extend ( [ val . warehouse_id . lot_input_id . id , val . warehouse_id . lot_output_id . id ] )
context [ ' location ' ] = locations
context [ ' compute_child ' ] = True
product_obj = self . pool . get ( ' product.product ' ) . read ( cr , uid , val . product_id . id , [ ] , context )
res = product_obj [ ' qty_available ' ] # value for stock_start
2010-03-11 13:05:02 +00:00
return res
def _get_past_future ( self , cr , uid , ids , field_names , arg , context ) :
res = { }
2010-03-15 08:58:28 +00:00
for val in self . browse ( cr , uid , ids , context = context ) :
2010-03-11 13:05:02 +00:00
if val . period_id . date_stop < time . strftime ( ' % Y- % m- %d ' ) :
res [ val . id ] = ' Past '
else :
res [ val . id ] = ' Future '
return res
2010-03-15 08:58:28 +00:00
def _get_op ( self , cr , uid , ids , field_names , arg , context ) : # op = OrderPoint
2010-03-11 13:05:02 +00:00
res = { }
2010-03-15 08:58:28 +00:00
for val in self . browse ( cr , uid , ids , context = context ) :
res [ val . id ] = { }
cr . execute ( " SELECT product_min_qty, product_max_qty, product_uom \
FROM stock_warehouse_orderpoint \
WHERE warehouse_id = % s AND product_id = % s AND active = ' TRUE ' " , (val.warehouse_id.id, val.product_id.id))
ret = cr . fetchone ( ) or [ 0.0 , 0.0 , False ]
coef = 1
round_value = 1
if ret [ 2 ] :
coef = self . _to_default_uom_factor ( cr , uid , val , ret [ 2 ] , context )
res_coef , round_value = self . _from_default_uom_factor ( cr , uid , val , val . product_uom . id , context = context )
coef = coef * res_coef
res [ val . id ] [ ' minimum_op ' ] = rounding ( ret [ 0 ] * coef , round_value )
res [ val . id ] [ ' maximum_op ' ] = ret [ 1 ] * coef
return res
def onchange_company ( self , cr , uid , ids , company_id ) :
result = { }
if company_id :
result [ ' warehouse_id ' ] = False
return { ' value ' : result }
2010-03-11 13:05:02 +00:00
2010-03-15 08:58:28 +00:00
def onchange_uom ( self , cr , uid , ids , product_uom , ) :
if not product_uom :
return { }
ret = { }
val1 = self . browse ( cr , uid , ids )
val = val1 [ 0 ]
coeff_uom2def = self . _to_default_uom_factor ( cr , uid , val , val . active_uom . id , { } )
coeff_def2uom , round_value = self . _from_default_uom_factor ( cr , uid , val , product_uom , { } )
coeff = coeff_uom2def * coeff_def2uom
ret [ ' planned_outgoing ' ] = rounding ( coeff * val . planned_outgoing , round_value )
ret [ ' to_procure ' ] = rounding ( coeff * val . to_procure , round_value )
ret [ ' active_uom ' ] = product_uom
return { ' value ' : ret }
2010-03-11 13:05:02 +00:00
_columns = {
2010-03-15 08:58:28 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
' history ' : fields . text ( ' Procurement History ' , readonly = True , help = " History of procurement or internal supply of this planning line. " ) ,
2010-03-11 13:05:02 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' Draft ' ) , ( ' done ' , ' Done ' ) ] , ' State ' , readonly = True ) ,
2010-03-15 08:58:28 +00:00
' period_id ' : fields . many2one ( ' stock.period ' , ' Period ' , required = True , \
help = ' Period for this planning. Requisition will be created for beginning of the period. ' ) ,
' warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Warehouse ' , required = True ) ,
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , help = ' Product which this planning is created for. ' ) ,
' product_uom_categ ' : fields . many2one ( ' product.uom.categ ' , ' Product UoM Category ' ) , # Invisible field for product_uom domain
' product_uom ' : fields . many2one ( ' product.uom ' , ' UoM ' , required = True , help = " Unit of Measure used to show the quanities of stock calculation. " \
" You can use units form default category or from second category (UoS category). " ) ,
' product_uos_categ ' : fields . many2one ( ' product.uom.categ ' , ' Product UoM Category ' ) , # Invisible field for product_uos domain
# Field used in onchange_uom to check what uom was before change to recalculate quantities acording to old uom (active_uom) and new uom.
' active_uom ' : fields . many2one ( ' product.uom ' , string = " Active UoM " ) ,
' planned_outgoing ' : fields . float ( ' Planned Out ' , required = True , \
help = ' Enter planned outgoing quantity from selected Warehouse during the selected Period of selected Product. ' \
' To plan this value look at Confirmed Out or Sales Forecasts. This value should be equal or greater than Confirmed Out. ' ) ,
' company_forecast ' : fields . function ( _get_forecast , method = True , string = ' Company Forecast ' , multi = ' company ' , \
help = ' All sales forecasts for whole company (for all Warehouses) of selected Product during selected Period. ' ) ,
' warehouse_forecast ' : fields . function ( _get_forecast , method = True , string = ' Warehouse Forecast ' , multi = ' warehouse ' , \
help = ' All sales forecasts for selected Warehouse of selected Product during selected Period. ' ) ,
' stock_simulation ' : fields . float ( ' Stock Simulation ' , readonly = True , \
help = ' Stock simulation at the end of selected Period. \n For current period it is: \n ' \
' Initial Stock - Already Out + Already In - Expected Out + Incoming Left. \n ' \
' For periods ahead it is: \n Initial Stock - Planned Out Before + Incoming Before - Planned Out + Planned In. ' ) ,
' incoming ' : fields . float ( ' Confirmed In ' , readonly = True , \
help = ' Quantity of all confirmed incoming moves in calculated Period. ' ) ,
' outgoing ' : fields . float ( ' Confirmed Out ' , readonly = True , \
help = ' Quantity of all confirmed outgoing moves in calculated Period. ' ) ,
' incoming_left ' : fields . float ( ' Incoming Left ' , readonly = True , \
help = ' Quantity left to Planned incoming quantity. This is calculated difference between Planned In and Confirmed In. ' \
' For current period Already In is also calculated. This value is used to create procurement for lacking quantity. ' ) ,
' outgoing_left ' : fields . float ( ' Expected Out ' , readonly = True , \
help = ' Quantity expected to go out in selected period. As a difference between Planned Out and Confirmed Out. ' \
' For current period Already Out is also calculated ' ) ,
' to_procure ' : fields . float ( string = ' Planned In ' , required = True , \
help = ' Enter quantity which (by your plan) should come in. Change this value and observe Stock simulation. ' \
' This value should be equal or greater than Confirmed In. ' ) ,
2010-03-11 13:05:02 +00:00
' line_time ' : fields . function ( _get_past_future , method = True , type = ' char ' , string = ' Past/Future ' ) ,
2010-03-15 08:58:28 +00:00
' minimum_op ' : fields . function ( _get_op , method = True , type = ' float ' , string = ' Minimum Rule ' , multi = ' minimum ' , \
help = ' Minimum quantity set in Minimum Stock Rules for this Warhouse ' ) ,
' maximum_op ' : fields . function ( _get_op , method = True , type = ' float ' , string = ' Maximum Rule ' , multi = ' maximum ' , \
help = ' Maximum quantity set in Minimum Stock Rules for this Warhouse ' ) ,
' outgoing_before ' : fields . float ( ' Planned Out Before ' , readonly = True , \
help = ' Planned Out in periods before calculated. ' \
' Between start date of current period and one day before start of calculated period. ' ) ,
' incoming_before ' : fields . float ( ' Incoming Before ' , readonly = True , \
help = ' Confirmed incoming in periods before calculated (Including Already In). ' \
' Between start date of current period and one day before start of calculated period. ' ) ,
' stock_start ' : fields . float ( ' Initial Stock ' , readonly = True , \
help = ' Stock quantity one day before current period. ' ) ,
' already_out ' : fields . float ( ' Already Out ' , readonly = True , \
help = ' Quantity which is already dispatched out of this warehouse in current period. ' ) ,
' already_in ' : fields . float ( ' Already In ' , readonly = True , \
help = ' Quantity which is already picked up to this warehouse in current period. ' ) ,
' stock_only ' : fields . boolean ( " Stock Location Only " , help = " Check to calculate stock location of selected warehouse only. " \
" If not selected calculation is made for input, stock and output location of warehouse. " ) ,
" procure_to_stock " : fields . boolean ( " Procure To Stock Location " , help = " Chect to make procurement to stock location of selected warehouse. " \
" If not selected procurement will be made into input location of warehouse. " ) ,
" confirmed_forecasts_only " : fields . boolean ( " Validated Forecasts " , help = " Check to take validated forecasts only. " \
" If not checked system takes validated and draft forecasts. " ) ,
' supply_warehouse_id ' : fields . many2one ( ' stock.warehouse ' , ' Source Warehouse ' , help = " Warehouse used as source in supply pick move created by ' Supply from Another Warhouse ' . " ) ,
" stock_supply_location " : fields . boolean ( " Stock Supply Location " , help = " Check to supply from Stock location of Supply Warehouse. " \
" If not checked supply will be made from Output location of Supply Warehouse. Used in ' Supply from Another Warhouse ' with Supply Warehouse. " ) ,
2010-03-11 13:05:02 +00:00
}
2010-03-15 08:58:28 +00:00
2010-03-11 13:05:02 +00:00
_defaults = {
' state ' : lambda * args : ' draft ' ,
2010-03-15 08:58:28 +00:00
' to_procure ' : 0.0 ,
' planned_outgoing ' : 0.0 ,
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' stock.planning ' , context = c ) ,
2010-03-11 13:05:02 +00:00
}
_order = ' period_id '
2010-03-15 08:58:28 +00:00
def _to_default_uom_factor ( self , cr , uid , val , uom_id , context ) :
uom_obj = self . pool . get ( ' product.uom ' )
uom = uom_obj . browse ( cr , uid , uom_id , context = context )
coef = uom . factor
if uom . category_id . id != val . product_id . uom_id . category_id . id :
coef = coef / val . product_id . uos_coeff
return val . product_id . uom_id . factor / coef
def _from_default_uom_factor ( self , cr , uid , val , uom_id , context ) :
uom_obj = self . pool . get ( ' product.uom ' )
uom = uom_obj . browse ( cr , uid , uom_id , context = context )
res = uom . factor
if uom . category_id . id != val . product_id . uom_id . category_id . id :
res = res / val . product_id . uos_coeff
return res / val . product_id . uom_id . factor , uom . rounding
def calculate_planning ( self , cr , uid , ids , context , * args ) :
one_minute = RelativeDateTime ( minutes = 1 )
current_date_beginning_c = mx . DateTime . today ( )
current_date_end_c = current_date_beginning_c + RelativeDateTime ( days = 1 , minutes = - 1 ) # to get hour 23:59:00
current_date_beginning = current_date_beginning_c . strftime ( ' % Y- % m- %d % H: % M: % S ' )
current_date_end = current_date_end_c . strftime ( ' % Y- % m- %d % H: % M: % S ' )
for val in self . browse ( cr , uid , ids , context = context ) :
day = mx . DateTime . strptime ( val . period_id . date_start , ' % Y- % m- %d % H: % M: % S ' )
dbefore = mx . DateTime . DateTime ( day . year , day . month , day . day ) - one_minute
day_before_calculated_period = dbefore . strftime ( ' % Y- % m- %d % H: % M: % S ' ) # one day before start of calculated period
cr . execute ( " SELECT date_start \
FROM stock_period AS period \
LEFT JOIN stock_planning AS planning \
ON ( planning . period_id = period . id ) \
WHERE ( period . date_stop > = % s ) AND ( period . date_start < = % s ) AND \
planning . product_id = % s " , (current_date_end, current_date_end, val.product_id.id,)) #
date = cr . fetchone ( )
start_date_current_period = date and date [ 0 ] or False
start_date_current_period = start_date_current_period or current_date_beginning
day = mx . DateTime . strptime ( start_date_current_period , ' % Y- % m- %d % H: % M: % S ' )
dbefore = mx . DateTime . DateTime ( day . year , day . month , day . day ) - one_minute
date_for_start = dbefore . strftime ( ' % Y- % m- %d % H: % M: % S ' ) # one day before current period
already_out = self . _get_in_out ( cr , uid , val , start_date_current_period , current_date_end , direction = ' out ' , done = True , context = context ) ,
already_in = self . _get_in_out ( cr , uid , val , start_date_current_period , current_date_end , direction = ' in ' , done = True , context = context ) ,
outgoing = self . _get_in_out ( cr , uid , val , val . period_id . date_start , val . period_id . date_stop , direction = ' out ' , done = False , context = context ) ,
incoming = self . _get_in_out ( cr , uid , val , val . period_id . date_start , val . period_id . date_stop , direction = ' in ' , done = False , context = context ) ,
outgoing_before = self . _get_outgoing_before ( cr , uid , val , start_date_current_period , day_before_calculated_period , context = context ) ,
incoming_before = self . _get_in_out ( cr , uid , val , start_date_current_period , day_before_calculated_period , direction = ' in ' , done = False , context = context ) ,
stock_start = self . _get_stock_start ( cr , uid , val , date_for_start , context = context ) ,
if start_date_current_period == val . period_id . date_start : # current period is calculated
current = True
else :
current = False
factor , round_value = self . _from_default_uom_factor ( cr , uid , val , val . product_uom . id , context = context )
self . write ( cr , uid , ids , {
' already_out ' : rounding ( already_out [ 0 ] * factor , round_value ) ,
' already_in ' : rounding ( already_in [ 0 ] * factor , round_value ) ,
' outgoing ' : rounding ( outgoing [ 0 ] * factor , round_value ) ,
' incoming ' : rounding ( incoming [ 0 ] * factor , round_value ) ,
' outgoing_before ' : rounding ( outgoing_before [ 0 ] * factor , round_value ) ,
' incoming_before ' : rounding ( ( incoming_before [ 0 ] + ( not current and already_in [ 0 ] ) ) * factor , round_value ) ,
' outgoing_left ' : rounding ( val . planned_outgoing - ( outgoing [ 0 ] + ( current and already_out [ 0 ] ) ) * factor , round_value ) ,
' incoming_left ' : rounding ( val . to_procure - ( incoming [ 0 ] + ( current and already_in [ 0 ] ) ) * factor , round_value ) ,
' stock_start ' : rounding ( stock_start [ 0 ] * factor , round_value ) ,
' stock_simulation ' : rounding ( val . to_procure - val . planned_outgoing + ( stock_start [ 0 ] + incoming_before [ 0 ] - outgoing_before [ 0 ] \
+ ( not current and already_in [ 0 ] ) ) * factor , round_value ) ,
} )
return True
# method below converts quantities and uoms to general OpenERP standard with UoM Qty, UoM, UoS Qty, UoS.
# from stock_planning standard where you have one Qty and one UoM (any from UoM or UoS category)
# so if UoM is from UoM category it is used as UoM in standard and if product has UoS the UoS will be calcualated.
# If UoM is from UoS category it is recalculated to basic UoS from product (in planning you can use any UoS from UoS category)
# and basic UoM is calculated.
def _qty_to_standard ( self , cr , uid , val , context ) :
uos = False
uos_qty = 0.0
if val . product_uom . category_id . id == val . product_id . uom_id . category_id . id :
uom_qty = val . incoming_left
uom = val . product_uom . id
if val . product_id . uos_id :
uos = val . product_id . uos_id . id
coeff_uom2def = self . _to_default_uom_factor ( cr , uid , val , val . product_uom . id , { } )
coeff_def2uom , round_value = self . _from_default_uom_factor ( cr , uid , val , uos , { } )
uos_qty = rounding ( val . incoming_left * coeff_uom2def * coeff_def2uom , round_value )
elif val . product_uom . category_id . id == val . product_id . uos_id . category_id . id :
coeff_uom2def = self . _to_default_uom_factor ( cr , uid , val , val . product_uom . id , { } )
uos = val . product_id . uos_id . id
coeff_def2uom , round_value = self . _from_default_uom_factor ( cr , uid , val , uos , { } )
uos_qty = rounding ( val . incoming_left * coeff_uom2def * coeff_def2uom , round_value )
uom = val . product_id . uom_id . id
coeff_def2uom , round_value = self . _from_default_uom_factor ( cr , uid , val , uom , { } )
uom_qty = rounding ( val . incoming_left * coeff_uom2def * coeff_def2uom , round_value )
return uom_qty , uom , uos_qty , uos
2010-03-11 13:05:02 +00:00
def procure_incomming_left ( self , cr , uid , ids , context , * args ) :
for obj in self . browse ( cr , uid , ids ) :
2010-03-15 08:58:28 +00:00
if obj . incoming_left < = 0 :
raise osv . except_osv ( _ ( ' Error ! ' ) , _ ( ' Incoming Left must be greater than 0 ! ' ) )
uom_qty , uom , uos_qty , uos = self . _qty_to_standard ( cr , uid , obj , context )
user = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context )
2010-05-27 12:47:06 +00:00
proc_id = self . pool . get ( ' procurement.order ' ) . create ( cr , uid , {
2010-03-15 08:58:28 +00:00
' company_id ' : obj . company_id . id ,
' name ' : _ ( ' Manual planning for ' ) + obj . period_id . name ,
' origin ' : _ ( ' MPS( ' ) + str ( user . login ) + ' ) ' + obj . period_id . name ,
' date_planned ' : obj . period_id . date_start ,
' product_id ' : obj . product_id . id ,
' product_qty ' : uom_qty ,
' product_uom ' : uom ,
' product_uos_qty ' : uos_qty ,
' product_uos ' : uos ,
' location_id ' : obj . procure_to_stock and obj . warehouse_id . lot_stock_id . id or obj . warehouse_id . lot_input_id . id ,
' procure_method ' : ' make_to_order ' ,
' note ' : _ ( " Procurement created in MPS by user: " ) + str ( user . login ) + _ ( " Creation Date: " ) + \
time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) + \
_ ( " \n For period: " ) + obj . period_id . name + _ ( " according to state: " ) + \
_ ( " \n Warehouse Forecast: " ) + str ( obj . warehouse_forecast ) + \
_ ( " \n Initial Stock: " ) + str ( obj . stock_start ) + \
_ ( " \n Planned Out: " ) + str ( obj . planned_outgoing ) + _ ( " Planned In: " ) + str ( obj . to_procure ) + \
_ ( " \n Already Out: " ) + str ( obj . already_out ) + _ ( " Already In: " ) + str ( obj . already_in ) + \
_ ( " \n Confirmed Out: " ) + str ( obj . outgoing ) + _ ( " Confirmed In: " ) + str ( obj . incoming ) + \
_ ( " \n Planned Out Before: " ) + str ( obj . outgoing_before ) + _ ( " Confirmed In Before: " ) + \
str ( obj . incoming_before ) + \
_ ( " \n Expected Out: " ) + str ( obj . outgoing_left ) + _ ( " Incoming Left: " ) + str ( obj . incoming_left ) + \
_ ( " \n Stock Simulation: " ) + str ( obj . stock_simulation ) + _ ( " Minimum stock: " ) + str ( obj . minimum_op ) ,
} , context = context )
wf_service = netsvc . LocalService ( " workflow " )
2010-05-27 12:47:06 +00:00
wf_service . trg_validate ( uid , ' procurement.order ' , proc_id , ' button_confirm ' , cr )
2010-03-15 08:58:28 +00:00
self . calculate_planning ( cr , uid , ids , context )
prev_text = obj . history or " "
self . write ( cr , uid , ids , {
' history ' : prev_text + _ ( ' Requisition ( ' ) + str ( user . login ) + " , " + time . strftime ( ' % Y. % m. %d % H: % M) ' ) + str ( obj . incoming_left ) + \
" " + obj . product_uom . name + " \n " ,
} )
2010-03-11 13:05:02 +00:00
return True
2010-03-15 08:58:28 +00:00
def internal_supply ( self , cr , uid , ids , context , * args ) :
for obj in self . browse ( cr , uid , ids ) :
if obj . incoming_left < = 0 :
raise osv . except_osv ( _ ( ' Error ! ' ) , _ ( ' Incoming Left must be greater than 0 ! ' ) )
if not obj . supply_warehouse_id :
raise osv . except_osv ( _ ( ' Error ! ' ) , _ ( ' You must specify a Source Warehouse ! ' ) )
if obj . supply_warehouse_id . id == obj . warehouse_id . id :
raise osv . except_osv ( _ ( ' Error ! ' ) , _ ( ' You must specify a Source Warehouse different than calculated (destination) Warehouse ! ' ) )
uom_qty , uom , uos_qty , uos = self . _qty_to_standard ( cr , uid , obj , context )
user = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context )
picking_id = self . pool . get ( ' stock.picking ' ) . create ( cr , uid , {
' origin ' : _ ( ' MPS( ' ) + str ( user . login ) + ' ) ' + obj . period_id . name ,
' type ' : ' internal ' ,
' state ' : ' auto ' ,
' date ' : obj . period_id . date_start ,
' move_type ' : ' direct ' ,
' invoice_state ' : ' none ' ,
' company_id ' : obj . company_id . id ,
' note ' : _ ( " Pick created from MPS by user: " ) + str ( user . login ) + _ ( " Creation Date: " ) + \
time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) + \
_ ( " \n For period: " ) + obj . period_id . name + _ ( " according to state: " ) + \
_ ( " \n Warehouse Forecast: " ) + str ( obj . warehouse_forecast ) + \
_ ( " \n Initial Stock: " ) + str ( obj . stock_start ) + \
_ ( " \n Planned Out: " ) + str ( obj . planned_outgoing ) + _ ( " Planned In: " ) + str ( obj . to_procure ) + \
_ ( " \n Already Out: " ) + str ( obj . already_out ) + _ ( " Already In: " ) + str ( obj . already_in ) + \
_ ( " \n Confirmed Out: " ) + str ( obj . outgoing ) + _ ( " Confirmed In: " ) + str ( obj . incoming ) + \
_ ( " \n Planned Out Before: " ) + str ( obj . outgoing_before ) + _ ( " Confirmed In Before: " ) + \
str ( obj . incoming_before ) + \
_ ( " \n Expected Out: " ) + str ( obj . outgoing_left ) + _ ( " Incoming Left: " ) + str ( obj . incoming_left ) + \
_ ( " \n Stock Simulation: " ) + str ( obj . stock_simulation ) + _ ( " Minimum stock: " ) + str ( obj . minimum_op ) ,
} )
move_id = self . pool . get ( ' stock.move ' ) . create ( cr , uid , {
' name ' : _ ( ' MPS( ' ) + str ( user . login ) + ' ) ' + obj . period_id . name ,
' picking_id ' : picking_id ,
' product_id ' : obj . product_id . id ,
' date_planned ' : obj . period_id . date_start ,
' product_qty ' : uom_qty ,
' product_uom ' : uom ,
' product_uos_qty ' : uos_qty ,
' product_uos ' : uos ,
' location_id ' : obj . stock_supply_location and obj . supply_warehouse_id . lot_stock_id . id or \
obj . supply_warehouse_id . lot_output_id . id ,
' location_dest_id ' : obj . procure_to_stock and obj . warehouse_id . lot_stock_id . id or \
obj . warehouse_id . lot_input_id . id ,
' tracking_id ' : False ,
' company_id ' : obj . company_id . id ,
} )
wf_service = netsvc . LocalService ( " workflow " )
wf_service . trg_validate ( uid , ' stock.picking ' , picking_id , ' button_confirm ' , cr )
self . calculate_planning ( cr , uid , ids , context )
prev_text = obj . history or " "
pick_name = self . pool . get ( ' stock.picking ' ) . browse ( cr , uid , picking_id ) . name
self . write ( cr , uid , ids , {
' history ' : prev_text + _ ( ' Pick List ' ) + pick_name + " ( " + str ( user . login ) + " , " + time . strftime ( ' % Y. % m. %d % H: % M) ' ) \
+ str ( obj . incoming_left ) + " " + obj . product_uom . name + " \n " ,
} )
return True
def product_id_change ( self , cr , uid , ids , product_id ) :
ret = { }
if product_id :
product_rec = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id )
ret [ ' product_uom ' ] = product_rec . uom_id . id
ret [ ' active_uom ' ] = product_rec . uom_id . id
ret [ ' product_uom_categ ' ] = product_rec . uom_id . category_id . id
ret [ ' product_uos_categ ' ] = product_rec . uos_id and product_rec . uos_id . category_id . id or False
else :
ret [ ' product_uom ' ] = False
ret [ ' product_uom_categ ' ] = False
ret [ ' product_uos_categ ' ] = False
res = { ' value ' : ret }
return res
2010-03-11 13:05:02 +00:00
stock_planning ( )
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: