2010-04-29 13:30:07 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# 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/>.
#
##############################################################################
2013-01-28 10:00:38 +00:00
import time
2013-06-30 22:26:46 +00:00
from datetime import datetime
from dateutil . relativedelta import relativedelta
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
2012-12-17 15:23:03 +00:00
import openerp . addons . decimal_precision as dp
2013-07-08 13:06:18 +00:00
from openerp . tools . translate import _
2013-07-08 13:35:57 +00:00
import openerp
2010-04-29 13:30:07 +00:00
2013-06-29 09:13:28 +00:00
class procurement_group ( osv . osv ) :
2013-06-27 14:46:29 +00:00
'''
2013-06-29 09:13:28 +00:00
The procurement requirement class is used to group products together
when computing procurements . ( tasks , physical products , . . . )
The goal is that when you have one sale order of several products
and the products are pulled from the same or several location ( s ) , to keep
having the moves grouped into pickings that represent the sale order .
Used in : sales order ( to group delivery order lines like the so ) , pull / push
rules ( to pack like the delivery order ) , on orderpoints ( e . g . for wave picking
all the similar products together ) .
Grouping is made only if the source and the destination is the same .
Suppose you have 4 lines on a picking from Output where 2 lines will need
to come from Input ( crossdock ) and 2 lines coming from Stock - > Output As
the four procurement orders will have the same group ids from the SO , the
move from input will have a stock . picking with 2 grouped lines and the move
from stock will have 2 grouped lines also .
The name is usually the name of the original document ( sale order ) or a
sequence computed if created manually .
2013-06-27 14:46:29 +00:00
'''
2013-06-29 09:13:28 +00:00
_name = ' procurement.group '
_description = ' Procurement Requisition '
_order = " id desc "
2013-06-27 14:46:29 +00:00
_columns = {
2013-07-25 14:43:35 +00:00
' name ' : fields . char ( ' Reference ' , required = True ) ,
2013-07-29 21:44:43 +00:00
' move_type ' : fields . selection ( [
( ' direct ' , ' Partial ' ) , ( ' one ' , ' All at once ' ) ] ,
' Delivery Method ' , required = True ) ,
2013-07-19 11:55:33 +00:00
' partner_id ' : fields . many2one ( ' res.partner ' , string = ' Partner ' ) , #Sale should pass it here
2013-07-23 07:50:53 +00:00
' procurement_ids ' : fields . many2one ( ' procurement.order ' , ' group_id ' , ' Procurements ' ) ,
2013-06-29 09:13:28 +00:00
}
_defaults = {
2013-07-29 21:44:43 +00:00
' name ' : lambda self , cr , uid , c : self . pool . get ( ' ir.sequence ' ) . get ( cr , uid , ' procurement.group ' ) or ' ' ,
' move_type ' : lambda self , cr , uid , c : ' one '
2013-06-29 09:13:28 +00:00
}
2013-06-27 14:46:29 +00:00
2013-06-30 22:26:46 +00:00
class procurement_rule ( osv . osv ) :
'''
A rule describe what a procurement should do ; produce , buy , move , . . .
'''
_name = ' procurement.rule '
_description = " Procurement Rule "
2013-07-08 13:06:18 +00:00
def _get_action ( self , cr , uid , context = None ) :
2013-07-10 10:31:56 +00:00
return [ ]
2013-07-08 13:06:18 +00:00
2013-06-30 22:26:46 +00:00
_columns = {
' name ' : fields . char ( ' Name ' , required = True ,
help = " This field will fill the packing origin and the name of its moves " ) ,
' group_id ' : fields . many2one ( ' procurement.group ' , ' Procurement Group ' ) ,
2013-07-08 13:06:18 +00:00
' action ' : fields . selection ( selection = lambda s , cr , uid , context = None : s . _get_action ( cr , uid , context = context ) ,
2013-07-12 16:31:24 +00:00
string = ' Action ' , required = True ) ,
2013-06-30 22:26:46 +00:00
}
2013-06-27 14:46:29 +00:00
2010-05-27 12:47:06 +00:00
class procurement_order ( osv . osv ) :
2010-04-29 13:30:07 +00:00
"""
Procurement Orders
"""
2010-05-27 12:47:06 +00:00
_name = " procurement.order "
2010-04-29 13:30:07 +00:00
_description = " Procurement "
2012-09-11 14:01:33 +00:00
_order = ' priority desc,date_planned '
2012-05-07 10:43:44 +00:00
_inherit = [ ' mail.thread ' ]
2010-05-19 20:02:36 +00:00
_log_create = False
2010-04-29 13:30:07 +00:00
_columns = {
2012-12-18 15:48:55 +00:00
' name ' : fields . text ( ' Description ' , required = True ) ,
2013-06-30 22:26:46 +00:00
2010-04-29 13:30:07 +00:00
' origin ' : fields . char ( ' Source Document ' , size = 64 ,
help = " Reference of the document that created this Procurement. \n "
2010-08-10 11:35:06 +00:00
" This is automatically completed by OpenERP. " ) ,
2013-07-09 13:22:17 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' , required = True ) ,
2013-06-30 22:26:46 +00:00
# These two fields are used for shceduling
2013-07-09 13:22:17 +00:00
' priority ' : fields . selection ( [ ( ' 0 ' , ' Not urgent ' ) , ( ' 1 ' , ' Normal ' ) , ( ' 2 ' , ' Urgent ' ) , ( ' 3 ' , ' Very Urgent ' ) ] , ' Priority ' , required = True , select = True ) ,
2011-12-09 06:03:08 +00:00
' date_planned ' : fields . datetime ( ' Scheduled date ' , required = True , select = True ) ,
2013-06-30 22:26:46 +00:00
2013-07-09 13:22:17 +00:00
' group_id ' : fields . many2one ( ' procurement.group ' , ' Procurement Requisition ' ) ,
' rule_id ' : fields . many2one ( ' procurement.rule ' , ' Rule ' ) ,
2013-06-30 22:26:46 +00:00
2013-07-10 15:02:07 +00:00
' product_id ' : fields . many2one ( ' product.product ' , ' Product ' , required = True , states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } , readonly = True ) ,
' product_qty ' : fields . float ( ' Quantity ' , digits_compute = dp . get_precision ( ' Product Unit of Measure ' ) , required = True , states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } , readonly = True ) ,
' product_uom ' : fields . many2one ( ' product.uom ' , ' Product Unit of Measure ' , required = True , states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } , readonly = True ) ,
2013-06-30 22:26:46 +00:00
2013-07-10 15:02:07 +00:00
' product_uos_qty ' : fields . float ( ' UoS Quantity ' , states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } , readonly = True ) ,
' product_uos ' : fields . many2one ( ' product.uom ' , ' Product UoS ' , states = { ' confirmed ' : [ ( ' readonly ' , False ) ] } , readonly = True ) ,
2013-06-30 22:26:46 +00:00
2010-04-29 13:30:07 +00:00
' state ' : fields . selection ( [
2013-07-09 13:22:17 +00:00
( ' cancel ' , ' Cancelled ' ) ,
( ' confirmed ' , ' Confirmed ' ) ,
( ' exception ' , ' Exception ' ) ,
( ' running ' , ' Running ' ) ,
( ' done ' , ' Done ' )
2013-06-30 22:26:46 +00:00
] , ' Status ' , required = True , track_visibility = ' onchange ' ) ,
2013-07-11 13:02:37 +00:00
' message ' : fields . text ( ' Latest error ' , help = " Exception occurred while computing procurement orders. " ) ,
2013-06-30 22:26:46 +00:00
2010-04-29 13:30:07 +00:00
}
_defaults = {
2013-06-30 22:26:46 +00:00
' state ' : ' confirmed ' ,
2010-12-07 17:53:42 +00:00
' priority ' : ' 1 ' ,
2010-04-29 13:30:07 +00:00
' date_planned ' : lambda * a : time . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
2010-05-27 12:47:06 +00:00
' company_id ' : lambda self , cr , uid , c : self . pool . get ( ' res.company ' ) . _company_default_get ( cr , uid , ' procurement.order ' , context = c )
2010-04-29 13:30:07 +00:00
}
2010-11-19 13:48:01 +00:00
def onchange_product_id ( self , cr , uid , ids , product_id , context = None ) :
2010-04-29 13:30:07 +00:00
""" Finds UoM and UoS of changed product.
@param product_id : Changed id of product .
@return : Dictionary of values .
"""
if product_id :
2010-11-19 13:48:01 +00:00
w = self . pool . get ( ' product.product ' ) . browse ( cr , uid , product_id , context = context )
2010-04-29 13:30:07 +00:00
v = {
' product_uom ' : w . uom_id . id ,
' product_uos ' : w . uos_id and w . uos_id . id or w . uom_id . id
}
return { ' value ' : v }
return { }
2013-06-30 22:26:46 +00:00
def run ( self , cr , uid , ids , context = None ) :
2013-07-10 15:00:42 +00:00
for procurement in self . browse ( cr , uid , ids , context = context ) :
if self . _assign ( cr , uid , procurement , context = context ) :
2013-07-09 13:22:17 +00:00
procurement . refresh ( )
self . _run ( cr , uid , procurement , context = context or { } )
2013-06-30 22:26:46 +00:00
self . write ( cr , uid , [ procurement . id ] , { ' state ' : ' running ' } , context = context )
2010-04-29 13:30:07 +00:00
else :
2013-07-09 13:22:17 +00:00
self . message_post ( cr , uid , [ procurement . id ] , body = _ ( ' No rule matching this procurement ' ) , context = context )
2013-06-30 22:26:46 +00:00
self . write ( cr , uid , [ procurement . id ] , { ' state ' : ' exception ' } , context = context )
2010-04-29 13:30:07 +00:00
return True
2013-06-30 22:26:46 +00:00
def check ( self , cr , uid , ids , context = None ) :
done = [ ]
2013-07-10 10:16:16 +00:00
for procurement in self . browse ( cr , uid , ids , context = context ) :
result = self . _check ( cr , uid , procurement , context = context )
2013-06-30 22:26:46 +00:00
if result :
self . write ( cr , uid , [ procurement . id ] , { ' state ' : ' done ' } , context = context )
done . append ( procurement . id )
return done
2013-02-04 16:31:51 +00:00
2013-06-30 22:26:46 +00:00
#
# Method to overwrite in different procurement modules
#
2013-07-12 08:37:40 +00:00
2013-07-10 15:00:42 +00:00
def _find_suitable_rule ( self , cr , uid , procurement , context = None ) :
''' This method returns a procurement.rule that depicts what to do with the given procurement
2013-07-10 10:16:16 +00:00
in order to complete its needs . It returns False if no suiting rule is found .
: param procurement : browse record
: rtype : int or False
'''
2013-07-01 06:30:34 +00:00
return False
2013-07-10 15:00:42 +00:00
def _assign ( self , cr , uid , procurement , context = None ) :
''' This method check what to do with the given procurement in order to complete its needs.
It returns False if no solution is found , otherwise it stores the matching rule ( if any ) and
returns True .
: param procurement : browse record
: rtype : boolean
'''
rule_id = self . _find_suitable_rule ( cr , uid , procurement , context = context )
if rule_id :
self . write ( cr , uid , [ procurement . id ] , { ' rule_id ' : rule_id } , context = context )
return True
return False
2013-06-30 22:26:46 +00:00
def _run ( self , cr , uid , procurement , context = None ) :
2013-07-10 10:16:16 +00:00
''' This method implements the resolution of the given procurement
: param procurement : browse record
'''
2010-04-29 13:30:07 +00:00
return True
2013-06-30 22:26:46 +00:00
def _check ( self , cr , uid , procurement , context = None ) :
2013-07-10 10:16:16 +00:00
''' Returns True if the given procurement is fulfilled, False otherwise
: param procurement : browse record
: rtype : boolean
'''
2013-07-10 15:38:55 +00:00
return False
2010-04-29 13:30:07 +00:00
2013-06-30 22:26:46 +00:00
#
# Scheduler
#
def run_scheduler ( self , cr , uid , use_new_cursor = False , context = None ) :
2012-05-23 11:04:39 +00:00
'''
2013-06-30 22:26:46 +00:00
Call the scheduler to check the procurement order
@param self : The object pointer
@param cr : The current row , from the database cursor ,
@param uid : The current user ID for security checks
@param ids : List of selected IDs
@param use_new_cursor : False or the dbname
@param context : A standard dictionary for contextual values
@return : Dictionary of values
2012-05-23 11:04:39 +00:00
'''
2013-06-30 22:26:46 +00:00
if context is None :
2012-05-23 11:04:39 +00:00
context = { }
2013-06-30 22:26:46 +00:00
try :
if use_new_cursor :
2013-07-08 13:35:57 +00:00
cr = openerp . registry ( use_new_cursor ) . db . cursor ( )
2013-06-30 22:26:46 +00:00
company = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid , context = context ) . company_id
maxdate = ( datetime . today ( ) + relativedelta ( days = company . schedule_range ) ) . strftime ( ' % Y- % m- %d % H: % M: % S ' )
# Run confirmed procurements
while True :
2013-07-09 13:22:17 +00:00
ids = self . search ( cr , uid , [ ( ' state ' , ' = ' , ' confirmed ' ) , ( ' date_planned ' , ' <= ' , maxdate ) ] , context = context )
if not ids :
break
2013-06-30 22:26:46 +00:00
self . run ( cr , uid , ids , context = context )
if use_new_cursor :
cr . commit ( )
# Check if running procurements are done
offset = 0
while True :
2013-07-09 13:22:17 +00:00
ids = self . search ( cr , uid , [ ( ' state ' , ' = ' , ' running ' ) , ( ' date_planned ' , ' <= ' , maxdate ) ] , offset = offset , context = context )
if not ids :
break
2013-06-30 22:26:46 +00:00
done = self . check ( cr , uid , ids , context = context )
offset + = len ( ids ) - len ( done )
if use_new_cursor and len ( done ) :
cr . commit ( )
finally :
if use_new_cursor :
try :
cr . close ( )
except Exception :
pass
2010-04-29 13:30:07 +00:00
return { }
2013-07-09 13:22:17 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: