Enhancement: add lag time to Project Management tasks. This adds a new

column to PM_task.  Also move the bulk of the PM project display JS
stuff into a separate JavaScript file rather than it being in a template
in the database.
This commit is contained in:
Drake 2006-09-07 22:01:36 +00:00
parent 428a3f8ac4
commit ca653ebef8
8 changed files with 569 additions and 448 deletions

View file

@ -48,6 +48,7 @@
- new: In the Project Management asset, tasks can now have multiple resources, which may be users or groups. Original single-resource data is migrated to the new schema by the 7.0.7 upgrade script.
- fix: makePrintable operation with other styleId
- fix: RandomThread macro not working properly. Only CS's with more than one thread are considered for the random search
- new: Tasks in the Project Management can now be assigned non-work lag time that is added to the main work duration of the task.
7.0.6
- fix: Error in DateTime.pm

View file

@ -26,8 +26,12 @@
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clear">
<td style="width:25%;" align="right" class="header">Duration&nbsp;</td>
<td style="width:25%; text-align:right" class="header">Duration&nbsp;</td>
<td style="width:25%;"><tmpl_var form.duration>&nbsp;<tmpl_var form.duration.units></td>
<td style="width:25%; text-align:right" class="header">Lag Time&nbsp;</td>
<td style="width:25%;"><tmpl_var form.lagTime>&nbsp;<tmpl_var form.lagTime.units></td>
</tr><tr class="clear">
<td style="width:50%;" colspan="2">&nbsp;</td>
<td style="width:25%;" align="right" class="header">Is Milestone&nbsp;</td>
<td style="width:25%;"><tmpl_var form.milestone></td>
</tr>

View file

@ -0,0 +1,47 @@
#ProjectManagerTMPL0003
<script type="text/javascript">
var taskArray=<tmpl_var project.task.array>;
</script>
<div class="barPositions">
<tmpl_loop task.div.loop>
<tmpl_if task.isMilestone>
<div class="milestone" style="left:<tmpl_var task.div.left>px;top:<tmpl_var task.div.top>px;">&diams;</div>
<tmpl_else>
<div class="projectBar" style="left:<tmpl_var task.div.left>px;top:<tmpl_var task.div.top>px;width:<tmpl_var task.div.width>px;background-color:<tmpl_var task.div.color>">
<div class="statusBar" style="width:<tmpl_var task.div.percentComplete>%;"></div>
<tmpl_if task.hasPredecessor>
<div class="projectLineH" style="top:4px;left:50px;width:75px;height:28px;">
<div class="projectLineV"></div>
</div>
</tmpl_if>
<tmpl_if task.hasResource>
<div class="projectLabel" style="left:<tmpl_var task.div.label.left>px;top:3px;margin-top:-3px;">
<tmpl_var task.resource.name>
</div>
</tmpl_if>
</div>
</tmpl_if>
</tmpl_loop>
<a name="<tmpl_var project.table.width>" id="projectTableWidth"></a>
<a name="<tmpl_var project.scroll.percentWidth>" id="projectScrollPercentWidth"></a>
<table cellpadding="0" cellspacing="0" border="0" style="width:<tmpl_var scrollWidth>px;z-index:1;">
<tr class="monthNames">
<tmpl_loop months.loop>
<td colspan="<tmpl_var month.colspan>" class="monthName" style="height:20px;"><tmpl_var month.name></td>
</tmpl_loop>
</tr>
<tr class="dates">
<tmpl_loop days.loop>
<td align="center" style="width:23px"><tmpl_var day.number></td>
</tmpl_loop>
</tr>
<tmpl_loop task.count.loop>
<tr>
<td colspan="<tmpl_var total.colspan>" class="empty" style="height:21px;">&nbsp;</td>
</tr>
</tmpl_loop>
</table>
<br />
</div>

View file

@ -2,417 +2,22 @@
<script type="text/javascript">
//<![CDATA[
var dayMS = 86400000;
var popTitle = "Add/Edit Task";
var dunits = "<tmpl_var project.durationUnits>";
var hoursPerDay = <tmpl_var project.hoursPerDay>;
var taskLength=<tmpl_var project.task.length>;
//--------------------------------------------------------------------------------------
function doCalendar (fieldId) {
Calendar.setup({
inputField : fieldId,
ifFormat : "%Y-%m-%d",
showsTime : false,
step : 1,
timeFormat : "12",
firstDay : false
});
}
//--------------------------------------------------------------------------------------
function buildMenuUrl (urltype,taskId) {
if(urltype == "edit") {
alert("edit task: "+taskId);
} else if(urltype == "insertAbove") {
alert("insert task above: "+taskId);
} else if(urltype == "insertBelow") {
alert("insert task below: "+taskId);
} else if(urltype == "delete") {
alert("delete task: "+taskId);
}
}
//--------------------------------------------------------------------------------------
function closeImage() {
return '<tmpl_var extras>/close.gif';
}
//--------------------------------------------------------------------------------------
function configureMilestone(box) {
var form = box.form;
if(box.checked==true) {
form.end.value=form.start.value;
form.duration.value=0;
form.duration.disabled=true;
form.end.disabled=true;
form.dependants.disabled=true;
form.resource.disabled=true;
form.percentComplete.disabled=true;
form.percentComplete.value=0;
} else {
form.end.disabled=false;
form.duration.disabled=false;
form.dependants.disabled=false;
form.resource.disabled=false;
form.percentComplete.disabled=false;
form.duration.value = (dunits == "hrs")?hoursPerDay:1;
}
}
//--------------------------------------------------------------------------------------
function checkEditTaskForm (form) {
if(form.name.value == "") {
alert("<tmpl_var form.name.error>");
return;
} else if(form.start.value == "") {
alert("<tmpl_var form.start.error>");
return;
} else if(form.milestone.checked==false && form.end.value == "") {
alert("<tmpl_var form.end.error>");
return;
}
form.submit();
}
//--------------------------------------------------------------------------------------
function intlDate(dateObj) {
return dateObj.getFullYear()+"-"+pad((dateObj.getMonth()+1))+"-"+pad(dateObj.getDate());
}
//--------------------------------------------------------------------------------------
function toDateObj(date) {
var to = date.split("-");
var dateObj = new Date(to[0],(to[1]-1),to[2],0,0,1,0);
return dateObj;
}
//--------------------------------------------------------------------------------------
function adjustTaskTimeFromDuration(start, end, duration, isTaskForm, predecessor, origStart, origEnd, seqNum) {
//set the form element
var form = duration.form;
//get today's date
var today = new Date();
var todayIntl = intlDate(today);
//set start and end date if not already set
if(start.value == "") start.value = todayIntl;
if(end.value == "") end.value = todayIntl;
//Convert predecessor hours to days
var taskDuration = duration.value;
if(dunits == "hrs") taskDuration = taskDuration / hoursPerDay;
var durationFloor = Math.floor(taskDuration);
//Handle task form and main form seperately due to differences in the forms
if(isTaskForm && taskDuration <= 0) {
//Convert to milestone if task is less or equal to zero
if(confirm("Zero duration tasks are considered Milestones. Do you wish to change this task to a milestone?")) {
form.milestone.checked = true;
configureMilestone(form.milestone);
} else {
duration.value = form.orig_duration.value;
}
return;
} else if (taskDuration <= 0){
//Do not let users zero out tasks
alert("Zero duration tasks are considered Milestones. Please edit the task by clicking the link if you wish to change this task to a milestone");
return;
}
//create the start date
var aTo = start.value.split("-");
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
//add new duration days to the start date
toDate.setDate(toDate.getDate() + durationFloor);
//set end date to this date
end.value = intlDate(toDate);
//Set new duration in taskArray
taskArray[seqNum]["duration"] = duration.value;
//Adjust time based on new end date
adjustTaskTimeFromDate(start, end, duration, end, isTaskForm, predecessor, origStart, origEnd, seqNum);
}
//--------------------------------------------------------------------------------------
function adjustTaskTimeFromDate (start, end, duration, element, isTaskForm, predecessor, origStart, origEnd, seqNum) {
//set the form element
var form = element.form;
//set original duration from task form to determine whether or not to continue to set duration
var orig_duration;
if(isTaskForm) {
if(form.milestone.checked == true) return;
orig_duration = form.orig_duration.value;
}
//Handle case where both start and end are empty
if(start.value == "" && end.value == "") {
//get today's date
var today = new Date();
var todayIntl = intlDate(today);
//set start and end date if not already set
start.value = todayIntl;
end.value = todayIntl;
}
//Handle case where one is set and the other isn't
if (end.value == "") end.value = start.value;
if(start.value == "") start.value = end.value;
if(isTaskForm && orig_duration == "") {
//Set duration if this is a new record
//Check to make sure start date comes before end date
var startcomp = start.value.replace(/-/g,"");
var endcomp = end.value.replace(/-/g,"");
if(startcomp > endcomp) {
alert("<tmpl_var form.greaterthan.error>");
if(element.name == "start") {
end.value = element.value;
} else {
start.value = element.value;
}
duration.value = (dunits == "hrs")?hoursPerDay:1;
return;
}
var d = getDaysInterval(start.value,end.value);
if(d == 0) d = 1;
if(dunits == "hrs") {
d = d * hoursPerDay;
}
duration.value = d;
} else {
//Set start/end if duration has been saved
var d = duration.value;
if(dunits == "hrs") {
//Convert to days
d = d / hoursPerDay;
}
//Round off duration or set it to zero if less than 1;
//alert("d = " + d + " floor = " + Math.floor(d));
if(d < 1) d = 0;
else d = Math.floor(d);
if(element.name.indexOf("start") > -1) {
//create the date
var aTo = start.value.split("-");
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
//add duration days to the start date
toDate.setDate(toDate.getDate() + d);
//set end date to this date
end.value = intlDate(toDate);
} else if(element.name.indexOf("end") > -1) {
//create the date
var aFrom = end.value.split("-");
var fromDate = new Date(aFrom[0],(aFrom[1]-1),aFrom[2],0,0,1,0);
//subtract duration days from the end date
fromDate.setDate(fromDate.getDate() - d);
//set start date to this date
start.value = intlDate(fromDate);
}
}
//Check Predecessors before moving stuff
var pred = predecessor.value;
if(pred != "") {
//Check to make sure that the dependency requirement for this task is still valid
//Get the predecessor end date
var taskStart = toDateObj(start.value);
var predTaskEnd;
if(isTaskForm) {
predTaskEnd = toDateObj(taskArray[pred]["end"]);
} else {
var predTaskEndId = "end_"+taskArray[pred]["id"]+"_formId"
predTaskEnd = toDateObj(document.getElementById(predTaskEndId).value);
}
if(taskStart.getTime() < predTaskEnd.getTime()) {
alert("<tmpl_var form.invalidMove.error>");
start.value = origStart.value;
end.value = origEnd.value;
return;
}
}
//Check all tasks past this one and move them forward if necessary (this only needs to happen on the main form)
if(!isTaskForm) {
arrangePredecessors(element,seqNum);
}
//reset orig start and end values
origStart.value = start.value;
origEnd.value = end.value;
if(!isTaskForm) {
//Adjust task form for
paintGanttChart();
}
}
//--------------------------------------------------------------------------------------
function trim(str) {
return str.replace(/^\s+|\s+$/, '');
}
//--------------------------------------------------------------------------------------
function arrangePredecessors (element,seqNum) {
for (var i = 1; i <= taskLength; i++) {
var seq = i;
var task = taskArray[seq];
var taskId = task["id"];
//Calculate duration and duraiton floor
var durationInDays = parseFloat(task["duration"]);
if(dunits == "hrs") durationInDays = durationInDays / hoursPerDay;
var durationFloor = Math.floor(durationInDays);
//alert("Duration Floor is: "+durationFloor);
//Get the current elements
var currElementStart = document.getElementById("start_"+taskId+"_formId");
var currElementEnd = document.getElementById("end_"+taskId+"_formId");
//alert("Current Start Date: "+currElementStart.value+" Current End Date: "+currElementEnd.value);
//Skip the first record as it is the record that was changed
if(seq > 1) {
var predecessor = task["predecessor"];
//alert("predecessor for "+i+" is "+predecessor);
if(predecessor != "") {
var pred = taskArray[predecessor];
var predEndDate = toDateObj(pred["end"]);
var startDate = toDateObj(task["start"]);
//alert ("Pred End Date: "+intlDate(predEndDate));
//Make sure start date of this task is greater than the end date of the predecessor
if(startDate.getTime() <= predEndDate.getTime()) {
//Change the start and end dates of the task
//Get the day part of the predecessor
var predDayPart = parseFloat(pred["dayPart"]);
//alert("predDayPart: "+predDayPart);
if(predDayPart > 0) {
//The previous task took up part of a day. Add the additional day part to the duration
durationInDays += predDayPart;
durationFloor = Math.floor(durationInDays);
}
//alert("Duration in Days: "+durationInDays+" Duration Floor: "+durationFloor);
//Set the start date of this task to the end date of the predecessor
currElementStart.value = pred["end"];
//Adjust end date for change in start date and update the object - start date is actually predEndDate now, so use the existing date object
predEndDate.setDate(predEndDate.getDate() + durationFloor);
currElementEnd.value = intlDate(predEndDate);
//alert("Set seq "+i+" to start: "+pred["end"]+" end: "+intlDate(predEndDate));
}
}
}
//Update task array with new start/end values
taskArray[seq]["start"] = currElementStart.value;
taskArray[seq]["end"] = currElementEnd.value;
taskArray[seq]["dayPart"] = (durationInDays - Math.floor(durationInDays));
}
}
//--------------------------------------------------------------------------------------
function getDaysInterval(from,to) {
var aFrom = from.split("-");
var aTo = to.split("-");
var fromDate = new Date(aFrom[0],(aFrom[1]-1),aFrom[2],0,0,1,0);
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
var fromEpoch = fromDate.getTime();
var toEpoch = toDate.getTime();
var seconds = toEpoch - fromEpoch;
if(seconds == 0) return 0;
return (seconds/dayMS);
}
//--------------------------------------------------------------------------------------
function pad(date) {
var str = ""+date;
if(str.length == 1) {
str = "0"+str;
}
return str;
}
//--------------------------------------------------------------------------------------
function paintGanttChart () {
var status = AjaxRequest.submit(document.forms['editAll'],{
'onSuccess':function(req){ document.getElementById('gantt').innerHTML = req.responseText; }
});
var mwidth = document.getElementById("projectTableWidth").name + "px";
var swidth = document.getElementById("projectScrollPercentWidth").name + "%";
document.getElementById("mastertable").style.width=mwidth;
document.getElementById("scrolltd").style.width=swidth;
}
//--------------------------------------------------------------------------------------
function validateDependant(field,origField,seqNum,start,end,duration,isTaskForm,origStart,origEnd) {
var pred = field.value;
var newTask = false;
if(pred != "") {
if(seqNum == "") seqNum = taskLength+1;
if(pred < 1) {
alert("<tmpl_var form.noPredecessor.error>");
field.value=origField.value;
return;
}
if(pred == seqNum) {
alert("<tmpl_var form.samePredecessor.error>");
field.value=origField.value;
return;
}
if(pred > seqNum) {
alert("<tmpl_var form.previousPredecessor.error>");
field.value = origField.value;
return;
}
//Set defaults if it's a new record and one of the other options hasn't been checked.
if(start.value == "" || end.value == "") {
//get today's date
newTask = true;
duration.value = (dunits == "hrs")?hoursPerDay:1;
}
//Get the predecessor end date and decide where the new start date belongs
var taskStart = start.value;
var taskStartObj = toDateObj(taskStart);
var predTaskEnd = taskArray[pred]["end"];
var predTaskEndObj = toDateObj(predTaskEnd);
//Change start date if it comes before predecessor end date
if(newTask || (taskStartObj.getTime() < predTaskEndObj.getTime())) {
//Convert predecessor hours to days
var taskDuration = duration.value;
if(dunits == "hrs") taskDuration = taskDuration / hoursPerDay;
var durationFloor = Math.floor(taskDuration);
//Get the predecessor dayPart
var predDayPart = parseFloat(pred["dayPart"]);
if(predDayPart > 0) {
//The previous task took up part of a day. Add the additional day part to the duration
taskDuration += predDayPart;
durationFloor = Math.floor(durationInDays);
}
//Set the start date of this task to the end date of the predecessor
start.value = predTaskEnd;
//Adjust end date for change in start date
adjustTaskTimeFromDate(start,end,duration,start,isTaskForm,field,origStart,origEnd,seqNum);
return;
}
}
//Repaint
if(!isTaskForm) {
//Set new predecessor in taskArray
taskArray[seqNum]["predecessor"] = pred;
paintGanttChart();
}
}
addEvent(window, "load", initPopUp);
dunits = "<tmpl_var project.durationUnits>";
hoursPerDay = <tmpl_var project.hoursPerDay>;
taskLength = <tmpl_var project.task.length>;
extrasPath = '<tmpl_var extras>';
errorMsgs = {
'name' : "<tmpl_var form.name.error>",
'start' : "<tmpl_var form.start.error>",
'end' : "<tmpl_var form.end.error>",
'greaterthan' : "<tmpl_var form.greaterthan.error>",
'invalidMove' : "<tmpl_var form.invalidMove.error>",
'noPredecessor' : "<tmpl_var form.noPredecessor.error>",
'samePredecessor' : "<tmpl_var form.samePredecessor.error>",
'previousPredecessor' : "<tmpl_var form.previousPredecessor.error>",
};
addEvent(window, "load", initPopUp);
//]]>
</script>
<tmpl_var form.header>
@ -458,6 +63,7 @@
<td align="center" style="height:20px"><tmpl_var task.start></td>
<td align="center" style="height:20px"><tmpl_var task.end></td>
<td align="center" style="height:20px"><tmpl_var task.dependants></td>
<tmpl_var task.lagTime>
</tr>
</tmpl_loop>
<tr><td colspan="6" style="border-style:none;">&nbsp;</td></tr>

View file

@ -23,6 +23,7 @@ my $session = start(); # this line required
# upgrade functions go here
dropLineageInAssetIndex($session);
giveTasksMultipleResources($session);
giveTasksLagTime($session);
finish($session); # this line required
@ -70,6 +71,15 @@ EOT
);
}
sub giveTasksLagTime {
my $session = shift;
print "\tGiving tasks lag time.\n" unless $quiet;
$session->db->write($_) for(<<'EOT',
ALTER TABLE PM_task ADD COLUMN lagTime bigint(20) DEFAULT 0;
EOT
);
}
# ---- DO NOT EDIT BELOW THIS LINE ----
#-------------------------------------------------

View file

@ -230,12 +230,12 @@ sub updateProjectTask {
return 0 unless ($taskId && $projectId && $totalHours);
my $task = $db->quickHashRef("select * from PM_task where taskId=?",[$taskId]);
my ($units,$hoursPer) = $db->quickArray("select durationUnits, hoursPerDay from PM_project where projectId=?",[$projectId]);
my ($units,$hoursPerDay) = $db->quickArray("select durationUnits, hoursPerDay from PM_project where projectId=?",[$projectId]);
return 0 unless ($task->{taskId});
my $duration = $task->{duration};
if($units eq "days"){
$duration = $duration / $hoursPer;
$duration = $duration * $hoursPerDay;
}
my $percentComplete = ($totalHours / $duration) * 100;
@ -589,6 +589,7 @@ sub www_editProjectSave {
$props->{projectId} = $projectId;
$props->{taskName} = $i18n->get("project start task label");
$props->{duration} = 0;
$props->{lagTime} = 0;
$props->{startDate} = $dt->time();
$props->{endDate} = $dt->time();
$props->{isMilestone} = 1;
@ -845,15 +846,23 @@ sub www_editTask {
$var->{'form.duration'} = WebGUI::Form::float($session,{
-name=>"duration",
-value=>$task->{duration},
-extras=>qq|style="width:70%;" onchange="adjustTaskTimeFromDuration(this.form.start,this.form.end,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq')" onblur="if(this.value == 0){ adjustTaskTimeFromDuration(this.form.start,this.form.end,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq') }" $extras|
-extras=>qq|style="width:70%;" onchange="adjustTaskTimeFromDuration(this.form.start,this.form.end,this,this.form.lagTime,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq')" onblur="if(this.value == 0){ adjustTaskTimeFromDuration(this.form.start,this.form.end,this,this.form.lagTime,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq') }" $extras|
});
$var->{'form.duration.units'} = $self->_getDurationUnitHashAbbrev->{$project->{durationUnits}};
$var->{'form.lagTime'} = WebGUI::Form::float($session,{
-name => "lagTime",
-value => $task->{lagTime},
-extras => qq|style="width:70%;" onchange="adjustTaskTimeFromDuration(this.form.start,this.form.end,this.form.duration,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq')" onblur="if(this.value == 0){ adjustTaskTimeFromDuration(this.form.start,this.form.end,this.form.duration,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq') }" $extras|
});
$var->{'form.lagTime.units'} = $self->_getDurationUnitHashAbbrev->{$project->{durationUnits}};
$var->{'form.start'} = WebGUI::Form::text($session,{
-name=>"start",
-value=>$start,
-size=>"10",
-maxlength=>"10",
-extras=>qq|onfocus="doCalendar(this.id);" onblur="if(this.form.milestone.checked==true) this.form.end.value=this.value; adjustTaskTimeFromDate(this.form.start,this.form.end,this.form.duration,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq');" style="width:88%;"|
-extras=>qq|onfocus="doCalendar(this.id);" onblur="if(this.form.milestone.checked==true) this.form.end.value=this.value; adjustTaskTimeFromDate(this.form.start,this.form.end,this.form.duration,this.form.lagTime,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq');" style="width:88%;"|
});
$var->{'form.end'} = WebGUI::Form::text($session,{
@ -861,7 +870,7 @@ sub www_editTask {
-value=>$end,
-size=>"10",
-maxlength=>"10",
-extras=>qq|onfocus="doCalendar(this.id);" style="width:88%;" onblur="adjustTaskTimeFromDate(this.form.start,this.form.end,this.form.duration,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq');" $extras|
-extras=>qq|onfocus="doCalendar(this.id);" style="width:88%;" onblur="adjustTaskTimeFromDate(this.form.start,this.form.end,this.form.duration,this.form.lagTime,this,true,this.form.dependants,this.form.orig_start,this.form.orig_end,'$seq');" $extras|
});
$var->{'form.dependants'} = WebGUI::Form::integer($session,{
@ -870,7 +879,7 @@ sub www_editTask {
-defaultValue=>"",
-size=>4,
-maxlength=>10,
-extras=>qq|style="width:50%;" onchange="validateDependant(this,this.form.orig_dependant,'$seq',this.form.start,this.form.end,this.form.duration,true,true,this.form.orig_start,this.form.orig_end);"|
-extras=>qq|style="width:50%;" onchange="validateDependant(this,this.form.orig_dependant,'$seq',this.form.start,this.form.end,this.form.duration,this.form.lagTime,true,true,this.form.orig_start,this.form.orig_end);"|
});
tie my %users, "Tie::IxHash";
@ -940,6 +949,7 @@ sub www_editTaskSave {
$props->{projectId} = $projectId;
$props->{taskName} = $form->process("name","text");
$props->{duration} = $isMilestone? 0 : $form->process("duration","text");
$props->{lagTime} = $isMilestone? 0 : $form->process("lagTime","text");
$props->{startDate} = $form->process("start","date");
$props->{endDate} = ($isMilestone ? $props->{startDate} : $form->process("end","date"));
$props->{dependants} = $form->process("dependants","selectBox") unless $isMilestone;
@ -959,7 +969,6 @@ sub www_editTaskSave {
my $taskId = $self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId);
$self->deleteCollateral('PM_taskResource', 'taskId', $taskId);
foreach my $resourceSpec (@resourceSpecs) {
# Buggo, should probably factor the common SQL out of the loop
my ($resourceKind, $resourceId) = split / /, $resourceSpec, 2;
$self->setCollateral('PM_taskResource', 'taskResourceId', {taskId => $taskId, resourceKind => $resourceKind, resourceId => $resourceId}, 1, 0, 'taskId', $taskId);
}
@ -1018,10 +1027,10 @@ sub _arrangePredecessors {
my $seqNum = $task->{sequenceNumber};
#$eh->warn("Seq Num = $seqNum");
#Calculate initial duration in days and duration floor
my $durationInDays = $task->{duration};
$durationInDays = $durationInDays / $project->{hoursPerDay} if( $project->{durationUnits} == "hours" );
my $totalDurationInDays = $task->{duration} + $task->{lagTime};
$totalDurationInDays = $totalDurationInDays / $project->{hoursPerDay} if( $project->{durationUnits} eq "hours" );
#$eh->warn("Duration in Days: ".$durationInDays);
my $durationFloor = floor($durationInDays);
my $totalDurationFloor = floor($totalDurationInDays);
#$eh->warn("Duration Floor: ".$durationFloor);
#Skip the first record as it has no predecessors
@ -1044,8 +1053,8 @@ sub _arrangePredecessors {
#Change the start and end dates of the task
if($predDayPart > 0) {
#The previous task took up a part of a day. Add the additional day part to get the correct duration
$durationInDays += $predDayPart;
$durationFloor = floor($durationInDays);
$totalDurationInDays += $predDayPart;
$totalDurationFloor = floor($totalDurationInDays);
}
#$eh->warn("Duration in Days is now: ".$durationInDays);
@ -1057,7 +1066,7 @@ sub _arrangePredecessors {
$task->{startDate} = $predEndDate;
#$eh->warn("Start Date is now: ".$dt->epochToSet($task->{startDate}));
#Adjust end date for change in start date and update the hash
$task->{endDate} = $dt->addToDateTime($task->{startDate}, 0, 0, $durationFloor);
$task->{endDate} = $dt->addToDateTime($task->{startDate}, 0, 0, $totalDurationFloor);
#$eh->warn("End Date is now: ".$dt->epochToSet($task->{endDate})."\n\n");
#Update the database
$self->setCollateral("PM_task","taskId",$task,1,0,"projectId",$projectId);
@ -1066,7 +1075,7 @@ sub _arrangePredecessors {
}
#Adjust duration of days to only include the part of the day used
$durationInDays = $durationInDays - floor($durationInDays);
$totalDurationInDays = $totalDurationInDays - floor($totalDurationInDays);
#$eh->warn("Day Part left over is: $durationInDays \n\n");
@ -1075,7 +1084,7 @@ sub _arrangePredecessors {
'startDate'=>$task->{startDate},
'endDate'=>$task->{endDate},
'duration'=>$task->{duration},
'dayPart'=>$durationInDays
'dayPart'=>$totalDurationInDays
};
}
@ -1110,6 +1119,7 @@ sub www_saveExistingTasks {
$props->{dependants} = $form->process("dependants_$taskId","selectBox");
unless($isMilestone) {
$props->{duration} = $form->process("duration_$taskId","float");
$props->{lagTime} = $form->process("lagTime_$taskId","float");
}
$props->{lastUpdateDate} = $dt->time();
$props->{lastUpdatedBy} = $user->userId;
@ -1157,6 +1167,7 @@ sub www_viewProject {
$style->setScript($extras."/contextMenu/contextMenu.js",{ type=>"text/javascript" });
$style->setScript($extras."/calendar/lang/calendar-en.js",{ type=>"text/javascript" });
$style->setScript($extras."/calendar/calendar-setup.js",{ type=>"text/javascript" });
$style->setScript($assetExtras."/projectDisplay.js",{ type=>"text/javascript" });
$style->setScript($assetExtras."/taskEdit.js",{ type=>"text/javascript" });
#Get Project Data
@ -1198,6 +1209,7 @@ sub www_viewProject {
my $startDate = $dt->epochToSet($row->{startDate});
my $endDate = $dt->epochToSet($row->{endDate});
my $duration = $row->{duration};
my $lagTime = $row->{lagTime};
$hash->{'task.number'} = $seq;
$hash->{'task.row.id'} = "taskrow::$id";
@ -1207,6 +1219,7 @@ sub www_viewProject {
my $startId = "start_".$id."_formId";
my $endId = "end_".$id."_formId";
my $durId = "duration_".$id."_formId";
my $lagId = "lagTime_".$id."_formId";
my $predId = "dependants_".$id."_formId";
my $origStartField = "orig_start_$id";
my $origStartFieldId = $origStartField."_formId";
@ -1220,7 +1233,7 @@ sub www_viewProject {
-value=>$startDate,
-size=>"10",
-maxlength=>"10",
-extras=>qq<onfocus="doCalendar(this.id);" class="taskdate" onblur="adjustTaskTimeFromDate(this,document.getElementById('$endId'),document.getElementById('$durId'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');">
-extras=>qq<onfocus="doCalendar(this.id);" class="taskdate" onblur="adjustTaskTimeFromDate(this,document.getElementById('$endId'),document.getElementById('$durId'),document.getElementById('$lagId'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');">
});
$hash->{'task.start'} .= WebGUI::Form::hidden($session,{
@ -1243,10 +1256,10 @@ sub www_viewProject {
$hash->{'task.end'} = WebGUI::Form::text($session,{
-name=>"end_$id",
-value=>$endDate,
-size=>"10",
-maxlength=>"10",
-extras=>qq|class="taskdate" onfocus="doCalendar(this.id);" onblur="adjustTaskTimeFromDate(document.getElementById('$startId'),this,document.getElementById('$durId'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');"|
-value=>$endDate,
-size=>"10",
-maxlength=>"10",
-extras=>qq|class="taskdate" onfocus="doCalendar(this.id);" onblur="adjustTaskTimeFromDate(document.getElementById('$startId'),this,document.getElementById('$durId'),document.getElementById('$lagId'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');"|
});
$hash->{'task.end'} .= WebGUI::Form::hidden($session,{
-name=>$origEndField,
@ -1265,10 +1278,16 @@ sub www_viewProject {
$hash->{'task.duration'} = WebGUI::Form::float($session,{
-name=>"duration_$id",
-value=>$duration,
-extras=>qq|class="taskduration" onchange="adjustTaskTimeFromDuration(document.getElementById('$startId'),document.getElementById('$endId'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');" |
-extras=>qq|class="taskduration" onchange="adjustTaskTimeFromDuration(document.getElementById('$startId'),document.getElementById('$endId'),this,document.getElementById('$lagId'),false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');" |
});
}
$hash->{'task.lagTime'} = WebGUI::Form::hidden($session,{
-name => "lagTime_$id",
-value => $lagTime,
-extras=>qq|id="$lagId"|
});
} else {
$hash->{'task.duration'} = $duration;
$hash->{'task.start'} = $startDate;
@ -1496,20 +1515,37 @@ sub www_drawGanttChart {
my $startDate = $task->{startDate};
my $endDate = $task->{endDate};
my $duration = $task->{duration};
my $lagTime = $task->{lagTime};
my $totalDuration = $duration + $lagTime;
my $predecessor = $task->{dependants};
$self->_doGanttTaskResourceDisplay($hash, $task);
my $durationFloor = floor($duration);
$duration = $duration / $hoursPerDay if( $dunits == "hours" );
#Set duration to 1 day if it's a milestone
#$duration = 1 unless ($duration);
if ($dunits eq 'hours') {
foreach ($duration, $lagTime, $totalDuration) {
$_ /= $hoursPerDay;
}
}
my ($durationFloor, $lagTimeFloor, $totalDurationFloor) =
map {floor($_)} ($duration, $lagTime, $totalDuration);
#Each day is 23 pixels so calculate the days and round
unless ($duration) {
$hash->{'task.div.width'} = $pixelSize;
} else {
$hash->{'task.div.width'} = int(($duration * $pixelSize)) || 3;
$hash->{'task.div.width'} = int(($totalDuration * $pixelSize)) || 3;
}
# Lerp RGB: probably not the best way, but it's good enough.
my @zerolag_color = (0x7a, 0xb7, 0xe9);
my @alllag_color = (0xc8, 0x2f, 0xd2);
$hash->{'task.div.color'} = sprintf "#%02x%02x%02x",
($totalDuration > 0)? do {
my $lerp = $lagTime / $totalDuration;
map { $zerolag_color[$_] +
($alllag_color[$_] - $zerolag_color[$_]) * $lerp }
(0..2);
} : @zerolag_color;
$hash->{'task.div.label.left'} = $hash->{'task.div.width'} + 3;
@ -1522,8 +1558,10 @@ sub www_drawGanttChart {
$predDayPart = $pred->{dayPart};
if($startDate eq $predEndDate && $predDayPart > 0) {
#The previous task took up a part of the same day. Add the additional day part to get the correct duration
$duration += $predDayPart;
$durationFloor = floor($duration);
$duration += $predDayPart;
$totalDuration += $predDayPart;
$durationFloor = floor($duration);
$totalDurationFloor = floor($totalDuration);
}
}
@ -1567,16 +1605,17 @@ sub www_drawGanttChart {
$duration = $duration - floor($duration);
$hash->{'task.div.percentComplete'} = $task->{percentComplete} || 0;
$var->{'project.task.array'} .= qq|"$seq": { "id":"$id" ,"start":"$startDate" ,"end":"$endDate", "duration":"$rduration", "dayPart":"$duration", "predecessor":"$predecessor" }|;
$var->{'project.task.array'} .= qq|"$seq": { "id":"$id" ,"start":"$startDate" ,"end":"$endDate", "duration":"$rduration", "dayPart":"$duration", "lagTime":"$lagTime", "predecessor":"$predecessor" }|;
$taskCount++;
$taskHash->{$seq} = {
'startDate'=>$task->{startDate},
'endDate'=>$task->{endDate},
'duration'=>$task->{duration},
'dayPart'=>$duration
};
}
'startDate'=>$task->{startDate},
'endDate'=>$task->{endDate},
'duration'=>$task->{duration},
'lagTime'=>$task->{lagTime},
'dayPart'=>$duration
};
}
$var->{'task.count.loop'} = \@taskCount;
$var->{'task.div.loop'} = \@taskDiv;

View file

@ -299,7 +299,7 @@ our $I18N = {
},
'task greaterthan error' => {
message => q|End Date cannot be greater than Start Date|,
message => q|Start Date cannot be later than End Date|,
lastUpdated => 0
},

View file

@ -0,0 +1,414 @@
var dayMS = 86400000;
var popTitle = "Add/Edit Task";
// To be set by template vars
var dunits, hoursPerDay, taskLength;
var extrasPath, errorMsgs;
function doCalendar (fieldId) {
Calendar.setup({
inputField : fieldId,
ifFormat : "%Y-%m-%d",
showsTime : false,
step : 1,
timeFormat : "12",
firstDay : false
});
}
//--------------------------------------------------------------------------------------
function buildMenuUrl (urltype,taskId) {
if(urltype == "edit") {
alert("edit task: "+taskId);
} else if(urltype == "insertAbove") {
alert("insert task above: "+taskId);
} else if(urltype == "insertBelow") {
alert("insert task below: "+taskId);
} else if(urltype == "delete") {
alert("delete task: "+taskId);
}
}
//--------------------------------------------------------------------------------------
function closeImage() {
return extrasPath + '/close.gif';
}
//--------------------------------------------------------------------------------------
function configureMilestone(box) {
var form = box.form;
if(box.checked==true) {
form.end.value=form.start.value;
form.duration.value=0;
form.duration.disabled=true;
form.lagTime.value=0;
form.lagTime.disabled=true;
form.end.disabled=true;
form.dependants.disabled=true;
form.resource.disabled=true;
form.percentComplete.disabled=true;
form.percentComplete.value=0;
} else {
form.end.disabled=false;
form.duration.disabled=false;
form.lagTime.disabled=false;
form.dependants.disabled=false;
form.resource.disabled=false;
form.percentComplete.disabled=false;
form.duration.value = (dunits == "hrs")?hoursPerDay:1;
}
}
//--------------------------------------------------------------------------------------
function checkEditTaskForm (form) {
if(form.name.value == "") {
alert(errorMsgs.name);
return;
} else if(form.start.value == "") {
alert(errorMsgs.start);
return;
} else if(form.milestone.checked==false && form.end.value == "") {
alert(errorMsgs.end);
return;
}
form.submit();
}
//--------------------------------------------------------------------------------------
function intlDate(dateObj) {
return dateObj.getFullYear()+"-"+pad((dateObj.getMonth()+1))+"-"+pad(dateObj.getDate());
}
//--------------------------------------------------------------------------------------
function toDateObj(date) {
var to = date.split("-");
var dateObj = new Date(to[0],(to[1]-1),to[2],0,0,1,0);
return dateObj;
}
//--------------------------------------------------------------------------------------
function adjustTaskTimeFromDuration(start, end, duration, lagTime, isTaskForm, predecessor, origStart, origEnd, seqNum) {
//set the form element
var form = duration.form;
//get today's date
var today = new Date();
var todayIntl = intlDate(today);
//set start and end date if not already set
if(start.value == "") start.value = todayIntl;
if(end.value == "") end.value = todayIntl;
//Convert hours to days
var taskDuration = parseFloat(duration.value);
var taskTotalDuration = taskDuration + parseFloat(lagTime.value);
if(dunits == "hrs") taskTotalDuration = taskTotalDuration / hoursPerDay;
var totalDurationFloor = Math.floor(taskTotalDuration);
//Handle task form and main form seperately due to differences in the forms
if(isTaskForm && taskDuration <= 0) {
//Convert to milestone if task is less or equal to zero
if(confirm("Zero duration tasks are considered Milestones. Do you wish to change this task to a milestone?")) {
form.milestone.checked = true;
configureMilestone(form.milestone);
} else {
duration.value = form.orig_duration.value;
}
return;
} else if (taskDuration <= 0){
//Do not let users zero out tasks
alert("Zero duration tasks are considered Milestones. Please edit the task by clicking the link if you wish to change this task to a milestone");
return;
}
//create the start date
var aTo = start.value.split("-");
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
//add new duration days to the start date
toDate.setDate(toDate.getDate() + totalDurationFloor);
//set end date to this date
end.value = intlDate(toDate);
//Set new duration in taskArray
taskArray[seqNum]["duration"] = taskDuration;
if (seqNum == null)
alert("WARNING, WARNING!");
//Adjust time based on new end date
adjustTaskTimeFromDate(start, end, duration, lagTime, end, isTaskForm, predecessor, origStart, origEnd, seqNum);
}
//--------------------------------------------------------------------------------------
function adjustTaskTimeFromDate (start, end, duration, lagTime, element, isTaskForm, predecessor, origStart, origEnd, seqNum) {
//set the form element
var form = element.form;
//set original duration from task form to determine whether or not to continue to set duration
var orig_duration;
if(isTaskForm) {
if(form.milestone.checked == true) return;
orig_duration = form.orig_duration.value;
}
//Handle case where both start and end are empty
if(start.value == "" && end.value == "") {
//get today's date
var today = new Date();
var todayIntl = intlDate(today);
//set start and end date if not already set
start.value = todayIntl;
end.value = todayIntl;
}
//Handle case where one is set and the other isn't
if (end.value == "") end.value = start.value;
if(start.value == "") start.value = end.value;
if(isTaskForm && orig_duration == "") {
//Set duration if this is a new record
//Check to make sure start date comes before end date
var startcomp = start.value.replace(/-/g,"");
var endcomp = end.value.replace(/-/g,"");
if(startcomp > endcomp) {
alert(errorMsgs.greaterthan);
if(element.name == "start") {
end.value = element.value;
} else {
start.value = element.value;
}
duration.value = (dunits == "hrs")?hoursPerDay:1;
lagTime.value = 0;
return;
}
var d = getDaysInterval(start.value,end.value);
if(d == 0) d = 1;
if(dunits == "hrs") {
d = d * hoursPerDay;
}
duration.value = d - lagTime.value;
} else {
//Set start/end if duration has been saved
var d = parseFloat(duration.value) + parseFloat(lagTime.value);
if(dunits == "hrs") {
//Convert to days
d = d / hoursPerDay;
}
//Round off duration or set it to zero if less than 1;
//alert("d = " + d + " floor = " + Math.floor(d));
if(d < 1) d = 0;
else d = Math.floor(d);
if(element.name.indexOf("start") > -1) {
//create the date
var aTo = start.value.split("-");
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
//add duration days to the start date
toDate.setDate(toDate.getDate() + d);
//set end date to this date
end.value = intlDate(toDate);
} else if(element.name.indexOf("end") > -1) {
//create the date
var aFrom = end.value.split("-");
var fromDate = new Date(aFrom[0],(aFrom[1]-1),aFrom[2],0,0,1,0);
//subtract duration days from the end date
fromDate.setDate(fromDate.getDate() - d);
//set start date to this date
start.value = intlDate(fromDate);
}
}
//Check Predecessors before moving stuff
var pred = predecessor.value;
if(pred != "") {
//Check to make sure that the dependency requirement for this task is still valid
//Get the predecessor end date
var taskStart = toDateObj(start.value);
var predTaskEnd;
if(isTaskForm) {
predTaskEnd = toDateObj(taskArray[pred]["end"]);
} else {
var predTaskEndId = "end_"+taskArray[pred]["id"]+"_formId"
predTaskEnd = toDateObj(document.getElementById(predTaskEndId).value);
}
if(taskStart.getTime() < predTaskEnd.getTime()) {
alert(errorMsgs.invalidMove);
start.value = origStart.value;
end.value = origEnd.value;
return;
}
}
//Check all tasks past this one and move them forward if necessary (this only needs to happen on the main form)
if(!isTaskForm) {
arrangePredecessors(element,seqNum);
}
//reset orig start and end values
origStart.value = start.value;
origEnd.value = end.value;
if(!isTaskForm) {
//Adjust task form for
paintGanttChart();
}
}
//--------------------------------------------------------------------------------------
function trim(str) {
return str.replace(/^\s+|\s+$/, '');
}
//--------------------------------------------------------------------------------------
function arrangePredecessors (element,seqNum) {
for (var i = 1; i <= taskLength; i++) {
var seq = i;
var task = taskArray[seq];
var taskId = task["id"];
//Calculate duration and duraiton floor
var totalDurationInDays = parseFloat(task["duration"]) + parseFloat(task["lagTime"]);
if(dunits == "hrs") totalDurationInDays = totalDurationInDays / hoursPerDay;
var totalDurationFloor = Math.floor(totalDurationInDays);
//alert("Duration Floor is: "+durationFloor);
//Get the current elements
var currElementStart = document.getElementById("start_"+taskId+"_formId");
var currElementEnd = document.getElementById("end_"+taskId+"_formId");
//alert("Current Start Date: "+currElementStart.value+" Current End Date: "+currElementEnd.value);
//Skip the first record as it is the record that was changed
if(seq > 1) {
var predecessor = task["predecessor"];
//alert("predecessor for "+i+" is "+predecessor);
if(predecessor != "") {
var pred = taskArray[predecessor];
var predEndDate = toDateObj(pred["end"]);
var startDate = toDateObj(task["start"]);
//alert ("Pred End Date: "+intlDate(predEndDate));
//Make sure start date of this task is greater than the end date of the predecessor
if(startDate.getTime() <= predEndDate.getTime()) {
//Change the start and end dates of the task
//Get the day part of the predecessor
var predDayPart = parseFloat(pred["dayPart"]);
//alert("predDayPart: "+predDayPart);
if(predDayPart > 0) {
//The previous task took up part of a day. Add the additional day part to the duration
totalDurationInDays += predDayPart;
totalDurationFloor = Math.floor(totalDurationInDays);
}
//alert("Duration in Days: "+durationInDays+" Duration Floor: "+durationFloor);
//Set the start date of this task to the end date of the predecessor
currElementStart.value = pred["end"];
//Adjust end date for change in start date and update the object - start date is actually predEndDate now, so use the existing date object
predEndDate.setDate(predEndDate.getDate() + totalDurationFloor);
currElementEnd.value = intlDate(predEndDate);
//alert("Set seq "+i+" to start: "+pred["end"]+" end: "+intlDate(predEndDate));
}
}
}
//Update task array with new start/end values
taskArray[seq]["start"] = currElementStart.value;
taskArray[seq]["end"] = currElementEnd.value;
taskArray[seq]["dayPart"] = (totalDurationInDays - Math.floor(totalDurationInDays));
}
}
//--------------------------------------------------------------------------------------
function getDaysInterval(from,to) {
var aFrom = from.split("-");
var aTo = to.split("-");
var fromDate = new Date(aFrom[0],(aFrom[1]-1),aFrom[2],0,0,1,0);
var toDate = new Date(aTo[0],(aTo[1]-1),aTo[2],0,0,1,0);
var fromEpoch = fromDate.getTime();
var toEpoch = toDate.getTime();
var seconds = toEpoch - fromEpoch;
if(seconds == 0) return 0;
return (seconds/dayMS);
}
//--------------------------------------------------------------------------------------
function pad(date) {
var str = ""+date;
if(str.length == 1) {
str = "0"+str;
}
return str;
}
//--------------------------------------------------------------------------------------
function paintGanttChart () {
var status = AjaxRequest.submit(document.forms['editAll'],{
'onSuccess':function(req){ document.getElementById('gantt').innerHTML = req.responseText; }
});
var mwidth = document.getElementById("projectTableWidth").name + "px";
var swidth = document.getElementById("projectScrollPercentWidth").name + "%";
document.getElementById("mastertable").style.width=mwidth;
document.getElementById("scrolltd").style.width=swidth;
}
//--------------------------------------------------------------------------------------
function validateDependant(field,origField,seqNum,start,end,duration,lagTime,isTaskForm,origStart,origEnd) {
var pred = field.value;
var newTask = false;
if(pred != "") {
if(seqNum == "") seqNum = taskLength+1;
if(pred < 1) {
alert(errorMsgs.noPredecessor);
field.value=origField.value;
return;
}
if(pred == seqNum) {
alert(errorMsgs.samePredecessor);
field.value=origField.value;
return;
}
if(pred > seqNum) {
alert(errorMsgs.previousPredecessor);
field.value = origField.value;
return;
}
//Set defaults if it's a new record and one of the other options hasn't been checked.
if(start.value == "" || end.value == "") {
//get today's date
newTask = true;
duration.value = (dunits == "hrs")?hoursPerDay:1;
}
//Get the predecessor end date and decide where the new start date belongs
var taskStart = start.value;
var taskStartObj = toDateObj(taskStart);
var predTaskEnd = taskArray[pred]["end"];
var predTaskEndObj = toDateObj(predTaskEnd);
//Change start date if it comes before predecessor end date
if(newTask || (taskStartObj.getTime() < predTaskEndObj.getTime())) {
//Convert predecessor hours to days
var taskTotalDuration = parseFloat(duration.value) + parseFloat(lagTime.value);
if(dunits == "hrs") taskTotalDuration = taskTotalDuration / hoursPerDay;
var totalDurationFloor = Math.floor(taskTotalDuration);
//Get the predecessor dayPart
var predDayPart = parseFloat(pred["dayPart"]);
if(predDayPart > 0) {
//The previous task took up part of a day. Add the additional day part to the duration
taskTotalDuration += predDayPart;
totalDurationFloor = Math.floor(totalDurationInDays);
}
//Set the start date of this task to the end date of the predecessor
start.value = predTaskEnd;
//Adjust end date for change in start date
adjustTaskTimeFromDate(start,end,duration,lagTime,start,isTaskForm,field,origStart,origEnd,seqNum);
return;
}
}
//Repaint
if(!isTaskForm) {
//Set new predecessor in taskArray
taskArray[seqNum]["predecessor"] = pred;
paintGanttChart();
}
}