Project management improvements

bzr revid: hda@tinyerp.com-20100226132006-lomo55f42zug2o1n
This commit is contained in:
HDA (OpenERP) 2010-02-26 18:50:06 +05:30
commit 3f969c76a6
16 changed files with 783 additions and 389 deletions

View File

@ -10,6 +10,7 @@
<record id="employee1" model="hr.employee">
<field name="name">Fabien Pinckaers</field>
<field name="calendar_id" ref="timesheet_group1"/>
<!--<field name="regime">45</field>-->
<field name="user_id" ref="base.user_root"/>
<!--<field name="holiday_max">25</field>-->

View File

@ -256,17 +256,23 @@ class task(osv.osv):
res[task.id]['delay_hours'] = res[task.id]['total_hours'] - task.planned_hours
return res
def onchange_planned(self, cr, uid, ids, planned=0.0, effective=0.0, date_start=None,occupation_rate=0.0):
def onchange_planned(self, cr, uid, ids, project, user_id = False,planned=0.0, effective=0.0,date_start=None,occupation_rate=0.0):
result = {}
for res in self.browse(cr, uid, ids):
if date_start and planned:
resource_id = self.pool.get('resource.resource').search(cr,uid,[('user_id','=',res.user_id.id)])
if resource_id:
resource_obj = self.pool.get('resource.resource').browse(cr,uid,resource_id)[0]
d = mx.DateTime.strptime(date_start,'%Y-%m-%d %H:%M:%S')
hrs = (planned)/(occupation_rate)
work_times = self.pool.get('resource.calendar').interval_get(cr, uid, resource_obj.calendar_id.id or False, d, hrs or 0.0, resource_obj.id)
result['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
if date_start:
resource_pool = self.pool.get('resource.resource')
project_pool = self.pool.get('project.project')
resource_calendar = self.pool.get('resource.calendar')
dt_start = mx.DateTime.strptime(date_start,'%Y-%m-%d %H:%M:%S')
resource_id = resource_pool.search(cr,uid,[('user_id','=',user_id)])
if resource_id:
resource_obj = resource_pool.browse(cr,uid,resource_id)[0]
hrs = planned/(float(occupation_rate) * resource_obj.time_efficiency)
calendar_id = resource_obj.calendar_id.id
else:
hrs = float(planned / occupation_rate)
calendar_id = project_pool.browse(cr,uid,project).resource_calendar_id .id
work_times = resource_calendar.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0,resource_id or False)
result['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
result['remaining_hours'] = planned-effective
return {'value':result}
@ -473,6 +479,83 @@ class task(osv.osv):
self.write(cr, uid, typ.id, {'type': index and types[index-1] or False})
return True
def constraint_date_start(self,cr,uid,task,date_end,context=None):
# Recursive call for all previous tasks if change in date_start < older time
resource_cal_pool = self.pool.get('resource.calendar')
resource_pool = self.pool.get('resource.resource')
calendar_id = task.project_id.resource_calendar_id.id
hours = task.remaining_hours / task.occupation_rate
resource_id = resource_pool.search(cr,uid,[('user_id','=',task.user_id.id)])
if resource_id:
resource_obj = resource_pool.browse(cr,uid,resource_id[0])
calendar_id = resource_obj.calendar_id.id
hours = task.planned_hours/(float(task.occupation_rate) * resource_obj.time_efficiency)
work_time = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, date_end, hours or 0.0,resource_id or False)
dt_start = work_time[0][0].strftime('%Y-%m-%d %H:%M:%S')
self.write(cr,uid,[task.id],{'date_start':dt_start,'date_end':date_end.strftime('%Y-%m-%d %H:%M:%S')})
def constraint_date_end(self,cr,uid,task,date_start,context=None):
# Recursive call for all next tasks if change in date_end > older time
resource_cal_pool = self.pool.get('resource.calendar')
resource_pool = self.pool.get('resource.resource')
calendar_id = task.project_id.resource_calendar_id.id
hours = task.remaining_hours / task.occupation_rate
resource_id = resource_pool.search(cr,uid,[('user_id','=',task.user_id.id)])
if resource_id:
resource_obj = resource_pool.browse(cr,uid,resource_id[0])
calendar_id = resource_obj.calendar_id.id
hours = task.planned_hours/(float(task.occupation_rate) * resource_obj.time_efficiency)
work_time = resource_cal_pool.interval_get(cr, uid, calendar_id or False, date_start, hours or 0.0,resource_id or False)
dt_end = work_time[-1][1].strftime('%Y-%m-%d %H:%M:%S')
self.write(cr,uid,[task.id],{'date_start':date_start.strftime('%Y-%m-%d %H:%M:%S'),'date_end':dt_end})
def write(self, cr, uid, ids, vals,context=None):
if not context:
context = {}
if context.get('scheduler',False):
return super(task, self).write(cr, uid, ids, vals, context=context)
# if the task is performed by a resource then its calendar and efficiency also taken
# otherwise the project's working calendar considered
tasks = self.browse(cr,uid,ids[0])
resource_cal_pool = self.pool.get('resource.calendar')
resource_pool = self.pool.get('resource.resource')
calendar_id = tasks.project_id.resource_calendar_id.id
hrs = tasks.remaining_hours / tasks.occupation_rate
resource_id = resource_pool.search(cr,uid,[('user_id','=',tasks.user_id.id)])
if resource_id:
resource_obj = resource_pool.browse(cr,uid,resource_id[0])
calendar_id = resource_obj.calendar_id.id
hrs = tasks.planned_hours/(float(tasks.occupation_rate) * resource_obj.time_efficiency)
# write method changes the date_start and date_end
# for previous and next tasks respectively based on valid condition
if vals.get('date_start'):
if vals['date_start'] < tasks.date_start:
dt_start = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S')
work_times = resource_cal_pool.interval_get(cr, uid, calendar_id or False, dt_start, hrs or 0.0,resource_id or False)
vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
super(task, self).write(cr, uid, ids, vals, context=context)
for prv_task in tasks.parent_ids:
self.constraint_date_start(cr,uid,prv_task,dt_start)
if vals.get('date_end'):
if vals['date_end'] > tasks.date_end:
dt_end = mx.DateTime.strptime(vals['date_end'],'%Y-%m-%d %H:%M:%S')
hrs = tasks.remaining_hours / tasks.occupation_rate
work_times = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, dt_end, hrs or 0.0,resource_id or False)
vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
super(task, self).write(cr, uid, ids, vals, context=context)
for next_task in tasks.child_ids:
self.constraint_date_end(cr,uid,next_task,dt_end)
return super(task, self).write(cr, uid, ids, vals, context=context)
task()
class project_work(osv.osv):

View File

@ -0,0 +1,81 @@
import pooler
import datetime
def timeformat_convert(cr, uid, time_string, context={}):
# Function to convert input time string:: 8.5 to output time string 8:30
split_list = str(time_string).split('.')
hour_part = split_list[0]
mins_part = split_list[1]
round_mins = int(round(float(mins_part) * 60,-2))
converted_string = hour_part + ':' + str(round_mins)[0:2]
return converted_string
def leaves_resource(cr,uid,calendar_id,resource_id=False,resource_calendar=False):
# To get the leaves for the resource_ids working on phase
pool = pooler.get_pool(cr.dbname)
resource_leaves_pool = pool.get('resource.calendar.leaves')
leaves = []
if resource_id:
resource_leave_ids = resource_leaves_pool.search(cr,uid,['|',('calendar_id','=',calendar_id),('calendar_id','=',resource_calendar),('resource_id','=',resource_id)])
else:
resource_leave_ids = resource_leaves_pool.search(cr,uid,[('calendar_id','=',calendar_id),('resource_id','=',False)])
res_leaves = resource_leaves_pool.read(cr,uid,resource_leave_ids,['date_from','date_to'])
for leave in range(len(res_leaves)):
dt_start = datetime.datetime.strptime(res_leaves[leave]['date_from'],'%Y-%m-%d %H:%M:%S')
dt_end = datetime.datetime.strptime(res_leaves[leave]['date_to'],'%Y-%m-%d %H:%M:%S')
no = dt_end - dt_start
[leaves.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
leaves.sort()
return leaves
def compute_working_calendar(cr,uid,calendar_id):
# To change the format of working calendar to bring it into 'faces' format
pool = pooler.get_pool(cr.dbname)
resource_week_pool = pool.get('resource.calendar.week')
time_range = "8:00-8:00"
non_working = ""
wk = {"0":"mon","1":"tue","2":"wed","3":"thu","4":"fri","5":"sat","6":"sun"}
wk_days = {}
wk_time = {}
wktime_list = []
wktime_cal = []
week_ids = resource_week_pool.search(cr,uid,[('calendar_id','=',calendar_id)])
week_obj = resource_week_pool.read(cr,uid,week_ids,['dayofweek','hour_from','hour_to'])
# Converting time formats into appropriate format required
# and creating a list like [('mon', '8:00-12:00'), ('mon', '13:00-18:00')]
for week in week_obj:
res_str = ""
if wk.has_key(week['dayofweek']):
day = wk[week['dayofweek']]
wk_days[week['dayofweek']] = wk[week['dayofweek']]
hour_from_str = timeformat_convert(cr,uid,week['hour_from'])
hour_to_str = timeformat_convert(cr,uid,week['hour_to'])
res_str = hour_from_str + '-' + hour_to_str
wktime_list.append((day,res_str))
# Converting it to format like [('mon', '8:00-12:00', '13:00-18:00')]
for item in wktime_list:
if wk_time.has_key(item[0]):
wk_time[item[0]].append(item[1])
else:
wk_time[item[0]] = [item[0]]
wk_time[item[0]].append(item[1])
for k,v in wk_time.items():
wktime_cal.append(tuple(v))
# For non working days adding [('tue,wed,fri,sat,sun', '8:00-8:00')]
for k,v in wk_days.items():
if wk.has_key(k):
wk.pop(k)
for v in wk.itervalues():
non_working += v + ','
if non_working:
wktime_cal.append((non_working[:-1],time_range))
return wktime_cal

View File

@ -247,7 +247,7 @@
name="planned_hours"
widget="float_time"
attrs="{'readonly':[('state','!=','draft')]}"
on_change="onchange_planned(planned_hours,effective_hours,date_start,occupation_rate)"/>
on_change="onchange_planned(project_id,user_id,planned_hours,effective_hours,date_start,occupation_rate)"/>
<field name="remaining_hours" select="2" widget="float_time" attrs="{'readonly':[('state','!=','draft')]}" colspan="2"/>
<button name="%(action_config_compute_remaining)d" string="Review" type="action" colspan="1" target="new" states="open,pending" icon="gtk-edit"/>
</group>

View File

@ -26,12 +26,11 @@ import datetime
from resource.faces import *
from new import classobj
import operator
import time
import project.project_resource as proj
compute_form = """<?xml version="1.0" ?>
<form string="Compute Scheduling of Tasks">
<field name="project_id" colspan="4"/>
<field name= "date_from" colspan="4"/>
</form>"""
success_msg = """<?xml version="1.0" ?>
@ -41,132 +40,39 @@ success_msg = """<?xml version="1.0" ?>
compute_fields = {
'project_id': {'string':'Project', 'type':'many2one', 'relation': 'project.project', 'required':'True'},
'date_from': {'string':"Start date",'type':'datetime','required':'True' ,'default': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S')},
}
def timeformat_convert(cr, uid, time_string, context={}):
# Function to convert input time string:: 8.5 to output time string 8:30
split_list = str(time_string).split('.')
hour_part = split_list[0]
mins_part = split_list[1]
round_mins = int(round(float(mins_part) * 60,-2))
converted_string = hour_part + ':' + str(round_mins)[0:2]
return converted_string
def leaves_resource(cr,uid,id):
# To get the leaves for the members working on project
pool = pooler.get_pool(cr.dbname)
resource_leaves_pool = pool.get('resource.calendar.leaves')
resource_leave_ids = resource_leaves_pool.search(cr,uid,[('resource_id','=',id)])
leaves = []
if resource_leave_ids:
res_leaves = resource_leaves_pool.read(cr,uid,resource_leave_ids,['date_from','date_to'])
for leave in range(len(res_leaves)):
dt_start = datetime.datetime.strptime(res_leaves[leave]['date_from'],'%Y-%m-%d %H:%M:%S')
dt_end = datetime.datetime.strptime(res_leaves[leave]['date_to'],'%Y-%m-%d %H:%M:%S')
no = dt_end - dt_start
leave_days = no.days + 1
[leaves.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(leave_days))]
leaves.sort()
return leaves
class wizard_compute_tasks(wizard.interface):
def _compute_date(self, cr, uid, data, context):
pool = pooler.get_pool(cr.dbname)
project_pool = pool.get('project.project')
task_pool = pool.get('project.task')
resource_pool = pool.get('resource.resource')
resource_leaves_pool = pool.get('resource.calendar.leaves')
resource_week_pool = pool.get('resource.calendar.week')
user_pool = pool.get('res.users')
project_id = data['form']['project_id']
project = project_pool.browse(cr,uid,project_id)
task_ids = task_pool.search(cr,uid,[('project_id','=',project_id)])
task_ids = task_pool.search(cr,uid,[('project_id','=',project_id),('state','in',['draft','open','pending'])])
if task_ids:
wktime_cal = []
leaves = []
task_ids.sort()
task_obj = task_pool.browse(cr,uid,task_ids)
task_1 = task_obj[0]
date_start = datetime.datetime.strftime(datetime.datetime.strptime(data['form']['date_from'],"%Y-%m-%d %H:%M:%S"),"%Y-%m-%d %H:%M")
calendar_id = project.resource_calendar_id.id
# If project has a working calendar then that would be used otherwise
# the default faces calendar would be used
if calendar_id:
resource_leave_ids = resource_leaves_pool.search(cr,uid,[('calendar_id','=',calendar_id)])
time_range = "8:00-8:00"
non_working = ""
wk = {"0":"mon","1":"tue","2":"wed","3":"thu","4":"fri","5":"sat","6":"sun"}
wk_days = {}
wk_time = {}
wktime_list = []
week_ids = resource_week_pool.search(cr,uid,[('calendar_id','=',calendar_id)])
week_obj = resource_week_pool.read(cr,uid,week_ids,['dayofweek','hour_from','hour_to'])
# Converting time formats into appropriate format required
# and creating a list like [('mon', '8:00-12:00'), ('mon', '13:00-18:00')]
for week in week_obj:
res_str = ""
if wk.has_key(week['dayofweek']):
day = wk[week['dayofweek']]
wk_days[week['dayofweek']] = wk[week['dayofweek']]
hour_from_str = timeformat_convert(cr,uid,week['hour_from'])
hour_to_str = timeformat_convert(cr,uid,week['hour_to'])
res_str = hour_from_str + '-' + hour_to_str
wktime_list.append((day,res_str))
# Converting it to format like [('mon', '8:00-12:00', '13:00-18:00')]
for item in wktime_list:
if wk_time.has_key(item[0]):
wk_time[item[0]].append(item[1])
else:
wk_time[item[0]] = [item[0]]
wk_time[item[0]].append(item[1])
for k,v in wk_time.items():
wktime_cal.append(tuple(v))
# For non working days adding [('tue,wed,fri,sat,sun', '8:00-8:00')]
for k,v in wk_days.items():
if wk.has_key(k):
wk.pop(k)
for v in wk.itervalues():
non_working += v + ','
if non_working:
wktime_cal.append((non_working[:-1],time_range))
# If project working calendar has any leaves
if resource_leave_ids:
res_leaves = resource_leaves_pool.read(cr,uid,resource_leave_ids,['date_from','date_to'])
for leave in range(len(res_leaves)):
dt_start = datetime.datetime.strptime(res_leaves[leave]['date_from'],'%Y-%m-%d %H:%M:%S')
dt_end = datetime.datetime.strptime(res_leaves[leave]['date_to'],'%Y-%m-%d %H:%M:%S')
no = dt_end - dt_start
leave_days = no.days + 1
[leaves.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(leave_days))]
leaves.sort()
start_date = project.date_start
if not project.date_start:
start_date = datetime.datetime.now().strftime("%Y-%m-%d")
date_start = datetime.datetime.strftime(datetime.datetime.strptime(start_date,"%Y-%m-%d"),"%Y-%m-%d %H:%M")
# To create resources which are the Project Members
resource = project.members
resource_objs = []
for no in range(len(resource)):
for resource in project.members:
leaves = []
resource_eff = 1.00
resource_id = resource_pool.search(cr,uid,[('user_id','=',resource[no].id)])
resource_id = resource_pool.search(cr,uid,[('user_id','=',resource.id)])
if resource_id:
# Getting the efficiency for specific resource
resource_eff = resource_pool.browse(cr,uid,resource_id)[0].time_efficiency
resource_obj = resource_pool.browse(cr,uid,resource_id)[0]
leaves = proj.leaves_resource(cr,uid,calendar_id or False ,resource_id,resource_obj.calendar_id.id)
resource_objs.append(classobj(str(resource.name),(Resource,),{'__doc__':resource.name,'__name__':resource.name,'vacation':tuple(leaves),'efficiency':resource_obj.time_efficiency}))
# Getting list of leaves for specific resource
if wktime_cal:
leaves = leaves_resource(cr,uid,resource_id)
resource_objs.append(classobj(str(resource[no].name),(Resource,),{'__doc__':resource[no].name,'__name__':resource[no].name,'vacation':tuple(leaves),'efficiency':resource_eff}))
priority_dict = {'0':1000,'1':800,'2':500,'3':300,'4':100}
# To create dynamic no of tasks with the resource specified
@ -191,22 +97,25 @@ class wizard_compute_tasks(wizard.interface):
minimum_time_unit = 1
# If project has calendar
if wktime_cal:
working_days = wktime_cal
vacation = tuple(leaves)
if calendar_id:
working_days = proj.compute_working_calendar(cr,uid,calendar_id)
vacation = tuple(proj.leaves_resource(cr,uid,calendar_id))
# Dynamic Creation of tasks
for i in range(len(task_obj)):
hours = str(task_obj[i].remaining_hours / task_obj[i].occupation_rate)+ 'H'
if task_obj[i].priority in priority_dict.keys():
priorty = priority_dict[task_obj[i].priority]
if task_obj[i].user_id:
i = 0
for each_task in task_obj:
hours = str(each_task.planned_hours / each_task.occupation_rate)+ 'H'
if each_task.priority in priority_dict.keys():
priorty = priority_dict[each_task.priority]
if each_task.user_id:
for resource_object in resource_objs:
if resource_object.__name__ == task_obj[i].user_id.name:
if resource_object.__name__ == each_task.user_id.name:
task = tasks_resource(i,hours,priorty,resource_object)
else:
task = tasks_resource(i,hours,priorty)
i += 1
# Writing back the dates
project = BalancedProject(Project)
loop_no = 0
for t in project:
@ -216,9 +125,8 @@ class wizard_compute_tasks(wizard.interface):
project_pool.write(cr,uid,[project_id],{'date':e_date})
else:
user_id = user_pool.search(cr,uid,[('name','=',t.booked_resource[0].__name__)])
task_pool.write(cr,uid,[task_obj[loop_no-1].id],{'date_start':s_date,'date_deadline':e_date,'user_id':user_id[0]})
task_pool.write(cr,uid,[task_obj[loop_no-1].id],{'date_start':s_date.strftime('%Y-%m-%d %H:%M:%S'),'date_deadline':e_date.strftime('%Y-%m-%d %H:%M:%S'),'user_id':user_id[0]})
loop_no +=1
return {}
states = {

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
@ -15,7 +15,7 @@
# 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/>.
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
@ -32,9 +32,10 @@
"init_xml" : [],
"demo_xml" : [#"project_demo.xml"
],
"update_xml": [
"update_xml": [
"project_wizard.xml" ,
"project_view.xml",
"project_wizard.xml"
"project_phase_workflow.xml"
],
'installable': True,
'active': False,

View File

@ -20,18 +20,17 @@
##############################################################################
from lxml import etree
from mx import DateTime
from mx.DateTime import now
import mx.DateTime
import time
from tools.translate import _
from osv import fields, osv
from tools.translate import _
class project_phase(osv.osv):
_name = "project.phase"
_description = "Project Phase"
def _check_recursion(self,cr,uid,ids):
obj_self = self.browse(cr, uid, ids[0])
prev_ids = obj_self.previous_phase_ids
@ -51,7 +50,7 @@ class project_phase(osv.osv):
#iter prev_ids
while prev_ids:
cr.execute('select distinct prv_phase_id from project_phase_previous_rel where phase_id in ('+','.join(map(str, prev_ids))+')')
cr.execute('select distinct prv_phase_id from project_phase_rel where next_phase_id in ('+','.join(map(str, prev_ids))+')')
prv_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
if obj_self.id in prv_phase_ids:
return False
@ -62,7 +61,7 @@ class project_phase(osv.osv):
#iter next_ids
while next_ids:
cr.execute('select distinct next_phase_id from project_phase_next_rel where phase_id in ('+','.join(map(str, next_ids))+')')
cr.execute('select distinct next_phase_id from project_phase_rel where prv_phase_id in ('+','.join(map(str, next_ids))+')')
next_phase_ids = filter(None, map(lambda x: x[0], cr.fetchall()))
if obj_self.id in next_phase_ids:
return False
@ -72,6 +71,14 @@ class project_phase(osv.osv):
next_ids = next_phase_ids
return True
def _check_dates(self, cr, uid, ids):
phase = self.read(cr, uid, ids[0],['date_start','date_end'])
if phase['date_start'] and phase['date_end']:
if phase['date_start'] > phase['date_end']:
return False
return True
_columns = {
'name': fields.char("Phase Name", size=64, required=True),
'date_start': fields.datetime('Starting Date'),
@ -79,22 +86,143 @@ class project_phase(osv.osv):
'constraint_date_start': fields.datetime('Constraint Starting Date'),
'constraint_date_end': fields.datetime('Constraint End Date'),
'project_id': fields.many2one('project.project', 'Project', required=True),
'next_phase_ids': fields.many2many('project.phase', 'project_phase_next_rel', 'phase_id', 'next_phase_id', 'Next Phases'),
'previous_phase_ids': fields.many2many('project.phase', 'project_phase_previous_rel', 'phase_id', 'prv_phase_id', 'Previous Phases'),
'duration': fields.float('Duration'),
'product_uom': fields.many2one('product.uom', 'Duration UoM', help="UoM (Unit of Measure) is the unit of measurement for Duration"),
'next_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'prv_phase_id', 'next_phase_id', 'Next Phases'),
'previous_phase_ids': fields.many2many('project.phase', 'project_phase_rel', 'next_phase_id', 'prv_phase_id', 'Previous Phases'),
'sequence': fields.integer('Sequence', help="Gives the sequence order when displaying a list of phases."),
'duration': fields.float('Duration',required=True),
'product_uom': fields.many2one('product.uom', 'Duration UoM',required=True, help="UoM (Unit of Measure) is the unit of measurement for Duration"),
'task_ids': fields.one2many('project.task', 'phase_id', "Project Tasks"),
'resource_ids': fields.one2many('project.resource.allocation', 'phase_id', "Project Resources"),
'responsible_id':fields.many2one('res.users', 'Responsible'),
'state': fields.selection([('draft', 'Draft'),('open', 'In Progress'),('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')], 'State', readonly=True, required=True,
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.\
\n If the phase is over, the states is set to \'Done\'.')
}
_defaults = {
'responsible_id': lambda obj,cr,uid,context: uid,
'date_start': lambda *a: time.strftime('%Y-%m-%d'),
'state': lambda *a: 'draft',
'sequence': lambda *a: 10,
}
_order = "name"
_constraints = [
(_check_recursion,'Error ! Loops In Phases Not Allowed',['next_phase_ids','previous_phase_ids'])
(_check_recursion,'Error ! Loops In Phases Not Allowed',['next_phase_ids','previous_phase_ids']),
(_check_dates, 'Error! Phase start-date must be lower then Phase end-date.', ['date_start', 'date_end'])
]
def onchange_project(self,cr,uid,ids,project):
result = {}
if project:
project_pool = self.pool.get('project.project')
project_id = project_pool.browse(cr,uid,project)
if project_id.date_start:
result['date_start'] = mx.DateTime.strptime(project_id.date_start,"%Y-%m-%d").strftime('%Y-%m-%d %H:%M:%S')
return {'value':result}
return {'value':{'date_start':[]}}
def constraint_date_start(self,cr,uid,phase,date_end,context=None):
# Recursive call for all previous phases if change in date_start < older time
resource_cal_pool = self.pool.get('resource.calendar')
uom_pool = self.pool.get('product.uom')
resource_pool = self.pool.get('resource.resource')
calendar_id = phase.project_id.resource_calendar_id.id
if phase.responsible_id.id:
resource_id = resource_pool.search(cr,uid,[('user_id','=',phase.responsible_id.id)])
calendar_id = resource_pool.browse(cr,uid,resource_id[0]).calendar_id.id
default_uom_id = uom_pool.search(cr,uid,[('name','=','Hour')])[0]
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration,default_uom_id)
work_time = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, date_end, avg_hours or 0.0,resource_id or False)
dt_start = work_time[0][0].strftime('%Y-%m-%d %H:%M:%S')
self.write(cr,uid,[phase.id],{'date_start':dt_start,'date_end':date_end.strftime('%Y-%m-%d %H:%M:%S')})
def constraint_date_end(self,cr,uid,phase,date_start,context=None):
# Recursive call for all next phases if change in date_end > older time
resource_cal_pool = self.pool.get('resource.calendar')
uom_pool = self.pool.get('product.uom')
resource_pool = self.pool.get('resource.resource')
calendar_id = phase.project_id.resource_calendar_id.id
if phase.responsible_id.id:
resource_id = resource_pool.search(cr,uid,[('user_id','=',phase.responsible_id.id)])
calendar_id = resource_pool.browse(cr,uid,resource_id[0]).calendar_id.id
default_uom_id = uom_pool.search(cr,uid,[('name','=','Hour')])[0]
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration,default_uom_id)
work_time = resource_cal_pool.interval_get(cr, uid, calendar_id or False, date_start, avg_hours or 0.0,resource_id or False)
dt_end = work_time[-1][1].strftime('%Y-%m-%d %H:%M:%S')
self.write(cr,uid,[phase.id],{'date_start':date_start.strftime('%Y-%m-%d %H:%M:%S'),'date_end':dt_end})
def write(self, cr, uid, ids, vals,context=None):
if not context:
context = {}
if context.get('scheduler',False):
return super(project_phase, self).write(cr, uid, ids, vals, context=context)
# if the phase is performed by a resource then its calendar and efficiency also taken
# otherwise the project's working calendar considered
phase = self.browse(cr,uid,ids[0])
resource_cal_pool = self.pool.get('resource.calendar')
resource_pool = self.pool.get('resource.resource')
uom_pool = self.pool.get('product.uom')
resource_id = False
calendar_id = phase.project_id.resource_calendar_id.id
if phase.responsible_id.id:
resource_id = resource_pool.search(cr,uid,[('user_id','=',phase.responsible_id.id)])
calendar_id = resource_pool.browse(cr,uid,resource_id[0]).calendar_id.id
default_uom_id = uom_pool.search(cr,uid,[('name','=','Hour')])[0]
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration,default_uom_id)
# write method changes the date_start and date_end
#for previous and next phases respectively based on valid condition
if vals.get('date_start'):
if vals['date_start'] < phase.date_start:
dt_start = mx.DateTime.strptime(vals['date_start'],'%Y-%m-%d %H:%M:%S')
work_times = resource_cal_pool.interval_get(cr, uid, calendar_id or False, dt_start, avg_hours or 0.0,resource_id or False)
vals['date_end'] = work_times[-1][1].strftime('%Y-%m-%d %H:%M:%S')
super(project_phase, self).write(cr, uid, ids, vals, context=context)
for prv_phase in phase.previous_phase_ids:
self.constraint_date_start(cr,uid,prv_phase,dt_start)
if vals.get('date_end'):
if vals['date_end'] > phase.date_end:
dt_end = mx.DateTime.strptime(vals['date_end'],'%Y-%m-%d %H:%M:%S')
work_times = resource_cal_pool.interval_min_get(cr, uid, calendar_id or False, dt_end, avg_hours or 0.0,resource_id or False)
vals['date_start'] = work_times[0][0].strftime('%Y-%m-%d %H:%M:%S')
super(project_phase, self).write(cr, uid, ids, vals, context=context)
for next_phase in phase.next_phase_ids:
self.constraint_date_end(cr,uid,next_phase,dt_end)
return super(project_phase, self).write(cr, uid, ids, vals, context=context)
def phase_draft(self, cr, uid, ids,*args):
self.write(cr, uid, ids, {'state': 'draft'})
return True
def phase_start(self, cr, uid, ids,*args):
self.write(cr, uid, ids, {'state': 'open'})
return True
def phase_pending(self, cr, uid, ids,*args):
self.write(cr, uid, ids, {'state': 'pending'})
return True
def phase_cancel(self, cr, uid, ids,*args):
self.write(cr, uid, ids, {'state': 'cancelled'})
return True
def phase_done(self, cr, uid, ids,*args):
self.write(cr, uid, ids, {'state': 'done'})
return True
project_phase()
class project_resource_allocation(osv.osv):
@ -102,9 +230,8 @@ class project_resource_allocation(osv.osv):
_description = 'Project Resource Allocation'
_rec_name = 'resource_id'
_columns = {
# 'name': fields.char('Name',size = 64),
'resource_id': fields.many2one('resource.resource', 'Resource', required=True),
'phase_id': fields.many2one('project.phase', 'Project Phase', required=True),
'phase_id': fields.many2one('project.phase', 'Project Phase',required=True),
'useability': fields.float('Useability', help="Useability of this ressource for this project phase in percentage (=50%)"),
}
_defaults = {

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="wkf_phase" model="workflow">
<field name="name">project.phase.wkf</field>
<field name="osv">project.phase</field>
<field name="on_create">True</field>
</record>
<record id="act_draft" model="workflow.activity">
<field name="wkf_id" ref="wkf_phase"/>
<field name="flow_start">True</field>
<field name="name">phase_draft</field>
<field name="kind">function</field>
<field name="action">phase_draft()</field>
</record>
<record id="act_start_phase" model="workflow.activity">
<field name="wkf_id" ref="wkf_phase"/>
<field name="name">phase_start</field>
<field name="kind">function</field>
<field name="action">phase_start()</field>
</record>
<record id="act_cancel_phase" model="workflow.activity">
<field name="wkf_id" ref="wkf_phase"/>
<field name="name">phase_cancel</field>
<field name="flow_stop">True</field>
<field name="kind">function</field>
<field name="action">phase_cancel()</field>
</record>
<record id="act_phase_pending" model="workflow.activity">
<field name="wkf_id" ref="wkf_phase"/>
<field name="name">phase_pending</field>
<field name="kind">function</field>
<field name="action">phase_pending()</field>
</record>
<record id="act_phase_done" model="workflow.activity">
<field name="wkf_id" ref="wkf_phase"/>
<field name="name">phase_done</field>
<field name="flow_stop">True</field>
<field name="kind">function</field>
<field name="action">phase_done()</field>
</record>
<record id="t0" model="workflow.transition">
<field name="act_from" ref="act_draft"/>
<field name="act_to" ref="act_start_phase"/>
<field name="signal">phase_start</field>
</record>
<record id="t1" model="workflow.transition">
<field name="act_from" ref="act_draft"/>
<field name="act_to" ref="act_cancel_phase"/>
<field name="signal">phase_cancel</field>
</record>
<record id="t2" model="workflow.transition">
<field name="act_from" ref="act_draft"/>
<field name="act_to" ref="act_phase_done"/>
<field name="signal">phase_done</field>
</record>
<record id="t3" model="workflow.transition">
<field name="act_from" ref="act_start_phase"/>
<field name="act_to" ref="act_phase_pending"/>
<field name="signal">phase_pending</field>
</record>
<record id="t4" model="workflow.transition">
<field name="act_from" ref="act_phase_pending"/>
<field name="act_to" ref="act_cancel_phase"/>
<field name="signal">phase_cancel</field>
</record>
<record id="t5" model="workflow.transition">
<field name="act_from" ref="act_phase_pending"/>
<field name="act_to" ref="act_draft"/>
<field name="signal">phase_draft</field>
</record>
<record id="t6" model="workflow.transition">
<field name="act_from" ref="act_start_phase"/>
<field name="act_to" ref="act_cancel_phase"/>
<field name="signal">phase_cancel</field>
</record>
<record id="t7" model="workflow.transition">
<field name="act_from" ref="act_start_phase"/>
<field name="act_to" ref="act_phase_done"/>
<field name="signal">phase_done</field>
</record>
<record id="t8" model="workflow.transition">
<field name="act_from" ref="act_start_phase"/>
<field name="act_to" ref="act_draft"/>
<field name="signal">phase_draft</field>
</record>
</data>
</openerp>

View File

@ -19,9 +19,8 @@
<field name="type">tree</field>
<field name="priority" eval="5"/>
<field name="arch" type="xml">
<tree string="Project Resource Allocation">
<tree editable="bottom" string="Project Resource Allocation">
<field name="resource_id"/>
<field name="phase_id"/>
<field name="useability"/>
</tree>
</field>
@ -34,29 +33,56 @@
<form string="Project Phase">
<group colspan="6" col="6">
<field name="name" select="1"/>
<field name="project_id"/>
<field name="project_id" on_change="onchange_project(project_id)"/>
<field name="responsible_id"/>
<field name="date_start"/>
<field name="date_end"/>
<field name="constraint_date_start"/>
<field name="constraint_date_end"/>
<field name="duration"/>
<field name="product_uom"/>
</group>
<notebook colspan="4">
<page string="Task Detail">
<separator colspan="4" string="Project's Tasks"/>
<field colspan="4" name="task_ids" nolabel="1"/>
</page>
<page string="Resource Allocation Detail">
<page string="Resource Allocation Detail">
<separator colspan="4" string="Resource Allocation"/>
<field colspan="4" name="resource_ids" nolabel="1"/>
<field colspan="4" name="resource_ids" nolabel="1">
<tree editable="bottom" string="Project Resource Allocation">
<field name="resource_id" context="{'project_id':parent.project_id}"/>
<field name="useability"/>
</tree>
<form string="Project Resource Allocation">
<field name="resource_id" context="{'project_id':parent.project_id}"/>
<field name="useability"/>
</form>
</field>
<group col="12" colspan="4">
<field name="state" select="1"/>
<button string="Draft" name="phase_draft" states="open"/>
<button string="Start Phase" name="phase_start" states="pending,draft"/>
<button string="Cancel" name="phase_cancel" states="draft,open,pending"/>
<button string="Done" name="phase_done" states="draft,pending,open"/>
<button string="Pending" name="phase_pending" states="open"/>
</group>
</page>
<page string="Other Info">
<group colspan="2" col="2">
<separator string="Constraints" colspan="2"/>
<field name="constraint_date_start"/>
<field name="constraint_date_end"/>
</group>
<group colspan="2" col="2">
<separator string="Scheduling" colspan="2"/>
<field name="sequence"/>
</group>
<separator colspan="4" string="Next Phases"/>
<field colspan="4" name="next_phase_ids" nolabel="1"/>
<separator colspan="4" string="Previous Phases"/>
<field colspan="4" name="previous_phase_ids" nolabel="1"/>
</page>
<page string="Task Detail">
<separator colspan="4" string="Project's Tasks"/>
<field colspan="4" name="task_ids" nolabel="1"/>
<button name="%(wizard_schedule_task)d" string="Schedule Tasks" type="action" icon="gtk-jump-to"/>
</page>
</notebook>
<newline/>
</form>
@ -92,7 +118,7 @@
res_model="project.task"
src_model="project.phase"
view_mode="tree,form"
domain="[('phase_id','=',active_id)]"/>
domain="[('phase_id','=',active_id)]"/>
<menuitem action="act_project_phase" id="menu_project_phase" parent="project.menu_project_management" sequence="4"/>

View File

@ -5,11 +5,13 @@
<wizard id="wizard_delegate_task" menu="False" model="project.task" name="project.task.delegate" string="Delegate Task"/>
<wizard id="wizard_attachment_task" model="project.task" name="project.task.attachment" string="All Attachments"/> -->
<wizard id="wizard_compute_phase" menu="False" model="project.phase" name="wizard.compute.phases" string="Compute Phase Scheduling"/>
<menuitem icon="terp-project" id="menu_main" name="Project Management"/>
<!--<menuitem icon="terp-project" id="menu_main" name="Project Management"/>-->
<wizard id="wizard_schedule_task" menu="False" model="project.phase" name="phase.schedule.tasks" string="Schedule Tasks"/>
<menuitem id="base.menu_pm_planning" name="Planning" parent="base.menu_main" sequence="3"/>
<menuitem
action="wizard_compute_phase"
id="menu_wizard_compute_phase"
parent="menu_main"
parent="base.menu_pm_planning"
type="wizard"
sequence="1"/>
</data>

View File

@ -20,6 +20,7 @@
##############################################################################
import compute_phases_date
import schedule_tasks
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -25,17 +25,17 @@ from tools.translate import _
import datetime
from resource.faces import *
from new import classobj
import operator
import project.project_resource as proj
compute_form = """<?xml version="1.0" ?>
<form string="Compute Scheduling of Phases">
<field name="phase_id" colspan="4"/>
<field name="project_id" colspan="4"/>
</form>"""
compute_fields = {
'phase_id': {'string':'Phase', 'type':'many2one', 'relation': 'project.phase', 'required':'True'},
'project_id': {'string':'Project', 'type':'many2one', 'relation': 'project.project'},
}
@ -44,188 +44,91 @@ success_msg = """<?xml version="1.0" ?>
<label string="Phase Scheduling completed successfully."/>
</form>"""
def timeformat_convert(cr, uid, time_string, context={}):
# Function to convert input time string:: 8.5 to output time string 8:30
def phase_schedule(cr,uid,phase,start_date,calendar_id=False):
pool = pooler.get_pool(cr.dbname)
phase_pool = pool.get('project.phase')
resource_pool = pool.get('resource.resource')
uom_pool = pool.get('product.uom')
wktime_cal = []
resource_cal = False
phase_resource = False
if phase:
resource_id = resource_pool.search(cr,uid,[('user_id','=',phase.responsible_id.id)])
if resource_id:
resource_obj = resource_pool.browse(cr,uid,resource_id)[0]
leaves = proj.leaves_resource(cr,uid,calendar_id or False ,resource_id,resource_obj.calendar_id.id)
phase_resource = classobj(str(resource_obj.name),(Resource,),{'__doc__':resource_obj.name,'__name__':resource_obj.name,'vacation':tuple(leaves),'efficiency':resource_obj.time_efficiency})
default_uom_id = uom_pool.search(cr,uid,[('name','=','Hour')])[0]
avg_hours = uom_pool._compute_qty(cr, uid, phase.product_uom.id, phase.duration,default_uom_id)
duration = str(avg_hours) + 'H'
split_list = str(time_string).split('.')
hour_part = split_list[0]
mins_part = split_list[1]
round_mins = int(round(float(mins_part) * 60,-2))
converted_string = hour_part + ':' + str(round_mins)[0:2]
return converted_string
# Creating a new project for each phase
def Project():
start = start_date
minimum_time_unit = 1
resource = phase_resource
# If project has working calendar else the default one would be considered
if calendar_id:
working_days = proj.compute_working_calendar(cr,uid,calendar_id)
vacation = tuple(proj.leaves_resource(cr,uid,calendar_id))
def leaves_resource(cr,uid,id):
# To get the leaves for the resource_ids working on phase
def phase():
effort = duration
pool = pooler.get_pool(cr.dbname)
resource_leaves_pool = pool.get('resource.calendar.leaves')
resource_leave_ids = resource_leaves_pool.search(cr,uid,[('resource_id','=',id)])
leaves = []
if resource_leave_ids:
res_leaves = resource_leaves_pool.read(cr,uid,resource_leave_ids,['date_from','date_to'])
for leave in range(len(res_leaves)):
dt_start = datetime.datetime.strptime(res_leaves[leave]['date_from'],'%Y-%m-%d %H:%M:%S')
dt_end = datetime.datetime.strptime(res_leaves[leave]['date_to'],'%Y-%m-%d %H:%M:%S')
no = dt_end - dt_start
leave_days = no.days + 1
[leaves.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(leave_days))]
leaves.sort()
return leaves
project = BalancedProject(Project)
s_date = project.phase.start.to_datetime()
e_date = project.phase.end.to_datetime()
def resource_list(cr,uid,obj):
# To get the resource_ids working on phase
# According to constraints on date start and date end on phase recalculation done
if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
start_date = datetime.datetime.strptime(phase.constraint_date_start,'%Y-%m-%d %H:%M:%S')
else:
start_date = s_date
if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
end_date= datetime.datetime.strptime(phase.constraint_date_end,'%Y-%m-%d %H:%M:%S')
date_start = phase.constraint_date_end[:-3]
else:
end_date = e_date
date_start = end_date
pool = pooler.get_pool(cr.dbname)
resource_pool = pool.get('resource.resource')
resources = obj.resource_ids
resource_objs = []
leaves = []
calendar_id = obj.project_id.resource_calendar_id
for no in range(len(resources)):
resource_id = resource_pool.search(cr,uid,[('id','=',resources[no].resource_id.id)])
if resource_id and calendar_id:
# Getting list of leaves for specific resource
leaves = leaves_resource(cr,uid,resource_id)
# Creating the faces.Resource object with resource specific efficiency and vacation
resource_objs.append(classobj(str(resources[no].resource_id.name),(Resource,),{'__doc__':resources[no].resource_id.name,'__name__':resources[no].resource_id.name,'efficiency':resources[no].useability/100,'vacation':tuple(leaves)}))
return resource_objs
# Writing the dates back
phase_pool.write(cr,uid,[phase.id],{'date_start':start_date.strftime('%Y-%m-%d %H:%M:%S'),'date_end':end_date.strftime('%Y-%m-%d %H:%M:%S')},context={'scheduler':True})
# Recursive calling the next phases till all the phases are scheduled
for phase in phase.next_phase_ids:
if phase.state in ['draft','open','pending']:
phase_schedule(cr,uid,phase,date_start,phase.project_id.resource_calendar_id.id or False)
else:
continue
#
class wizard_compute_phases(wizard.interface):
def _compute_date(self, cr, uid, data, context):
pool = pooler.get_pool(cr.dbname)
project_pool = pool.get('project.project')
phase_pool = pool.get('project.phase')
resource_pool = pool.get('resource.resource')
resource_leaves_pool = pool.get('resource.calendar.leaves')
resource_week_pool = pool.get('resource.calendar.week')
avg_hr = 0.0
wktime_cal = []
leaves = []
phase_id = data['form']['phase_id']
phase = phase_pool.browse(cr,uid,phase_id)
calendar_id = phase.project_id.resource_calendar_id.id
# If project has a working calendar then that would be used otherwise
# the default faces calendar would be used
if calendar_id:
time_range = "8:00-8:00"
non_working = ""
wk = {"0":"mon","1":"tue","2":"wed","3":"thu","4":"fri","5":"sat","6":"sun"}
wk_days = {}
wk_time = {}
wktime_list = []
hours = []
hr = 0
week_ids = resource_week_pool.search(cr,uid,[('calendar_id','=',calendar_id)])
week_obj = resource_week_pool.read(cr,uid,week_ids,['dayofweek','hour_from','hour_to'])
# if project mentioned
if data['form']['project_id']:
project_id = project_pool.browse(cr,uid,data['form']['project_id'])
phase_ids = phase_pool.search(cr,uid,[('project_id','=',project_id.id),('state','in',['draft','open','pending']),('previous_phase_ids','=',False)])
# Converting time formats into appropriate format required
# and creating a list like [('mon', '8:00-12:00'), ('mon', '13:00-18:00')]
for week in week_obj:
res_str = ""
if wk.has_key(week['dayofweek']):
day = wk[week['dayofweek']]
wk_days[week['dayofweek']] = wk[week['dayofweek']]
hour_from_str = timeformat_convert(cr,uid,week['hour_from'])
hour_to_str = timeformat_convert(cr,uid,week['hour_to'])
res_str = hour_from_str + '-' + hour_to_str
hours.append(week['hour_from'])
hours.append(week['hour_to'])
wktime_list.append((day,res_str))
# Converting it to format like [('mon', '8:00-12:00', '13:00-18:00')]
for item in wktime_list:
if wk_time.has_key(item[0]):
wk_time[item[0]].append(item[1])
else:
wk_time[item[0]] = [item[0]]
wk_time[item[0]].append(item[1])
for k,v in wk_time.items():
wktime_cal.append(tuple(v))
for hour in range(len(hours)):
if hour%2 ==0:
hr += float(hours[hour+1]) - float(hours[hour])
avg_hr = hr/len(wktime_cal)
# For non working days adding [('tue,wed,fri,sat,sun', '8:00-8:00')]
for k,v in wk_days.items():
if wk.has_key(k):
wk.pop(k)
for v in wk.itervalues():
non_working += v + ','
if non_working:
wktime_cal.append((non_working[:-1],time_range))
# If project working calendar has any leaves
resource_leave_ids = resource_leaves_pool.search(cr,uid,[('calendar_id','=',calendar_id)])
if resource_leave_ids:
res_leaves = resource_leaves_pool.read(cr,uid,resource_leave_ids,['date_from','date_to'])
for leave in range(len(res_leaves)):
dt_start = datetime.datetime.strptime(res_leaves[leave]['date_from'],'%Y-%m-%d %H:%M:%S')
dt_end = datetime.datetime.strptime(res_leaves[leave]['date_to'],'%Y-%m-%d %H:%M:%S')
no = dt_end - dt_start
leave_days = no.days + 1
[leaves.append((dt_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d')) for x in range(int(leave_days))]
leaves.sort()
def phase_schedule(cr,uid,phase,start_date,avg_hour = 0.0):
if phase:
# To get resources and the duration for the phase
resources_list = resource_list(cr,uid,phase)
if not avg_hour:
avg_hour = 8.0
man_days = str(phase.duration * avg_hour) + 'H'
# Creating a new project for each phase
def Project():
start = start_date
# If project has working calendar else the default one would be considered
if wktime_cal:
working_days = wktime_cal
vacation = tuple(leaves)
def phase():
effort = man_days
resource = reduce(operator.or_,resources_list)
project = BalancedProject(Project)
print 'Project Phase Start & End:::',project.phase.name,project.phase.booked_resource,project.phase.start.to_datetime(),project.phase.end.to_datetime()
s_date = project.phase.start.to_datetime()
e_date = project.phase.end.to_datetime()
# According to constraints on date start and date end on phase recalculation done
if phase.constraint_date_start and str(s_date) < phase.constraint_date_start:
start_date = phase.constraint_date_start
else:
start_date = s_date
if phase.constraint_date_end and str(e_date) > phase.constraint_date_end:
end_date = phase.constraint_date_end[:-3]
else:
end_date = e_date
# Writing the dates back
phase_pool.write(cr,uid,[phase.id],{'date_start':start_date,'date_end':end_date})
date_start = end_date
# Recursive calling the next phases till all the phases are scheduled
for phase in phase.next_phase_ids:
phase_schedule(cr,uid,phase,date_start)
# Phase Scheduling starts from here with the call to phase_schedule method
start_dt = datetime.datetime.strftime((datetime.datetime.strptime(phase.project_id.date_start,"%Y-%m-%d")),"%Y-%m-%d %H:%M")
if avg_hr:
phase_schedule(cr,uid,phase,start_dt,avg_hr)
# else all the draft,open,pending states phases taken
else:
phase_schedule(cr,uid,phase,start_dt)
phase_ids = phase_pool.search(cr,uid,[('state','in',['draft','open','pending']),('previous_phase_ids','=',False)])
phase_ids.sort()
phase_objs = phase_pool.browse(cr,uid,phase_ids)
for phase in phase_objs:
start_date = phase.project_id.date_start
if not phase.project_id.date_start:
start_date = datetime.datetime.now().strftime("%Y-%m-%d")
start_dt = datetime.datetime.strftime((datetime.datetime.strptime(start_date,"%Y-%m-%d")),"%Y-%m-%d %H:%M")
calendar_id = phase.project_id.resource_calendar_id.id
phase_schedule(cr,uid,phase,start_dt,calendar_id or False)
return {}
states = {
'init': {
'actions': [],

View File

@ -0,0 +1,137 @@
# -*- 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/>.
#
##############################################################################
import wizard
import pooler
from tools.translate import _
import datetime
from resource.faces import *
from new import classobj
import operator
import time
import project.project_resource as proj
success_msg = """<?xml version="1.0" ?>
<form string="Compute Scheduling of Tasks">
<label string="Task Scheduling completed successfully."/>
</form>"""
def resource_list(cr,uid,phase):
# To create resources which are the Project Members
resource_objs = []
for resource in phase.resource_ids:
res = resource.resource_id
leaves = []
resource_eff = res.time_efficiency
resource_cal = res.calendar_id.id
wktime_cal = proj.compute_working_calendar(cr,uid,resource_cal)
leaves = proj.leaves_resource(cr,uid,phase.project_id.resource_calendar_id.id or False ,res.id,resource_cal)
resource_objs.append(classobj(str(res.user_id.name),(Resource,),{'__doc__':res.user_id.name,'__name__':res.user_id.name,'vacation':tuple(leaves),'efficiency':resource_eff}))
return resource_objs
class wizard_schedule_task(wizard.interface):
def _compute_date(self, cr, uid, data, context):
pool = pooler.get_pool(cr.dbname)
phase_pool = pool.get('project.phase')
task_pool = pool.get('project.task')
user_pool = pool.get('res.users')
phase = phase_pool.browse(cr,uid,data['id'])
task_ids = map(lambda x:x.id,(filter(lambda x:x.state in ['open','draft','pending'] ,phase.task_ids)))
if task_ids:
task_ids.sort()
tasks = task_pool.browse(cr,uid,task_ids)
wktime_cal = []
start_date = str(phase.date_start)[:-9]
if not phase.date_start:
if not phase.project_id.date_start:
start_date = datetime.datetime.now().strftime("%Y-%m-%d")
else:
start_date = phase.project_id.date_start
date_start = datetime.datetime.strftime(datetime.datetime.strptime(start_date,"%Y-%m-%d"),"%Y-%m-%d %H:%M")
calendar_id = phase.project_id.resource_calendar_id.id
resource_objs = resource_list(cr,uid,phase)
priority_dict = {'0':1000,'1':800,'2':500,'3':300,'4':100}
# To create dynamic no of tasks with the resource specified
def tasks_resource(j,eff,priorty = 500,obj=False):
def task():
"""
task is a dynamic method!
"""
effort = eff
if obj:
resource = obj
priority = priorty
task.__doc__ = "TaskNO%d" %j
task.__name__ = "task%d" %j
return task
# Creating the project with all the tasks and resources
def Project():
title = "Test Project"
start = date_start
resource = reduce(operator.or_,resource_objs)
minimum_time_unit = 1
# If project has calendar
if calendar_id:
working_days = proj.compute_working_calendar(cr,uid,calendar_id)
vacation = tuple(proj.leaves_resource(cr,uid,calendar_id))
# Dynamic Creation of tasks
i = 0
for each_task in tasks:
hours = str(each_task.planned_hours / each_task.occupation_rate)+ 'H'
if each_task.priority in priority_dict.keys():
priorty = priority_dict[each_task.priority]
if each_task.user_id:
for resource_object in resource_objs:
if resource_object.__name__ == each_task.user_id.name:
task = tasks_resource(i,hours,priorty,resource_object)
else:
task = tasks_resource(i,hours,priorty)
i += 1
# Writing back the dates
project = BalancedProject(Project)
loop_no = 0
for t in project:
s_date = t.start.to_datetime()
e_date = t.end.to_datetime()
if loop_no > 0:
user_id = user_pool.search(cr,uid,[('name','=',t.booked_resource[0].__name__)])
task_pool.write(cr,uid,[tasks[loop_no-1].id],{'date_start':s_date.strftime('%Y-%m-%d %H:%M:%S'),'date_deadline':e_date.strftime('%Y-%m-%d %H:%M:%S'),'user_id':user_id[0]},context={'scheduler':True})
loop_no +=1
return {}
states = {
'init': {
'actions': [_compute_date],
'result': {'type':'form','arch':success_msg,'fields':{}, 'state':[('end', 'Ok')]},
}
}
wizard_schedule_task('phase.schedule.tasks')
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -20,6 +20,7 @@
##############################################################################
import resource
import faces
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -36,68 +36,67 @@ class resource_calendar(osv.osv):
_defaults = {
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'resource.calendar', c)
}
def interval_min_get(self, cr, uid, id, dt_from, hours , resource=0):
def interval_min_get(self, cr, uid, id, dt_from, hours,resource=False):
resource_cal_leaves = self.pool.get('resource.calendar.leaves')
dt_leave = []
if not id:
return [(dt_from-mx.DateTime.RelativeDateTime(hours=int(hours)*3), dt_from)]
if resource:
resource_leave_ids = self.pool.get('resource.calendar.leaves').search(cr,uid,[('resource_id','=',resource)])
if resource_leave_ids:
res_leaves = self.pool.get('resource.calendar.leaves').read(cr,uid,resource_leave_ids,['date_from','date_to'])
for i in range(len(res_leaves)):
dtf = mx.DateTime.strptime(res_leaves[i]['date_from'],'%Y-%m-%d %H:%M:%S')
dtt = mx.DateTime.strptime(res_leaves[i]['date_to'],'%Y-%m-%d %H:%M:%S')
leave_days = ((dtt - dtf).days) + 1
for x in range(int(leave_days)):
dt_leave.append((dtf + mx.DateTime.RelativeDateTime(days=x)).strftime('%Y-%m-%d'))
dt_leave.sort()
resource_leave_ids = resource_cal_leaves.search(cr,uid,[('calendar_id','=',id),'|',('resource_id','=',False),('resource_id','=',resource)])
res_leaves = resource_cal_leaves.read(cr,uid,resource_leave_ids,['date_from','date_to'])
for leave in res_leaves:
dtf = mx.DateTime.strptime(leave['date_from'],'%Y-%m-%d %H:%M:%S')
dtt = mx.DateTime.strptime(leave['date_to'],'%Y-%m-%d %H:%M:%S')
no = dtt - dtf
[dt_leave.append((dtf + mx.DateTime.RelativeDateTime(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
dt_leave.sort()
dt_leave.reverse()
todo = hours
cycle = 0
result = []
maxrecur = 100
current_hour = dt_from.hour
while (todo>0) and maxrecur:
cr.execute("select hour_from,hour_to from resource_calendar_week where dayofweek='%s' and calendar_id=%s order by hour_from", (dt_from.day_of_week,id))
cr.execute("select hour_from,hour_to from resource_calendar_week where dayofweek='%s' and calendar_id=%s order by hour_from desc", (dt_from.day_of_week,id))
for (hour_from,hour_to) in cr.fetchall():
if (hour_to>current_hour) and (todo>0):
m = max(hour_from, current_hour)
if (hour_to-m)>todo:
hour_to = m+todo
d1 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(m)),int((m%1) * 60))
d2 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(hour_to)),int((hour_to%1) * 60))
dt1 = d1.strftime('%Y-%m-%d')
dt2 = d2.strftime('%Y-%m-%d')
for i in range(len(dt_leave)):
if dt1 == dt_leave[i]:
dt_from += mx.DateTime.RelativeDateTime(days=1)
d1 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(m)),int((m%1) * 60))
d2 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(hour_to)),int((hour_to%1) * 60))
dt1 = d1.strftime('%Y-%m-%d')
dt2 = d2.strftime('%Y-%m-%d')
leave_flag = False
if (hour_from<current_hour) and (todo>0):
m = min(hour_to, current_hour)
if (m-hour_from)>todo:
hour_from = m-todo
dt_check = dt_from.strftime('%Y-%m-%d')
for leave in dt_leave:
if dt_check == leave:
dt_check = mx.DateTime.strptime(dt_check,"%Y-%m-%d") - mx.DateTime.RelativeDateTime(days=1)
leave_flag = True
if leave_flag:
break
else:
d1 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(hour_from)),int((hour_from%1) * 60))
d2 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(m)),int((m%1) * 60))
result.append((d1, d2))
current_hour = hour_to
todo -= (hour_to - m)
dt_from += mx.DateTime.RelativeDateTime(days=1)
current_hour = 0
current_hour = hour_from
todo -= (m-hour_from)
dt_from -= mx.DateTime.RelativeDateTime(days=1)
current_hour = 24
maxrecur -= 1
result.reverse()
return result
def interval_get(self, cr, uid, id, dt_from, hours, resource=0, byday=True):
def interval_get(self, cr, uid, id, dt_from, hours, resource=False, byday=True):
resource_cal_leaves = self.pool.get('resource.calendar.leaves')
dt_leave = []
if not id:
return [(dt_from,dt_from+mx.DateTime.RelativeDateTime(hours=int(hours)*3))]
if resource:
resource_leave_ids = self.pool.get('resource.calendar.leaves').search(cr,uid,[('resource_id','=',resource)])
if resource_leave_ids:
res_leaves = self.pool.get('resource.calendar.leaves').read(cr,uid,resource_leave_ids,['date_from','date_to'])
for i in range(len(res_leaves)):
dtf = mx.DateTime.strptime(res_leaves[i]['date_from'],'%Y-%m-%d %H:%M:%S')
dtt = mx.DateTime.strptime(res_leaves[i]['date_to'],'%Y-%m-%d %H:%M:%S')
no = dtt - dtf
leave_days = no.days + 1
for x in range(int(leave_days)):
dt_leave.append((dtf + mx.DateTime.RelativeDateTime(days=x)).strftime('%Y-%m-%d'))
dt_leave.sort()
resource_leave_ids = resource_cal_leaves.search(cr,uid,[('calendar_id','=',id),'|',('resource_id','=',False),('resource_id','=',resource)])
res_leaves = resource_cal_leaves.read(cr,uid,resource_leave_ids,['date_from','date_to'])
for leave in res_leaves:
dtf = mx.DateTime.strptime(leave['date_from'],'%Y-%m-%d %H:%M:%S')
dtt = mx.DateTime.strptime(leave['date_to'],'%Y-%m-%d %H:%M:%S')
no = dtt - dtf
[dt_leave.append((dtf + mx.DateTime.RelativeDateTime(days=x)).strftime('%Y-%m-%d')) for x in range(int(no.days + 1))]
dt_leave.sort()
print 'dtLevae',dt_leave
todo = hours
cycle = 0
result = []
@ -106,24 +105,24 @@ class resource_calendar(osv.osv):
while (todo>0) and maxrecur:
cr.execute("select hour_from,hour_to from resource_calendar_week where dayofweek='%s' and calendar_id=%s order by hour_from", (dt_from.day_of_week,id))
for (hour_from,hour_to) in cr.fetchall():
leave_flag = False
if (hour_to>current_hour) and (todo>0):
m = max(hour_from, current_hour)
if (hour_to-m)>todo:
hour_to = m+todo
d1 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(m)),int((m%1) * 60))
d2 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(hour_to)),int((hour_to%1) * 60))
dt1 = d1.strftime('%Y-%m-%d')
dt2 = d2.strftime('%Y-%m-%d')
for i in range(len(dt_leave)):
if dt1 == dt_leave[i]:
dt_from += mx.DateTime.RelativeDateTime(days=1)
d1 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(m)),int((m%1) * 60))
d2 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(hour_to)),int((hour_to%1) * 60))
dt1 = d1.strftime('%Y-%m-%d')
dt2 = d2.strftime('%Y-%m-%d')
result.append((d1, d2))
current_hour = hour_to
todo -= (hour_to - m)
dt_check = dt_from.strftime('%Y-%m-%d')
for leave in dt_leave:
if dt_check == leave:
dt_check = mx.DateTime.strptime(dt_check,"%Y-%m-%d") + mx.DateTime.RelativeDateTime(days=1)
leave_flag = True
if leave_flag:
break
else:
d1 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(m)),int((m%1) * 60))
d2 = mx.DateTime.DateTime(dt_from.year,dt_from.month,dt_from.day,int(math.floor(hour_to)),int((hour_to%1) * 60))
result.append((d1, d2))
current_hour = hour_to
todo -= (hour_to - m)
dt_from += mx.DateTime.RelativeDateTime(days=1)
current_hour = 0
maxrecur -= 1
@ -155,8 +154,8 @@ class resource_resource(osv.osv):
'company_id' : fields.many2one('res.company', 'Company', required=True),
'resource_type': fields.selection([('user','Human'),('material','Material')], 'Resource Type', required=True),
'user_id' : fields.many2one('res.users', 'User',help='Related user name for the resource to manage its access.'),
'time_efficiency' : fields.float('Efficiency factor', size=8, help="This field depict the efficiency of the resource to complete tasks. e.g resource put alone on a phase of 5 days with 5 tasks assigned to him, will show a load of 100% for this phase by default, but if we put a efficency of 200%, then his load will only be 50%."),
'calendar_id' : fields.many2one("resource.calendar", "Working time", help="Define the schedule of resource"),
'time_efficiency' : fields.float('Efficiency factor', size=8, required=True,help="This field depict the efficiency of the resource to complete tasks. e.g resource put alone on a phase of 5 days with 5 tasks assigned to him, will show a load of 100% for this phase by default, but if we put a efficency of 200%, then his load will only be 50%."),
'calendar_id' : fields.many2one("resource.calendar", "Working time",required=True, help="Define the schedule of resource"),
}
_defaults = {
'resource_type' : lambda *a: 'user',
@ -164,6 +163,18 @@ class resource_resource(osv.osv):
'active' : lambda *a: True,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'resource.resource', c)
}
def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
if context is None:
context = {}
if context.get('project_id',False):
project_pool = self.pool.get('project.project')
project_rec = project_pool.browse(cr,uid,context['project_id'])
user_ids = [user_id.id for user_id in project_rec.members]
args.append(('user_id','in',user_ids))
return super(resource_resource, self).search(cr, uid, args, offset, limit,order, context, count)
resource_resource()
class resource_calendar_leaves(osv.osv):
@ -187,5 +198,14 @@ class resource_calendar_leaves(osv.osv):
_constraints = [
(check_dates, 'Error! leave start-date must be lower then leave end-date.', ['date_from', 'date_to'])
]
def onchange_resource(self,cr,uid,ids,resource):
result = {}
if resource:
resource_pool = self.pool.get('resource.resource')
result['calendar_id'] = resource_pool.browse(cr,uid,resource).calendar_id.id
return {'value':result}
return {'value':{'calendar_id':[]}}
resource_calendar_leaves()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -61,7 +61,6 @@
<field name="arch" type="xml">
<calendar color="resource_id" date_start="date_from" string="Resource">
<field name="name"/>
</calendar>
</field>
</record>
@ -135,7 +134,7 @@
<field name="calendar_id" />
<field name="date_from" />
<field name="date_to" />
<field name="resource_id" />
<field name="resource_id" on_change="onchange_resource(resource_id)"/>
</form>
</field>
</record>