diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt
index de16c052e..ea10a4c70 100644
--- a/docs/changelog/7.x.x.txt
+++ b/docs/changelog/7.x.x.txt
@@ -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
diff --git a/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl b/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl
index b230e836a..52bc7a553 100644
--- a/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl
+++ b/docs/upgrades/templates-7.0.7/project_manager_edit_task.tmpl
@@ -26,8 +26,12 @@
-
+
| |
+
+ |
+
+ | |
|
diff --git a/docs/upgrades/templates-7.0.7/project_manager_gantt_chart.tmpl b/docs/upgrades/templates-7.0.7/project_manager_gantt_chart.tmpl
new file mode 100644
index 000000000..3665dc23a
--- /dev/null
+++ b/docs/upgrades/templates-7.0.7/project_manager_gantt_chart.tmpl
@@ -0,0 +1,47 @@
+#ProjectManagerTMPL0003
+
+
+
+
+
+ ♦
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+ | |
+
+
+
+
+
+
diff --git a/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl b/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl
index 2025a7fd1..57a5fc74b 100644
--- a/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl
+++ b/docs/upgrades/templates-7.0.7/project_manager_project_display.tmpl
@@ -2,417 +2,22 @@
@@ -458,6 +63,7 @@
|
|
|
+
| |
diff --git a/docs/upgrades/upgrade_7.0.6-7.0.7.pl b/docs/upgrades/upgrade_7.0.6-7.0.7.pl
index f5f8e0fdb..e6b724fc2 100644
--- a/docs/upgrades/upgrade_7.0.6-7.0.7.pl
+++ b/docs/upgrades/upgrade_7.0.6-7.0.7.pl
@@ -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 ----
#-------------------------------------------------
diff --git a/lib/WebGUI/Asset/Wobject/ProjectManager.pm b/lib/WebGUI/Asset/Wobject/ProjectManager.pm
index d1bfb4d74..90de3d243 100644
--- a/lib/WebGUI/Asset/Wobject/ProjectManager.pm
+++ b/lib/WebGUI/Asset/Wobject/ProjectManager.pm
@@ -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
+ -extras=>qq
});
$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;
diff --git a/lib/WebGUI/i18n/English/Asset_ProjectManager.pm b/lib/WebGUI/i18n/English/Asset_ProjectManager.pm
index e50ace60b..4730a170f 100644
--- a/lib/WebGUI/i18n/English/Asset_ProjectManager.pm
+++ b/lib/WebGUI/i18n/English/Asset_ProjectManager.pm
@@ -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
},
diff --git a/www/extras/wobject/ProjectManager/projectDisplay.js b/www/extras/wobject/ProjectManager/projectDisplay.js
new file mode 100644
index 000000000..1e31ba8fd
--- /dev/null
+++ b/www/extras/wobject/ProjectManager/projectDisplay.js
@@ -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();
+ }
+}