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
2014-08-13 10:00:38 +00:00
from psycopg2 import OperationalError
2013-01-28 10:00:38 +00:00
2014-04-03 07:10:10 +00:00
from openerp import SUPERUSER_ID
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
2014-05-22 09:02:58 +00:00
PROCUREMENT_PRIORITIES = [ ( ' 0 ' , ' Not urgent ' ) , ( ' 1 ' , ' Normal ' ) , ( ' 2 ' , ' Urgent ' ) , ( ' 3 ' , ' Very Urgent ' ) ]
2013-06-29 09:13:28 +00:00
class procurement_group ( osv . osv ) :
2013-06-27 14:46:29 +00:00
'''
2014-04-03 07:10:10 +00:00
The procurement group class is used to group products together
2013-06-29 09:13:28 +00:00
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 = {
2014-04-03 07:10:10 +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 ) ,
2014-04-03 07:10:10 +00:00
' procurement_ids ' : fields . one2many ( ' 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 ' ' ,
2014-06-02 07:58:09 +00:00
' move_type ' : lambda self , cr , uid , c : ' direct '
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-09-23 12:22:22 +00:00
_order = " name "
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 = {
2014-12-12 17:07:48 +00:00
' name ' : fields . char ( ' Name ' , required = True , translate = True ,
2013-06-30 22:26:46 +00:00
help = " This field will fill the packing origin and the name of its moves " ) ,
2014-04-23 08:23:22 +00:00
' active ' : fields . boolean ( ' Active ' , help = " If unchecked, it will allow you to hide the rule without removing it. " ) ,
2013-11-07 11:20:53 +00:00
' group_propagation_option ' : fields . selection ( [ ( ' none ' , ' Leave Empty ' ) , ( ' propagate ' , ' Propagate ' ) , ( ' fixed ' , ' Fixed ' ) ] , string = " Propagation of Procurement Group " ) ,
' group_id ' : fields . many2one ( ' procurement.group ' , ' Fixed 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-11-07 11:20:53 +00:00
' sequence ' : fields . integer ( ' Sequence ' ) ,
2013-10-11 07:22:10 +00:00
' company_id ' : fields . many2one ( ' res.company ' , ' Company ' ) ,
2013-06-30 22:26:46 +00:00
}
2013-06-27 14:46:29 +00:00
2013-11-07 11:20:53 +00:00
_defaults = {
' group_propagation_option ' : ' propagate ' ,
' sequence ' : 20 ,
2014-04-23 08:23:22 +00:00
' active ' : True ,
2013-11-07 11:20:53 +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 "
2013-11-06 08:54:48 +00:00
_order = ' priority desc, date_planned, id asc '
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
2014-05-21 09:52:05 +00:00
' origin ' : fields . char ( ' Source Document ' ,
2010-04-29 13:30:07 +00:00
help = " Reference of the document that created this Procurement. \n "
2014-07-09 11:39:38 +00:00
" This is automatically completed by Odoo. " ) ,
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
2014-05-22 09:02:58 +00:00
' priority ' : fields . selection ( PROCUREMENT_PRIORITIES , ' Priority ' , required = True , select = True , track_visibility = ' onchange ' ) ,
2013-11-07 11:20:53 +00:00
' date_planned ' : fields . datetime ( ' Scheduled Date ' , required = True , select = True , track_visibility = ' onchange ' ) ,
2013-06-30 22:26:46 +00:00
2013-10-08 14:18:34 +00:00
' group_id ' : fields . many2one ( ' procurement.group ' , ' Procurement Group ' ) ,
2013-12-20 09:26:50 +00:00
' rule_id ' : fields . many2one ( ' procurement.rule ' , ' Rule ' , track_visibility = ' onchange ' , help = " Chosen rule for the procurement resolution. Usually chosen by the system but can be manually set by the procurement manager to force an unusual behavior. " ) ,
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 ' )
2014-07-06 14:44:26 +00:00
] , ' Status ' , required = True , track_visibility = ' onchange ' , copy = False ) ,
2010-04-29 13:30:07 +00:00
}
2013-08-02 11:33:08 +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
}
def unlink ( self , cr , uid , ids , context = None ) :
2010-12-13 06:43:09 +00:00
procurements = self . read ( cr , uid , ids , [ ' state ' ] , context = context )
2010-04-29 13:30:07 +00:00
unlink_ids = [ ]
for s in procurements :
2014-04-28 15:33:55 +00:00
if s [ ' state ' ] == ' cancel ' :
2010-04-29 13:30:07 +00:00
unlink_ids . append ( s [ ' id ' ] )
else :
2012-08-07 11:06:16 +00:00
raise osv . except_osv ( _ ( ' Invalid Action! ' ) ,
2013-11-07 11:20:53 +00:00
_ ( ' Cannot delete Procurement Order(s) which are in %s state. ' ) % s [ ' state ' ] )
2010-04-29 13:30:07 +00:00
return osv . osv . unlink ( self , cr , uid , unlink_ids , context = context )
2013-11-07 11:20:53 +00:00
def do_view_procurements ( self , cr , uid , ids , context = None ) :
'''
This function returns an action that display existing procurement orders
of same procurement group of given ids .
'''
act_obj = self . pool . get ( ' ir.actions.act_window ' )
2014-05-07 09:18:18 +00:00
action_id = self . pool . get ( ' ir.model.data ' ) . xmlid_to_res_id ( cr , uid , ' procurement.do_view_procurements ' , raise_if_not_found = True )
result = act_obj . read ( cr , uid , [ action_id ] , context = context ) [ 0 ]
2013-11-07 11:20:53 +00:00
group_ids = set ( [ proc . group_id . id for proc in self . browse ( cr , uid , ids , context = context ) if proc . group_id ] )
result [ ' domain ' ] = " [( ' group_id ' , ' in ' ,[ " + ' , ' . join ( map ( str , list ( group_ids ) ) ) + " ])] "
return result
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-12-18 10:13:45 +00:00
def get_cancel_ids ( self , cr , uid , ids , context = None ) :
return [ proc . id for proc in self . browse ( cr , uid , ids , context = context ) if proc . state != ' done ' ]
2013-08-04 13:52:18 +00:00
def cancel ( self , cr , uid , ids , context = None ) :
2013-12-18 10:13:45 +00:00
#cancel only the procurements that aren't done already
to_cancel_ids = self . get_cancel_ids ( cr , uid , ids , context = context )
if to_cancel_ids :
return self . write ( cr , uid , to_cancel_ids , { ' state ' : ' cancel ' } , context = context )
2013-11-06 08:32:31 +00:00
def reset_to_confirmed ( self , cr , uid , ids , context = None ) :
return self . write ( cr , uid , ids , { ' state ' : ' confirmed ' } , context = context )
2013-08-04 13:52:18 +00:00
2014-08-13 10:00:38 +00:00
def run ( self , cr , uid , ids , autocommit = False , context = None ) :
2014-03-26 14:29:23 +00:00
for procurement_id in ids :
2014-07-15 16:38:06 +00:00
#we intentionnaly do the browse under the for loop to avoid caching all ids which would be resource greedy
2014-04-03 09:53:58 +00:00
#and useless as we'll make a refresh later that will invalidate all the cache (and thus the next iteration
#will fetch all the ids again)
2014-03-26 14:29:23 +00:00
procurement = self . browse ( cr , uid , procurement_id , context = context )
2013-08-16 10:27:34 +00:00
if procurement . state not in ( " running " , " done " ) :
2014-08-13 10:00:38 +00:00
try :
if self . _assign ( cr , uid , procurement , context = context ) :
res = self . _run ( cr , uid , procurement , context = context or { } )
if res :
self . write ( cr , uid , [ procurement . id ] , { ' state ' : ' running ' } , context = context )
else :
self . write ( cr , uid , [ procurement . id ] , { ' state ' : ' exception ' } , context = context )
2013-08-19 13:46:15 +00:00
else :
2014-08-13 10:00:38 +00:00
self . message_post ( cr , uid , [ procurement . id ] , body = _ ( ' No rule matching this procurement ' ) , context = context )
2013-08-19 13:46:15 +00:00
self . write ( cr , uid , [ procurement . id ] , { ' state ' : ' exception ' } , context = context )
2014-08-13 10:00:38 +00:00
if autocommit :
cr . commit ( )
except OperationalError :
if autocommit :
cr . rollback ( )
continue
else :
raise
2010-04-29 13:30:07 +00:00
return True
2014-08-13 10:00:38 +00:00
def check ( self , cr , uid , ids , autocommit = False , context = None ) :
2013-10-09 08:04:52 +00:00
done_ids = [ ]
2013-07-10 10:16:16 +00:00
for procurement in self . browse ( cr , uid , ids , context = context ) :
2014-08-13 10:00:38 +00:00
try :
result = self . _check ( cr , uid , procurement , context = context )
if result :
done_ids . append ( procurement . id )
if autocommit :
cr . commit ( )
except OperationalError :
if autocommit :
cr . rollback ( )
continue
else :
raise
2013-10-09 08:04:52 +00:00
if done_ids :
self . write ( cr , uid , done_ids , { ' state ' : ' done ' } , context = context )
return done_ids
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-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
'''
2013-12-20 09:22:41 +00:00
#if the procurement already has a rule assigned, we keep it (it has a higher priority as it may have been chosen manually)
if procurement . rule_id :
return True
elif procurement . product_id . type != ' service ' :
2013-09-16 09:37:10 +00:00
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
2013-07-10 15:00:42 +00:00
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
2014-02-14 08:46:50 +00:00
: returns : True if the resolution of the procurement was a success , False otherwise to set it in exception
2013-07-10 10:16:16 +00:00
'''
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
#
2014-06-11 12:05:13 +00:00
def run_scheduler ( self , cr , uid , use_new_cursor = False , company_id = False , context = None ) :
2012-05-23 11:04:39 +00:00
'''
2014-04-03 07:10:10 +00:00
Call the scheduler to check the procurement order . This is intented to be done for all existing companies at
the same time , so we ' re running all the methods as SUPERUSER to avoid intercompany and access rights issues.
2013-06-30 22:26:46 +00:00
@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
2014-06-02 16:57:26 +00:00
@param use_new_cursor : if set , use a dedicated cursor and auto - commit after processing each procurement .
This is appropriate for batch jobs only .
2013-06-30 22:26:46 +00:00
@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 :
2014-06-02 16:57:26 +00:00
cr = openerp . registry ( cr . dbname ) . cursor ( )
2013-06-30 22:26:46 +00:00
# Run confirmed procurements
2014-06-11 12:05:13 +00:00
dom = [ ( ' state ' , ' = ' , ' confirmed ' ) ]
if company_id :
dom + = [ ( ' company_id ' , ' = ' , company_id ) ]
2014-08-13 10:00:38 +00:00
prev_ids = [ ]
2013-06-30 22:26:46 +00:00
while True :
2014-06-11 12:05:13 +00:00
ids = self . search ( cr , SUPERUSER_ID , dom , context = context )
2014-08-13 10:00:38 +00:00
if not ids or prev_ids == ids :
2013-07-09 13:22:17 +00:00
break
2014-08-13 10:00:38 +00:00
else :
prev_ids = ids
self . run ( cr , SUPERUSER_ID , ids , autocommit = use_new_cursor , context = context )
2013-06-30 22:26:46 +00:00
if use_new_cursor :
cr . commit ( )
# Check if running procurements are done
offset = 0
2014-06-11 12:05:13 +00:00
dom = [ ( ' state ' , ' = ' , ' running ' ) ]
if company_id :
dom + = [ ( ' company_id ' , ' = ' , company_id ) ]
2014-08-13 10:00:38 +00:00
prev_ids = [ ]
2013-06-30 22:26:46 +00:00
while True :
2014-06-11 12:05:13 +00:00
ids = self . search ( cr , SUPERUSER_ID , dom , offset = offset , context = context )
2014-08-13 10:00:38 +00:00
if not ids or prev_ids == ids :
2013-07-09 13:22:17 +00:00
break
2014-08-13 10:00:38 +00:00
else :
prev_ids = ids
self . check ( cr , SUPERUSER_ID , ids , autocommit = use_new_cursor , context = context )
2013-12-17 15:32:58 +00:00
if use_new_cursor :
2013-06-30 22:26:46 +00:00
cr . commit ( )
finally :
if use_new_cursor :
try :
cr . close ( )
except Exception :
pass
2012-05-23 11:04:39 +00:00
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: