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 + + +
+ + +
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+
+
+ + +
Duration Duration   Lag Time  
  Is Milestone 
+ + + + + + + + + + + + + + + +
 
+
+ + 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(); + } +}