2010-01-12 12:45:02 +00:00
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 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/>.
#
##############################################################################
2010-08-12 07:31:45 +00:00
2011-01-11 05:48:01 +00:00
from datetime import datetime
2010-01-12 12:45:02 +00:00
from tools . translate import _
from osv import fields , osv
2011-10-25 23:31:41 +00:00
from resource . faces import task as Task
2010-02-18 10:07:31 +00:00
2010-01-12 12:45:02 +00:00
class project_phase ( osv . osv ) :
_name = " project.phase "
_description = " Project Phase "
2010-07-02 14:15:50 +00:00
def _check_recursion ( self , cr , uid , ids , context = None ) :
if context is None :
context = { }
2010-03-11 07:10:06 +00:00
data_phase = self . browse ( cr , uid , ids [ 0 ] , context = context )
prev_ids = data_phase . previous_phase_ids
next_ids = data_phase . next_phase_ids
2010-05-27 10:00:31 +00:00
# it should neither be in prev_ids nor in next_ids
2010-03-11 07:10:06 +00:00
if ( data_phase in prev_ids ) or ( data_phase in next_ids ) :
2010-01-18 13:44:53 +00:00
return False
ids = [ id for id in prev_ids if id in next_ids ]
2010-03-09 11:52:35 +00:00
# both prev_ids and next_ids must be unique
2010-01-18 13:44:53 +00:00
if ids :
return False
2010-03-09 11:52:35 +00:00
# unrelated project
2010-01-18 13:44:53 +00:00
prev_ids = [ rec . id for rec in prev_ids ]
next_ids = [ rec . id for rec in next_ids ]
2010-03-09 11:52:35 +00:00
# iter prev_ids
2010-01-18 13:44:53 +00:00
while prev_ids :
2010-07-02 14:15:50 +00:00
cr . execute ( ' SELECT distinct prv_phase_id FROM project_phase_rel WHERE next_phase_id IN %s ' , ( tuple ( prev_ids ) , ) )
2010-01-18 13:44:53 +00:00
prv_phase_ids = filter ( None , map ( lambda x : x [ 0 ] , cr . fetchall ( ) ) )
2010-03-11 07:10:06 +00:00
if data_phase . id in prv_phase_ids :
2010-01-18 13:44:53 +00:00
return False
ids = [ id for id in prv_phase_ids if id in next_ids ]
if ids :
return False
prev_ids = prv_phase_ids
2010-03-09 11:52:35 +00:00
# iter next_ids
2010-01-18 13:44:53 +00:00
while next_ids :
2010-07-02 14:15:50 +00:00
cr . execute ( ' SELECT distinct next_phase_id FROM project_phase_rel WHERE prv_phase_id IN %s ' , ( tuple ( next_ids ) , ) )
2010-01-18 13:44:53 +00:00
next_phase_ids = filter ( None , map ( lambda x : x [ 0 ] , cr . fetchall ( ) ) )
2010-03-11 07:10:06 +00:00
if data_phase . id in next_phase_ids :
2010-01-18 13:44:53 +00:00
return False
ids = [ id for id in next_phase_ids if id in prev_ids ]
if ids :
return False
next_ids = next_phase_ids
return True
2010-01-12 12:45:02 +00:00
2010-07-02 14:15:50 +00:00
def _check_dates ( self , cr , uid , ids , context = None ) :
2010-04-06 16:15:10 +00:00
for phase in self . read ( cr , uid , ids , [ ' date_start ' , ' date_end ' ] , context = context ) :
if phase [ ' date_start ' ] and phase [ ' date_end ' ] and phase [ ' date_start ' ] > phase [ ' date_end ' ] :
return False
2010-02-26 11:18:14 +00:00
return True
2010-07-08 12:53:59 +00:00
def _get_default_uom_id ( self , cr , uid ) :
model_data_obj = self . pool . get ( ' ir.model.data ' )
model_data_id = model_data_obj . _get_id ( cr , uid , ' product ' , ' uom_hour ' )
return model_data_obj . read ( cr , uid , [ model_data_id ] , [ ' res_id ' ] ) [ 0 ] [ ' res_id ' ]
2010-10-12 13:21:51 +00:00
2011-10-25 23:31:41 +00:00
def _compute_progress ( self , cr , uid , ids , field_name , arg , context = None ) :
2010-09-29 15:07:02 +00:00
res = { }
if not ids :
return res
for phase in self . browse ( cr , uid , ids , context = context ) :
2011-10-25 23:31:41 +00:00
if phase . state == ' done ' :
res [ phase . id ] = 100.0
continue
elif phase . state == " cancelled " :
res [ phase . id ] = 0.0
continue
elif not phase . task_ids :
res [ phase . id ] = 0.0
continue
tot = done = 0.0
2010-09-29 15:07:02 +00:00
for task in phase . task_ids :
2011-10-25 23:31:41 +00:00
tot + = task . total_hours
done + = min ( task . effective_hours , task . total_hours )
if not tot :
res [ phase . id ] = 0.0
else :
res [ phase . id ] = round ( 100.0 * done / tot , 2 )
2010-09-29 15:07:02 +00:00
return res
2010-07-08 12:53:59 +00:00
2010-01-12 12:45:02 +00:00
_columns = {
2010-05-21 07:14:29 +00:00
' name ' : fields . char ( " Name " , size = 64 , required = True ) ,
2011-10-25 23:31:41 +00:00
' date_start ' : fields . datetime ( ' Start Date ' , help = " It ' s computed by the scheduler according the project date or the end date of the previous phase. " , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
' date_end ' : fields . datetime ( ' End Date ' , help = " It ' s computed by the scheduler according to the start date and the duration. " , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
' constraint_date_start ' : fields . datetime ( ' Minimum Start Date ' , help = ' force the phase to start after this date ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
' constraint_date_end ' : fields . datetime ( ' Deadline ' , help = ' force the phase to finish before this date ' , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
2010-01-12 12:45:02 +00:00
' project_id ' : fields . many2one ( ' project.project ' , ' Project ' , required = True ) ,
2010-07-29 05:31:04 +00:00
' next_phase_ids ' : fields . many2many ( ' project.phase ' , ' project_phase_rel ' , ' prv_phase_id ' , ' next_phase_id ' , ' Next Phases ' , states = { ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
' previous_phase_ids ' : fields . many2many ( ' project.phase ' , ' project_phase_rel ' , ' next_phase_id ' , ' prv_phase_id ' , ' Previous Phases ' , states = { ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
2010-02-25 06:01:27 +00:00
' sequence ' : fields . integer ( ' Sequence ' , help = " Gives the sequence order when displaying a list of phases. " ) ,
2010-07-29 05:31:04 +00:00
' duration ' : fields . float ( ' Duration ' , required = True , help = " By default in days " , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
' product_uom ' : fields . many2one ( ' product.uom ' , ' Duration UoM ' , required = True , help = " UoM (Unit of Measure) is the unit of measurement for Duration " , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
' task_ids ' : fields . one2many ( ' project.task ' , ' phase_id ' , " Project Tasks " , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ) ,
2011-10-25 23:31:41 +00:00
' user_force_ids ' : fields . many2many ( ' res.users ' , string = ' Force Assigned Users ' ) ,
' user_ids ' : fields . one2many ( ' project.user.allocation ' , ' phase_id ' , " Assigned Users " , states = { ' done ' : [ ( ' readonly ' , True ) ] , ' cancelled ' : [ ( ' readonly ' , True ) ] } ,
help = " The ressources on the project can be computed automatically by the scheduler " ) ,
2011-09-15 05:45:57 +00:00
' state ' : fields . selection ( [ ( ' draft ' , ' New ' ) , ( ' open ' , ' In Progress ' ) , ( ' pending ' , ' Pending ' ) , ( ' cancelled ' , ' Cancelled ' ) , ( ' done ' , ' Done ' ) ] , ' State ' , readonly = True , required = True ,
2010-02-25 06:01:27 +00:00
help = ' If the phase is created the state \' Draft \' . \n If the phase is started, the state becomes \' In Progress \' . \n If review is needed the phase is in \' Pending \' state. \
2010-09-29 15:07:02 +00:00
\n If the phase is over , the states is set to \' Done \' . ' ) ,
2011-10-25 23:31:41 +00:00
' progress ' : fields . function ( _compute_progress , string = ' Progress ' , help = " Computed based on related tasks " ) ,
2010-01-12 12:45:02 +00:00
}
_defaults = {
2010-05-14 09:15:02 +00:00
' state ' : ' draft ' ,
' sequence ' : 10 ,
2010-07-14 09:04:50 +00:00
' product_uom ' : lambda self , cr , uid , c : self . pool . get ( ' product.uom ' ) . search ( cr , uid , [ ( ' name ' , ' = ' , _ ( ' Day ' ) ) ] , context = c ) [ 0 ]
2010-01-12 12:45:02 +00:00
}
2011-10-25 23:31:41 +00:00
_order = " project_id, date_start, sequence "
2010-02-26 11:18:14 +00:00
_constraints = [
2010-04-06 16:15:10 +00:00
( _check_recursion , ' Loops in phases not allowed ' , [ ' next_phase_ids ' , ' previous_phase_ids ' ] ) ,
( _check_dates , ' Phase start-date must be lower than phase end-date. ' , [ ' date_start ' , ' date_end ' ] ) ,
2010-02-26 11:18:14 +00:00
]
2010-07-02 14:15:50 +00:00
def onchange_project ( self , cr , uid , ids , project , context = None ) :
2011-10-25 23:31:41 +00:00
return { }
2010-02-26 11:18:14 +00:00
2010-09-18 05:52:11 +00:00
def copy ( self , cr , uid , id , default = None , context = None ) :
if default is None :
default = { }
if not default . get ( ' name ' , False ) :
default [ ' name ' ] = self . browse ( cr , uid , id , context = context ) . name + _ ( ' (copy) ' )
return super ( project_phase , self ) . copy ( cr , uid , id , default , context )
2010-02-25 06:01:27 +00:00
2010-03-09 11:52:35 +00:00
def set_draft ( self , cr , uid , ids , * args ) :
2010-02-25 06:01:27 +00:00
self . write ( cr , uid , ids , { ' state ' : ' draft ' } )
return True
2010-03-09 11:52:35 +00:00
def set_open ( self , cr , uid , ids , * args ) :
2010-02-25 06:01:27 +00:00
self . write ( cr , uid , ids , { ' state ' : ' open ' } )
return True
2010-07-02 14:15:50 +00:00
def set_pending ( self , cr , uid , ids , * args ) :
2010-02-25 06:01:27 +00:00
self . write ( cr , uid , ids , { ' state ' : ' pending ' } )
return True
2010-03-09 11:52:35 +00:00
def set_cancel ( self , cr , uid , ids , * args ) :
2010-02-25 06:01:27 +00:00
self . write ( cr , uid , ids , { ' state ' : ' cancelled ' } )
return True
2010-03-09 11:52:35 +00:00
def set_done ( self , cr , uid , ids , * args ) :
2010-02-25 06:01:27 +00:00
self . write ( cr , uid , ids , { ' state ' : ' done ' } )
return True
2010-02-18 10:07:31 +00:00
2011-10-25 23:31:41 +00:00
def generate_phase ( self , cr , uid , phases , context = None ) :
context = context or { }
result = " "
2011-01-05 09:56:17 +00:00
task_pool = self . pool . get ( ' project.task ' )
2011-10-25 23:31:41 +00:00
for phase in phases :
if phase . state in ( ' done ' , ' cancelled ' ) :
continue
duration_uom = {
' days ' : ' d ' , ' day ' : ' d ' , ' d ' : ' d ' ,
' months ' : ' m ' , ' month ' : ' month ' , ' m ' : ' m ' ,
' weeks ' : ' w ' , ' week ' : ' w ' , ' w ' : ' w ' ,
' hours ' : ' H ' , ' hour ' : ' H ' , ' h ' : ' H ' ,
} . get ( phase . product_uom . name . lower ( ) , " h " )
duration = str ( phase . duration ) + duration_uom
result + = '''
2011-01-04 10:02:05 +00:00
def Phase_ % s ( ) :
2011-10-26 00:16:18 +00:00
effort = \" %s \" ' ' ' % (phase.id, duration)
2011-10-25 23:31:41 +00:00
start = [ ]
if phase . constraint_date_start :
start . append ( ' datetime.datetime.strptime( " ' + str ( phase . constraint_date_start ) + ' " , " % Y- % m- %d % H: % M: % S " ) ' )
for previous_phase in phase . previous_phase_ids :
start . append ( " up.Phase_ %s .end " % ( previous_phase . id , ) )
if start :
result + = '''
start = max ( % s )
''' % ( ' , ' .join(start))
if phase . user_force_ids :
result + = '''
2011-01-04 10:02:05 +00:00
resource = % s
2011-10-25 23:31:41 +00:00
''' % ' | ' .join(map(lambda x: ' User_ ' +str(x.id), phase.user_force_ids))
2011-01-06 14:45:40 +00:00
2011-10-25 23:31:41 +00:00
result + = task_pool . _generate_task ( cr , uid , phase . task_ids , ident = 8 , context = context )
result + = " \n "
2011-01-04 10:02:05 +00:00
2011-10-25 23:31:41 +00:00
return result
2010-01-12 12:45:02 +00:00
project_phase ( )
2011-10-25 23:31:41 +00:00
class project_user_allocation ( osv . osv ) :
_name = ' project.user.allocation '
_description = ' Phase User Allocation '
_rec_name = ' user_id '
2010-01-12 12:45:02 +00:00
_columns = {
2011-10-25 23:31:41 +00:00
' user_id ' : fields . many2one ( ' res.users ' , ' User ' , required = True ) ,
2010-07-25 15:55:48 +00:00
' phase_id ' : fields . many2one ( ' project.phase ' , ' Project Phase ' , ondelete = ' cascade ' , required = True ) ,
2010-09-30 14:41:19 +00:00
' project_id ' : fields . related ( ' phase_id ' , ' project_id ' , type = ' many2one ' , relation = " project.project " , string = ' Project ' , store = True ) ,
2011-10-25 23:31:41 +00:00
' date_start ' : fields . datetime ( ' Start Date ' , help = " Starting Date " ) ,
' date_end ' : fields . datetime ( ' End Date ' , help = " Ending Date " ) ,
2010-01-12 12:45:02 +00:00
}
2011-10-25 23:31:41 +00:00
project_user_allocation ( )
2010-01-12 12:45:02 +00:00
class project ( osv . osv ) :
_inherit = " project.project "
_columns = {
2010-03-03 07:53:14 +00:00
' phase_ids ' : fields . one2many ( ' project.phase ' , ' project_id ' , " Project Phases " ) ,
2010-01-12 12:45:02 +00:00
}
2011-10-25 23:31:41 +00:00
def schedule_phases ( self , cr , uid , ids , context = None ) :
context = context or { }
if type ( ids ) in ( long , int , ) :
ids = [ ids ]
projects = self . browse ( cr , uid , ids , context = context )
result = self . _schedule_header ( cr , uid , ids , context = context )
for project in projects :
result + = self . _schedule_project ( cr , uid , project , context = context )
result + = self . pool . get ( ' project.phase ' ) . generate_phase ( cr , uid , project . phase_ids , context = context )
local_dict = { }
exec result in local_dict
projects_gantt = Task . BalancedProject ( local_dict [ ' Project ' ] )
for project in projects :
project_gantt = getattr ( projects_gantt , ' Project_ %d ' % ( project . id , ) )
for phase in project . phase_ids :
if phase . state in ( ' done ' , ' cancelled ' ) :
continue
# Maybe it's better to update than unlink/create if it already exists ?
p = getattr ( project_gantt , ' Phase_ %d ' % ( phase . id , ) )
self . pool . get ( ' project.user.allocation ' ) . unlink ( cr , uid ,
[ x . id for x in phase . user_ids ] ,
context = context
)
for r in p . booked_resource :
self . pool . get ( ' project.user.allocation ' ) . create ( cr , uid , {
' user_id ' : int ( r . name [ 5 : ] ) ,
' phase_id ' : phase . id ,
' date_start ' : p . start . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' date_end ' : p . end . strftime ( ' % Y- % m- %d % H: % M: % S ' )
} , context = context )
self . pool . get ( ' project.phase ' ) . write ( cr , uid , [ phase . id ] , {
' date_start ' : p . start . strftime ( ' % Y- % m- %d % H: % M: % S ' ) ,
' date_end ' : p . end . strftime ( ' % Y- % m- %d % H: % M: % S ' )
} , context = context )
return True
2010-01-12 12:45:02 +00:00
project ( )
2010-09-24 09:26:14 +00:00
class project_task ( osv . osv ) :
2010-01-12 12:45:02 +00:00
_inherit = " project.task "
_columns = {
2010-03-02 13:27:02 +00:00
' phase_id ' : fields . many2one ( ' project.phase ' , ' Project Phase ' ) ,
2010-01-12 12:45:02 +00:00
}
2010-09-24 09:26:14 +00:00
project_task ( )
2011-11-22 08:51:38 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: