Add progressive tasks to the Task Manager; these are distinct from the

existing timed tasks and milestones in that they have a duration that
increases based on the amount of time tracked for them, and no set
completion time.  Nearby minor fixes.
This commit is contained in:
Drake 2006-10-02 22:33:00 +00:00
parent 51280b0b38
commit b8f173fefe
10 changed files with 818 additions and 320 deletions

View file

@ -11,7 +11,7 @@
uploaded when images are put in the form. (Martin Kamerbeek / Procolix)
- change: PM asset task editor now defaults start date to start of project
- Rearranged the autotag name creation to be easier to read.
- add: progressive (duration-tracked but untimed) tasks now possible in Project Manager
7.0.8
- Fixed a couple of minor bugs with the default values of the Request

View file

@ -0,0 +1,79 @@
#ProjectManagerTMPL0004
<tmpl_var form.header>
<table class="popUp" cellspacing="0" cellpadding="3" border="0">
<tbody>
<tr>
<td align="right" style="width:25%;" class="header">Task Name&nbsp;</td>
<td style="width:75%;"><tmpl_var form.name></td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clear">
<td style="width:25%;" align="right" class="header">Start&nbsp;</td>
<td style="width:25%;"><tmpl_var form.start></td>
<td style="width:25%;" align="right" class="header">Finish&nbsp;</td>
<td style="width:25%;"><tmpl_var form.end></td>
</tr><tr class="clear">
<td style="width:100%; text-align: center" colspan="4" class="header"><tmpl_var form.taskType></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clear">
<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>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clear">
<td style="width:25%;" align="right" class="header">Predecessor&nbsp;</td>
<td style="width:25%;"><tmpl_var form.dependants></td>
<td style="width:25%;" align="right" class="header">% Complete&nbsp;</td>
<td style="width:25%;"><tmpl_var form.percentComplete></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td colspan="2">
<table width="100%" cellpadding="3" cellspacing="0">
<tbody>
<tr class="clearNoBG" style="text-align:center;">
<td style="text-align:left;">
<span class="header">Resources:</span><br /><br />
<span style="margin-bottom:2px;padding-bottom:2px;padding-left:5px;-moz-box-sizing:border-box;display:block;">
<a style="text-decoration:none;" id="<tmpl_var form.addUser.id>" href="<tmpl_var form.addUser.link>" target="_new" onclick="taskEdit_searchPopup(this.href); return false;"><img style="border-style:none;vertical-align:middle;" title="<tmpl_var form.addUser.text>" alt="<tmpl_var form.addUser.text>" src="<tmpl_var assetExtras>/users.gif" /><span style="text-decoration:none;font-size:8pt;"> Add User To Task</span></a>&nbsp;&nbsp;&nbsp;&nbsp;
<a style="text-decoration:none;" id="<tmpl_var form.addGroup.id>" href="<tmpl_var form.addGroup.link>" target="_new" onclick="taskEdit_searchPopup(this.href); return false;"><img style="border-style:none;vertical-align:middle;" title="<tmpl_var form.addGroup.text>" alt="<tmpl_var form.addGroup.text>" src="<tmpl_var assetExtras>/groups.gif" /><span style="text-decoration:none;font-size:8pt;"> Add Group to Task</span></a>
</span><tmpl_var form.resourceDiv>
</td>
</tr>
<tr>
<td style="text-align:right;" class="header"><a href="javascript:void(checkEditTaskForm(document.editTaskForm))" class="saveBtn">Save</a></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<tmpl_var form.footer>

View file

@ -0,0 +1,43 @@
#ProjectManagerTMPL0003
<script type="text/javascript">
taskArray=<tmpl_var project.task.array>;
</script>
<div class="barPositions">
<tmpl_loop task.div.loop>
<tmpl_if task.isUntimed>
<div class="milestone" style="left:<tmpl_var task.div.left>px;top:<tmpl_var task.div.top>px;">&diams;</div>
</tmpl_if><tmpl_if task.hasDuration>
<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

@ -0,0 +1,274 @@
#ProjectManagerTMPL0002
<script type="text/javascript">
//<![CDATA[
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>",
'untimedPredecessor' : "<tmpl_var form.untimedPredecessor.error>"
};
addEvent(window, "load", initPopUp);
//]]>
</script>
<tmpl_var form.header>
<table cellpadding="0" cellspacing="0" border="0" class="project" id="mastertable" style="width:<tmpl_var project.table.width>px;">
<tbody>
<tr>
<td style="width:20px;height:20px;">&nbsp;</td>
<td style="width:300px;" align="center">Task Name</td>
<td style="width:70px;" align="center">Duration</td>
<td style="width:70px;" align="center">Start</td>
<td style="width:70px;" align="center">Finish</td>
<td style="width:30px;" align="center">Pred</td>
<td rowspan="<tmpl_var project.gantt.rowspan>" valign="top" id="scrolltd" style="border-style:none;width:<tmpl_var project.scroll.percentWidth>%;">
<div class="scroller" id="gantt">
<tmpl_var project.ganttChart>
</div>
</td>
</tr>
<tr>
<td colspan="6">&nbsp;</td>
</tr>
<tmpl_loop task.loop>
<tr id="<tmpl_var task.row.id>">
<td align="center" style="height:20px"><tmpl_var task.number></td>
<td align="left" style="height:20px">
<tmpl_if task.canAdd>
<script type="text/javascript">
//<![CDATA[
var cMenu = new cMenu_createWithLink("id_<tmpl_var task.number>","<tmpl_var task.name>");
cMenu.addLink("<tmpl_var task.edit.url>","<tmpl_var task.edit.label>");
cMenu.addLink("<tmpl_var task.insertAbove.url>","<tmpl_var task.insertAbove.label>");
cMenu.addLink("<tmpl_var task.insertBelow.url>","<tmpl_var task.insertBelow.label>");
cMenu.addLink("<tmpl_var task.delete.url>","<tmpl_var task.delete.label>");
cMenu.print();
//]]>
</script>
<!-- <a href="<tmpl_var task.edit.url>" class="submodal-400-350"><tmpl_var task.name></a> -->
<tmpl_else>
<tmpl_var task.name>
</tmpl_if>
</td>
<td align="center" style="height:20px"><tmpl_var task.duration> <tmpl_var task.duration.units></td>
<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>
<tr><td colspan="6" style="border-style:none;">&nbsp;</td></tr>
</tbody>
</table>
<tmpl_var form.footer>
<div id="links">
<!-- <tmpl_if project.canEdit><a href="<tmpl_var task.resources.url>"><tmpl_var task.resources.label></a>&nbsp;|&nbsp;</tmpl_if> -->
<tmpl_if task.canAdd><a href="<tmpl_var task.add.url>" class="submodal-400-300"><tmpl_var task.add.label></a>&nbsp;|&nbsp;</tmpl_if>
<a href="<tmpl_var task.back.url>"><tmpl_var task.back.label></a>
</div>
~~~
<style type="text/css">
body, html {
margin:0px;
padding:0px;
}
.project {
position:relative;
margin-top:5px;
margin-left:5px;
}
.project td {
border:solid silver 1px;
border-bottom:solid gray 1px;
font-size:9pt;
font-family:arial;
}
.project a {
color:#000000;
font-weight: normal;
font-size: 9pt;
text-decoration: none;
}
.project a:hover {
color:#7AB7E9;
font-weight: normal;
font-size: 9pt;
text-decoration: none;
}
.taskname {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 295px;
}
.taskduration {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 25px;
}
.taskdate {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 68px;
}
.taskdependant {
font-family: verdana;
font-size: 10px;
font-weight: normal;
color: black;
width: 20px;
}
tr.monthNames td {
text-align:center;
}
tr.dates td {
width:16px;
}
div.scroller {
overflow:scroll;
position:relative;
width:400px;
}
* html div.scroller {
overflow-x:scroll;
}
td.empty {
background-color:transparent;
border-style:none;
height:20px;
}
div.barPositions {
position:relative;
}
div.projectBar {
position:absolute;
height:10px;
z-index:2;
padding:0px;
margin:0px;
font-size:1pt;
}
div.statusBar {
background-color:#000;
position:absolute;
height:5px;
z-index:3;
padding:0px;
top:2px;
margin:0px;
font-size:1pt;
}
div.projectLineH {
background: url('images/dot.gif') top repeat-x;
font-size:1pt;
text-align:right;
position:absolute;
}
div.projectLineV {
width:1px;
background-color:black;
position:relative;
font-size:1pt;
height:100%;
float:right;
}
div.projectLabel {
position:absolute;
margin-top:-3px;
white-space:nowrap;
font-family: verdana;
font-size:8px;
}
div.milestone {
color:#000000;
position:absolute;
z-index:4;
margin:0px;
padding:0px;
margin-top:-3px;
font-size: 10px;
font-family: times;
}
#links {
margin-top:7px;
margin-left:5px;
font-family:arial;
font-size:9pt;
}
#links a {
color:#7AB7E9;
}
</style>
<style>
.popUp {
width:100%;
border:solid black 0px;
background-color:#F5F5F5;
}
.popUp td {
font-size:9pt;
font-family:arial;
border-top:solid #E0E0E0 1px;
}
.popUp tr.clear td {
border-style:none;
background-color:#F0F0F0;
}
.popUp tr.clearNoBG td {
border-style:none;
}
.popUp td.header {
font-weight:bold;
}
.popUp td span.header {
font-weight:bold;
}
a.saveBtn {
width:40px;
height:15px;
background-color:silver;
padding:1px;
padding-left:5px;
padding-right:5px;
color:white;
border:solid gray 1px;
text-decoration:none;
font-weight:bold;
display:block;
float:right;
text-align:center;
}
a.saveBtn:hover {
background-color:#F0F0F0;
color:gray;
}
</style>

View file

@ -19,7 +19,7 @@ my $quiet;
my $session = start();
# upgrade functions go here
pmAddProgressiveTasks($session);
finish($session);
@ -31,7 +31,26 @@ finish($session);
# # and here's our code
#}
sub pmAddProgressiveTasks {
my $session = shift;
print "\tMaking progressive tasks representable.\n" unless $quiet;
$session->db->write($_) for(<<'EOT',
ALTER TABLE PM_task
ADD COLUMN taskType enum('timed', 'progressive', 'milestone') NOT NULL
DEFAULT 'timed',
CHANGE COLUMN percentComplete percentComplete float NULL DEFAULT NULL
EOT
<<'EOT',
UPDATE PM_task SET taskType = 'milestone'
AND percentComplete = NULL
WHERE isMilestone = 1
EOT
<<'EOT',
ALTER TABLE PM_task
DROP COLUMN isMilestone
EOT
);
}
# ---- DO NOT EDIT BELOW THIS LINE ----

View file

@ -175,7 +175,7 @@ sub getProjectList {
my $userId = $_[0];
my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups};
my $groupIdQuery = @groupIds?
('PM_taskResource.resourceId IN ('.join(',', map{'?'} @groupIds).')') : '0';
('PM_taskResource.resourceId IN ('.join(',', ('?') x @groupIds).')') : '0';
$self->session->db->buildHashRef(<<"SQL", [$userId, @groupIds]);
SELECT DISTINCT PM_project.projectId, PM_project.name
@ -196,7 +196,7 @@ sub getTaskList {
my $userId = $_[1];
my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups};
my $groupIdQuery = @groupIds?
('PM_taskResource.resourceId IN ('.join(',', map{'?'} @groupIds).')') : '0';
('PM_taskResource.resourceId IN ('.join(',', ('?') x @groupIds).')') : '0';
$self->session->db->buildHashRef(<<"SQL", [$projectId, $userId, @groupIds]);
SELECT DISTINCT PM_task.taskId, PM_task.taskName
@ -209,33 +209,42 @@ SQL
}
#-------------------------------------------------------------------
#API method called by Time Tracker to set percent complete field in the task and update the project cache
# API method called by Time Tracker to set percent complete field in the task and update the project cache
sub updateProjectTask {
my $self = shift;
my $db = $self->session->db;
my $eh = $self->session->errorHandler;
my $self = shift;
my $db = $self->session->db;
my $eh = $self->session->errorHandler;
my $taskId = $_[0];
my $projectId = $_[1];
my $totalHours = $_[2];
my $taskId = $_[0];
my $projectId = $_[1];
my $deltaHours = $_[2];
return 0 unless ($taskId && $projectId && $deltaHours);
$eh->warn("taskId: $taskId ~~ projectId: $projectId ~~ totalHours: $totalHours");
return 0 unless ($taskId && $projectId && $totalHours);
my $task = $self->getCollateral('PM_task', 'taskId', $taskId);
my ($units,$hoursPerDay) = $db->quickArray("select durationUnits, hoursPerDay from PM_project where projectId=?",[$projectId]);
return 0 unless ($task->{taskId});
if ($task->{taskType} eq 'milestone') {
return 0;
} elsif ($task->{taskType} eq 'progressive') {
my $deltaDuration = ($units eq 'days')? ($deltaHours / $hoursPerDay) : $deltaHours;
$task->{duration} += $deltaDuration;
$task->{endDate} += $deltaDuration * 3600;
$task->{duration} = 0 if $task->{duration} < 0;
$task->{endDate} = $task->{startDate} if $task->{endDate} < $task->{startDate};
# Don't need to consider dependants here because nothing is allowed
# to depend on a progressive task.
} else {
my $durationHours = ($units eq 'days')? ($task->{duration} * $hoursPerDay) : $task->{duration};
$task->{percentComplete} += ($deltaHours / $durationHours) * 100;
$task->{percentComplete} = 0 if $task->{percentComplete} < 0;
$task->{percentComplete} = 100 if $task->{percentComplete} > 100;
}
my $task = $db->quickHashRef("select * from PM_task where taskId=?",[$taskId]);
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 * $hoursPerDay;
}
my $percentComplete = ($totalHours / $duration) * 100;
$self->setCollateral("PM_task","taskId",{ taskId=>$taskId, percentComplete=>$percentComplete });
$self->_updateProject($projectId);
return 1;
$self->setCollateral("PM_task","taskId", { taskId=>$taskId, duration=>$task->{duration}, percentComplete=>$task->{percentComplete} });
$self->_updateProject($projectId);
return 1;
}
#-------------------------------------------------------------------
@ -263,26 +272,21 @@ sub _userCanManageProjectList {
#-------------------------------------------------------------------
sub _updateProject {
my $self = shift;
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $eh = $session->errorHandler;
my $projectId= $_[0];
my $self = shift;
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $projectId= $_[0];
my ($minStart) = $db->quickArray("select min(startDate) from PM_task where projectId=".$db->quote($projectId));
my ($maxEnd) = $db->quickArray("select max(endDate) from PM_task where projectId=".$db->quote($projectId));
my ($projectTotal) = $db->quickArray("select sum(duration) from PM_task where projectId=".$db->quote($projectId));
my $complete = 0;
my $tasks = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=".$db->quote($projectId)." order by sequenceNumber asc");
foreach my $task (@{$tasks}) {
$complete += ($task->{duration} * ($task->{percentComplete}/100));
}
my $projectComplete = ($projectTotal == 0)?0:(($complete / $projectTotal) * 100);
$db->write("update PM_project set startDate=?, endDate=?, percentComplete=? where projectId=?",[$minStart,$maxEnd,$projectComplete,$projectId]);
my ($minStart, $maxEnd) = $db->quickArray("select min(startDate), max(endDate) from PM_task where projectId=?", [$projectId]);
my ($projectTotal, $complete) = 0;
my $tasks = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=? and taskType <> 'timed' order by sequenceNumber asc", [$projectId]);
foreach my $task (@{$tasks}) {
$projectTotal += $task->{duration};
$complete += ($task->{duration} * ($task->{percentComplete}/100));
}
my $projectComplete = ($projectTotal == 0)? 0 : (($complete / $projectTotal) * 100);
$db->write("update PM_project set startDate=?, endDate=?, percentComplete=? where projectId=?",[$minStart,$maxEnd,$projectComplete,$projectId]);
}
#-------------------------------------------------------------------
@ -420,7 +424,7 @@ sub www_deleteTask {
#Set Local Vars
my $taskId = $form->get("taskId");
my $task = $db->quickHashRef("select * from PM_task where taskId=?",[$taskId]);
my $task = $self->getCollateral('PM_task', 'taskId', $taskId);
my $projectId = $task->{projectId};
my $taskRank = $task->{sequenceNumber};
@ -619,7 +623,7 @@ sub www_editProjectSave {
$props->{lagTime} = 0;
$props->{startDate} = $dt->time();
$props->{endDate} = $dt->time();
$props->{isMilestone} = 1;
$props->{taskType} = 'milestone';
$props->{creationDate} = $now;
$props->{createdBy} = $uid;
$props->{lastUpdateDate} = $now;
@ -723,7 +727,7 @@ sub _userSearchQuery {
my $exclude = shift;
my $searchPattern = lc('%'.shift().'%');
my @exclude = ('1', '3', split /\;/, $exclude);
my $excludePlaceholders = '('.join(',', map{'?'} @exclude).')';
my $excludePlaceholders = '('.join(',', ('?') x @exclude).')';
my $query = <<"SQL";
SELECT 'user' AS resourceKind, users.userId AS resourceId
@ -756,7 +760,7 @@ sub _groupSearchQuery {
my $exclude = shift;
my $searchPattern = lc('%'.shift().'%');
my @exclude = ('1', '7', split /\;/, $exclude);
my $excludePlaceholders = '('.join(',', map{'?'} @exclude).')';
my $excludePlaceholders = '('.join(',', ('?') x @exclude).')';
my $query = <<"SQL";
SELECT 'group' AS resourceKind, groups.groupId AS resourceId
FROM groups
@ -812,15 +816,17 @@ sub www_editTask {
my $projectId = $form->get("projectId");
my $taskId = $form->get("taskId") || "new";
my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId));
my $task = $db->quickHashRef("select * from PM_task where taskId=".$db->quote($taskId));
my $project = $self->getCollateral('PM_project', 'projectId', $projectId);
my $task = $self->getCollateral('PM_task', 'taskId', $taskId);
#Check Privileges
return $privilege->insufficient unless $self->_userCanManageProject($user, $projectId);
my $isMilestone = $task->{isMilestone};
my $taskType = ($task->{taskType} || 'timed');
my $seq = $task->{sequenceNumber};
my $extras = ($isMilestone)?" disabled":"";
my $disabledIfUntimed = ($taskType eq 'timed')? "" : " disabled";
my $disabledIfMilestone = ($taskType ne 'milestone')? "" : " disabled";
$var->{'form.header'} = WebGUI::Form::formHeader($session,{
action=>$self->getUrl,
extras=>q|name="editTaskForm"|
@ -871,16 +877,16 @@ 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,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|
-name => "duration",
-value => (($taskType ne 'milestone')? $task->{duration} : 'N/A'),
-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') }" $disabledIfMilestone|
});
$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|
-value => (($taskType eq 'timed')? $task->{lagTime} : 'N/A'),
-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') }" $disabledIfUntimed|
});
$var->{'form.lagTime.units'} = $self->_getDurationUnitHashAbbrev->{$project->{durationUnits}};
@ -889,7 +895,7 @@ sub www_editTask {
-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.form.lagTime,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(getTaskType(this.form) == 'milestone') 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,{
@ -897,7 +903,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.form.lagTime,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');" $disabledIfUntimed|
});
$var->{'form.dependants'} = WebGUI::Form::integer($session,{
@ -932,16 +938,23 @@ sub www_editTask {
$var->{'form.resourceDiv'} =
'<div id="taskEdit_resourceList_div">'.$self->_innerHtmlOfResources(@resources).'</div>';
$var->{'form.milestone'} = WebGUI::Form::checkbox($session, {
-name=>"milestone",
-value=>1,
-checked=>$task->{isMilestone},
-extras=>q|onclick="configureMilestone(this)"|
my %taskTypeOptions;
tie %taskTypeOptions, 'Tie::IxHash';
foreach my $option (qw/timed progressive milestone/) {
$taskTypeOptions{$option} = $i18n->get("taskType $option label");
}
$var->{'form.taskType'} = WebGUI::Form::radioList($session, {
-name=>'taskType',
-vertical=>0,
-options=>\%taskTypeOptions,
-defaultValue=>$taskType,
-extras=>q|onchange="configureForTaskType(this.form)"|,
});
$var->{'form.percentComplete'} = WebGUI::Form::float($session, {
-name=>"percentComplete",
-value=>$task->{percentComplete},
-extras=>$extras
-name => "percentComplete",
-value => (($taskType eq 'timed')? $task->{percentComplete} : 'N/A'),
-extras => $disabledIfUntimed
});
$var->{'form.save'} = WebGUI::Form::submit($session, {
-value=>"Save",
@ -969,37 +982,46 @@ sub www_editTaskSave {
#Check Privileges
return $privilege->insufficient unless $self->_userCanManageProject($user, $projectId);
my $isMilestone = $form->process("milestone","checkbox");
my $taskType = $form->process("taskType", "radioList");
my ($isMilestone, $isUntimed) = ($taskType eq 'milestone', $taskType ne 'timed');
my $isProgressive = ($taskType eq 'progressive');
my $props = {};
$props->{taskId} = $form->process("taskId","hidden");
$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->{lagTime} = $isUntimed? 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;
$props->{isMilestone} = $isMilestone || 0;
$props->{endDate} = $isMilestone? $props->{startDate} :
$isProgressive? undef : $form->process("end","date");
$props->{dependants} = $form->process("dependants","selectBox") unless $isUntimed;
$props->{taskType} = $taskType;
my @resourceSpecs = $form->process("resources","hiddenList");
$props->{percentComplete} = $isMilestone? 0 : $form->process("percentComplete","float");
$props->{percentComplete} = $isUntimed? 0 : $form->process("percentComplete","float");
unless (defined $props->{endDate}) {
my $totalDuration = $props->{duration} + $props->{lagTime};
my $totalDurationDays = ($project->{durationUnits} eq 'days')? $totalDuration : ($totalDuration / $project->{hoursPerDay});
$props->{endDate} = $props->{startDate} + $totalDurationDays*86400;
}
my $now = $dt->time();
if($props->{taskId} eq "new") {
$props->{creationDate} = $now;
$props->{createdBy} = $user->userId;
$props->{creationDate} = $now;
$props->{createdBy} = $user->userId;
}
$props->{lastUpdateDate} = $now;
$props->{lastUpdatedBy} = $user->userId;
#Save the extended task data
# Save the extended task data
my $taskId = $self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId);
$self->deleteCollateral('PM_taskResource', 'taskId', $taskId);
foreach my $resourceSpec (@resourceSpecs) {
my ($resourceKind, $resourceId) = split / /, $resourceSpec, 2;
$self->setCollateral('PM_taskResource', 'taskResourceId', {taskId => $taskId, resourceKind => $resourceKind, resourceId => $resourceId}, 1, 0, 'taskId', $taskId);
}
#Reorder tasks if task is inserted
my $insertAt = $form->get("insertAt");
if($insertAt) {
@ -1028,94 +1050,74 @@ sub www_editTaskSave {
$self->reorderCollateral("PM_task","taskId","projectId",$projectId);
}
$self->_updateProject($projectId);
$self->_arrangePredecessors($project,$taskId);
$self->_updateProject($projectId);
$self->_clobberImproperDependants($projectId);
$self->_updateDependantDates($projectId);
return $self->www_viewProject($projectId,$taskId);
}
#-------------------------------------------------------------------
sub _arrangePredecessors {
my $self = shift;
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $eh = $session->errorHandler;
my ($project,$taskId) = @_;
my $projectId = $project->{projectId};
sub _updateDependantDates {
my $self = shift;
my $db = $self->session->db;
my $dt = $self->session->datetime;
my ($projectId) = @_;
my $project = $self->getCollateral('PM_project', 'projectId', $projectId);
my $tasks = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=?",[$projectId]);
my $seq = 0;
if($taskId) {
$seq = "(select sequenceNumber from PM_task where taskId=".$db->quote($taskId).")";
}
my $tasks = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=?",[$projectId]);
my $taskHash = {};
foreach my $task (@{$tasks}) {
my $seqNum = $task->{sequenceNumber};
#$eh->warn("Seq Num = $seqNum");
#Calculate initial duration in days and duration floor
my $totalDurationInDays = $task->{duration} + $task->{lagTime};
$totalDurationInDays = $totalDurationInDays / $project->{hoursPerDay} if( $project->{durationUnits} eq "hours" );
#$eh->warn("Duration in Days: ".$durationInDays);
my $totalDurationFloor = floor($totalDurationInDays);
#$eh->warn("Duration Floor: ".$durationFloor);
#Skip the first record as it has no predecessors
#next if (scalar(keys %{$taskHash})) == 1;
if( scalar(keys %{$taskHash} ) > 0 ) {
#If the task has a predecessor, ensure that it starts are the right time.
my $predecessor = $task->{dependants};
#$eh->warn("Predecessor is: ".$predecessor);
if($predecessor) {
#Get the predecessor task data - since predecessors come before this task, it should be in the taskHash
my $pred = $taskHash->{$predecessor};
my $predEndDate = $pred->{endDate};
#$eh->warn("Task Start Date: ".$dt->epochToSet($task->{startDate}));
#$eh->warn("Pred End Date: ".$dt->epochToSet($predEndDate));
#Get the day part of the predecessor
my $predDayPart = $pred->{dayPart};
#$eh->warn("Pred Day Part: ".$predDayPart);
#Make sure start date of this task is greater than the end date of the predecessor
if($task->{startDate} <= $predEndDate) {
#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
$totalDurationInDays += $predDayPart;
$totalDurationFloor = floor($totalDurationInDays);
}
my $taskHash = {};
foreach my $task (@{$tasks}) {
my $seqNum = $task->{sequenceNumber};
# Calculate initial duration in days and duration floor for this task.
my $totalDurationInDays = $task->{duration} + $task->{lagTime};
$totalDurationInDays = $totalDurationInDays / $project->{hoursPerDay} if( $project->{durationUnits} eq "hours" );
my $totalDurationFloor = floor($totalDurationInDays);
# If we have a predecessor, check against it.
if (my $predecessor = $task->{dependants}) {
my $pred = $taskHash->{$predecessor};
unless ($pred) {
# Predecessor has to have a lower sequence number, right? Right?
$self->session->errorHandler->error("Internal: predecessor '$predecessor' of task with seqNum '$seqNum' not in task hash?!");
next;
}
# Need to fix the dates iff we intersect our predecessor.
if ($task->{startDate} <= $pred->{endDate}) {
# Update for fractional day part.
# Buggo: why?
$totalDurationInDays += $pred->{dayPart};
$totalDurationFloor = floor($totalDurationInDays);
#$eh->warn("Duration in Days is now: ".$durationInDays);
#$eh->warn("Duration Floor is now: ".$durationFloor);
#$eh->warn("Pred End Date is now: ".$dt->epochToSet($predEndDate));
#Set the start date of this task to the end date of the predecessor and update the hash
$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, $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);
}
}
}
# Set start and end dates of the current task.
$task->{startDate} = $pred->{endDate};
$task->{endDate} = $dt->addToDateTime($task->{startDate}, 0, 0, $totalDurationFloor);
# Update.
$self->setCollateral("PM_task","taskId",$task,1,0,"projectId",$projectId);
}
}
#Adjust duration of days to only include the part of the day used
$totalDurationInDays = $totalDurationInDays - floor($totalDurationInDays);
#$eh->warn("Day Part left over is: $durationInDays \n\n");
#add this task to the taskHash
$taskHash->{$seqNum} = {
'startDate'=>$task->{startDate},
'endDate'=>$task->{endDate},
'duration'=>$task->{duration},
'dayPart'=>$totalDurationInDays
};
}
return;
# Extract fractional day part.
my $totalDurationInDaysFrac = $totalDurationInDays - floor($totalDurationInDays);
$taskHash->{$seqNum} = {
'startDate'=>$task->{startDate},
'endDate'=>$task->{endDate},
'duration'=>$task->{duration},
'dayPart'=>$totalDurationInDaysFrac
};
}
}
sub _clobberImproperDependants {
my $self = shift;
my $projectId = shift;
my @nondependTaskIds = $self->session->db->buildArray("SELECT sequenceNumber FROM PM_task WHERE projectId = ? AND taskType <> 'timed'", [$projectId]);
return unless @nondependTaskIds;
$self->session->db->write("UPDATE PM_task SET dependants = NULL WHERE projectId = ? AND dependants IN (".join(', ', ('?') x @nondependTaskIds).")", [$projectId, @nondependTaskIds]);
}
#-------------------------------------------------------------------
@ -1136,27 +1138,28 @@ sub www_saveExistingTasks {
#Save each row
foreach my $task (@{$tasks}) {
my $isMilestone = $task->{isMilestone};
my $props = {};
my $taskId = $task->{taskId};
$props->{taskId} = $taskId;
$props->{projectId} = $projectId;
$props->{startDate} = $form->process("start_$taskId","date");
$props->{endDate} = $form->process("end_$taskId","date");
$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;
$self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId);
my $taskType = $task->{taskType};
my $isUntimed = ($taskType ne 'timed');
my $props = {};
my $taskId = $task->{taskId};
$props->{taskId} = $taskId;
$props->{projectId} = $projectId;
$props->{startDate} = $form->process("start_$taskId","date");
$props->{endDate} = $form->process("end_$taskId","date");
$props->{dependants} = $form->process("dependants_$taskId","selectBox");
unless ($isUntimed) {
$props->{duration} = $form->process("duration_$taskId","float");
$props->{lagTime} = $form->process("lagTime_$taskId","float");
}
$props->{lastUpdateDate} = $dt->time();
$props->{lastUpdatedBy} = $user->userId;
$self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId);
}
#Rearrange predecessors
#$self->_arrangePredecessors($project);
$self->_updateProject($projectId);
$self->_updateDependantDates($projectId);
return $self->www_drawGanttChart();
}
@ -1188,7 +1191,7 @@ sub www_viewProject {
$style->setLink($assetExtras."/cMenu.css",{rel=>"stylesheet",type=>"text/css"});
$style->setScript($assetExtras."/cMenu.js",{ type=>"text/javascript" });
$style->setScript($extras."/js/at/AjaxRequest.js",{ type=>"text/javascript" });
$style->setScript($extras."/js/at/AjaxRequest.js",{ type=>"text/javascript" });
$style->setScript($extras."/js/modal/modal.js",{ type=>"text/javascript" });
$style->setScript($extras."/calendar/calendar.js",{ type=>"text/javascript" });
$style->setScript($extras."/contextMenu/contextMenu.js",{ type=>"text/javascript" });
@ -1215,7 +1218,7 @@ sub www_viewProject {
$var->{'task.end.label'} = $i18n->get("task end label");
$var->{'task.dependants.label'} = $i18n->get("task dependant label");
#JavaScript Alert Errors for Tasks
# JavaScript Alert Errors for Tasks
$var->{'form.name.error'} = $i18n->get("task name error");
$var->{'form.start.error'} = $i18n->get("task start error");
$var->{'form.end.error'} = $i18n->get("task end error");
@ -1224,6 +1227,7 @@ sub www_viewProject {
$var->{'form.samePredecessor.error'} = $i18n->get("task samePredecessor error");
$var->{'form.noPredecessor.error'} = $i18n->get("task noPredecessor error");
$var->{'form.invalidMove.error'} = $i18n->get("task invalidMove error");
$var->{'form.untimedPredecessor.error'} = $i18n->get("task untimedPredecessor error");
my @taskList = ();
my $count = 0;
@ -1231,7 +1235,7 @@ sub www_viewProject {
my $hash = {};
my $seq = $row->{sequenceNumber};
my $id = $row->{taskId};
my $isMilestone = $row->{isMilestone} || 0;
my $isUntimed = ($row->{taskType} ne 'timed');
my $startDate = $dt->epochToSet($row->{startDate});
my $endDate = $dt->epochToSet($row->{endDate});
my $duration = $row->{duration};
@ -1242,25 +1246,25 @@ sub www_viewProject {
$hash->{'task.name'} = $row->{taskName};
if($canEditTasks) {
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";
my $origDepField = "orig_dependants_$id";
my $origDepFieldId = $origDepField."_formId";
my $origEndField = "orig_end_$id";
my $origEndFieldId = $origEndField."_formId";
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";
my $origDepField = "orig_dependants_$id";
my $origDepFieldId = $origDepField."_formId";
my $origEndField = "orig_end_$id";
my $origEndFieldId = $origEndField."_formId";
$hash->{'task.start'} = WebGUI::Form::text($session,{
-name=>"start_$id",
-value=>$startDate,
-size=>"10",
-maxlength=>"10",
-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::text($session,{
-name=>"start_$id",
-value=>$startDate,
-size=>"10",
-maxlength=>"10",
-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,{
-name=>$origStartField,
@ -1272,7 +1276,7 @@ sub www_viewProject {
-name=>"dependants_$id",
-value=>$row->{dependants} || "",
-defaultValue=>"",
-extras=>qq|class="taskdependant" onchange="validateDependant(this,document.getElementById('$origDepFieldId'),'$seq',document.getElementById('$startId'),document.getElementById('$endId'),document.getElementById('$durId'),false,document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'));"|
-extras=>qq|class="taskdependant" onchange="validateDependant(this,document.getElementById('$origDepFieldId'),'$seq',document.getElementById('$startId'),document.getElementById('$endId'),document.getElementById('$durId'),document.getElementById('$lagId'),false,document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'));"|
});
$hash->{'task.dependants'} .= WebGUI::Form::hidden($session,{
-name=>$origDepField,
@ -1292,8 +1296,8 @@ sub www_viewProject {
-value=>$endDate,
-extras=>qq|id="$origEndFieldId"|
});
#Don't display uneditable fields if the task is a milestone.
if($isMilestone) {
# Don't display duration for untimed tasks.
if ($isUntimed) {
$hash->{'task.duration'} = $row->{duration};
$hash->{'task.duration'} .= WebGUI::Form::hidden($session,{
-name=>"duration_$id",
@ -1301,7 +1305,7 @@ sub www_viewProject {
-extras=>qq|id="$durId"|
});
} else {
$hash->{'task.duration'} = WebGUI::Form::float($session,{
$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,document.getElementById('$lagId'),false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),'$seq');" |
@ -1321,7 +1325,7 @@ sub www_viewProject {
$hash->{'task.dependants'} = $row->{dependants} || "&nbsp;";
}
$hash->{'task.duration.units'} = $dunits;
$hash->{'task.isMilestone'} = "true" if($isMilestone);
$hash->{'task.taskType'} = $row->{taskType};
if($canEditTasks) {
$hash->{'task.edit.url'} = $self->getUrl("func=editTask;projectId=$projectId;taskId=".$row->{taskId});
$hash->{'task.edit.label'} = $i18n->get("edit task label");
@ -1366,16 +1370,16 @@ sub www_viewProject {
$var->{'task.add.url'} = $self->getUrl("func=editTask;projectId=$projectId;taskId=new");
$var->{'task.canAdd'} = "true";
}
#Rowspan of gantt chart is 4 plus number of tasks
# Rowspan of gantt chart is 4 plus number of tasks
$var->{'project.gantt.rowspan'} = 4 + $taskLength;
$var->{'project.ganttChart'} = $self->www_drawGanttChart($projectId, $data, $var);
$var->{'task.back.label'} = $i18n->get("task back label");
$var->{'task.back.url'} = $self->getUrl;
return $style->process($self->processTemplate($var,$self->getValue("projectDisplayTemplateId")),$self->getValue("styleTemplateId"));
}
@ -1539,6 +1543,7 @@ sub www_drawGanttChart {
my $hash = {};
my $id = $task->{taskId};
my $seq = $task->{sequenceNumber};
my $taskType = $task->{taskType};
my $startDate = $task->{startDate};
my $endDate = $task->{endDate};
my $duration = $task->{duration};
@ -1556,12 +1561,8 @@ sub www_drawGanttChart {
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(($totalDuration * $pixelSize)) || 3;
}
# Each day is 23 pixels so calculate the days and round
$hash->{'task.div.width'} = int(($totalDuration * $pixelSize));
# Lerp RGB: probably not the best way, but it's good enough.
my @zerolag_color = (0x7a, 0xb7, 0xe9);
@ -1573,8 +1574,9 @@ sub www_drawGanttChart {
($alllag_color[$_] - $zerolag_color[$_]) * $lerp }
(0..2);
} : @zerolag_color;
$hash->{'task.div.label.left'} = $hash->{'task.div.width'} + 3;
$hash->{'task.div.label.left'} = 12 if $hash->{'task.div.label.left'} < 12;
my $predDayPart = 0;
my $predEndDate = "";
@ -1618,7 +1620,9 @@ sub www_drawGanttChart {
$hash->{'task.div.left'} = int(($daysLeft * $pixelSize)); #Each day is 23 pixels so calculate the days and round
#$eh->warn("Starts at: $daysLeft * $pixelSize :".$hash->{'task.div.left'});
$hash->{'task.isMilestone'} = $task->{isMilestone};
# Buggo. Refactor.
$hash->{'task.isUntimed'} = ($task->{taskType} ne 'timed');
$hash->{'task.hasDuration'} = ($task->{taskType} ne 'milestone');
push(@taskDiv, $hash);
push(@taskCount, { 'task.counter' => $task->{sequenceNumber} } );
@ -1632,7 +1636,7 @@ 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", "lagTime":"$lagTime", "predecessor":"$predecessor" }|;
$var->{'project.task.array'} .= qq|"$seq": { "id":"$id" ,"start":"$startDate" ,"end":"$endDate", "duration":"$rduration", "dayPart":"$duration", "lagTime":"$lagTime", "predecessor":"$predecessor", "type":"$taskType" }|;
$taskCount++;
$taskHash->{$seq} = {

View file

@ -199,7 +199,7 @@ sub view {
#-------------------------------------------------------------------
sub www_editTimeEntrySave {
my $self = shift;
my ($session,$privilege,$form,$db,$user,$eh,$dt) = $self->getSessionVars("privilege","form","db","user","errorHandler","datetime");
my ($session,$privilege,$form,$db,$user,$eh,$dt) = $self->getSessionVars("privilege","form","db","user","errorHandler","datetime");
return $privilege->insufficient unless ($self->canView);
@ -214,53 +214,56 @@ sub www_editTimeEntrySave {
$props->{endDate} = $form->process("endDate","hidden");
$props->{reportComplete} = $form->process("isComplete","checkbox") || 0;
$props->{resourceId} = $form->process("resourceId","hidden");
if($reportId eq "new") {
$props->{creationDate} = $now;
$props->{createdBy} = $currUser;
if ($reportId eq "new") {
$props->{creationDate} = $now;
$props->{createdBy} = $currUser;
}
$props->{lastUpdatedBy} = $currUser;
$props->{lastUpdateDate} = $now;
$reportId = $self->setCollateral("TT_report","reportId",$props,0,1);
my @tasks = ();
my $tasksToUpdate = {};
my %deltaHours = ();
foreach my $entry (@{$db->buildArrayRefOfHashRefs("SELECT * FROM TT_timeEntry WHERE reportId = ?", [$reportId])}) {
$deltaHours{$entry->{projectId}}{$entry->{taskId}} -= $entry->{hours};
}
my $rowTotal = $form->get("rowTotal");
for(my $i = 1; $i <= $rowTotal; $i++) {
my $taskEntryId = $form->get("taskEntryId_$i");
next unless ($taskEntryId);
$props = {};
$props->{entryId} = $taskEntryId;
$props->{reportId} = $reportId;
$props->{taskDate} = $form->process("taskDate_$i","selectBox");
$props->{projectId} = $form->process("projectId_$i","selectBox");
$props->{taskId} = $form->process("taskId_$i","selectBox");
$props->{hours} = $form->process("hours_$i","float");
$props->{comments} = $form->process("comments_$i","text");
if ($props->{taskDate} && $props->{projectId} && $props->{taskId} && $props->{hours}) {
$taskEntryId = $self->setCollateral("TT_timeEntry","entryId",$props,0,0);
push(@tasks,$taskEntryId);
$tasksToUpdate->{$taskEntryId} = { taskId=>$props->{taskId}, projectId=>$props->{projectId} };
}
my @entryIds = ();
for (my $i = 1; $i <= $rowTotal; $i++) {
my $entryId = $form->get("taskEntryId_$i");
next unless $entryId;
my $props = {};
$props->{entryId} = $entryId;
$props->{reportId} = $reportId;
$props->{taskDate} = $form->process("taskDate_$i","selectBox");
$props->{projectId} = $form->process("projectId_$i","selectBox");
$props->{taskId} = $form->process("taskId_$i","selectBox");
$props->{hours} = $form->process("hours_$i","float");
$props->{comments} = $form->process("comments_$i","text");
$deltaHours{$props->{projectId}}{$props->{taskId}} += $props->{hours};
next unless $props->{taskDate} and $props->{projectId} and $props->{taskId} and $props->{hours};
$entryId = $self->setCollateral("TT_timeEntry","entryId",$props,0,0);
push @entryIds, $entryId;
}
#Delete anything not in the task list
$db->write("delete from TT_timeEntry where reportId=? and entryId not in (".$db->quoteAndJoin(\@tasks).")",[$reportId]);
#Update Project Management App if integrated
if($self->getValue("pmIntegration")) {
foreach my $eid (@tasks) {
my $task = $tasksToUpdate->{$eid};
my $taskId = $task->{taskId};
my $projectId = $task->{projectId};
my $pmAsset = WebGUI::Asset::Wobject::ProjectManager->getProjectInstance($session,$projectId);
if($pmAsset) {
my ($totalHours) = $db->quickArray("select sum(hours) from TT_timeEntry where taskId=?",[$taskId]);
$pmAsset->updateProjectTask($taskId,$projectId,$totalHours);
}
}
}
# Clobber other entries. We can't just do this beforehand
# because otherwise setCollateral will fail.
$db->write("DELETE FROM TT_timeEntry WHERE reportId = ? AND entryId NOT IN (".join(', ', ('?') x @entryIds).")", [$reportId, @entryIds]);
# Update Project Management App if integrated
if ($self->getValue("pmIntegration")) {
foreach my $projectId (keys %deltaHours) {
foreach my $taskId (keys %{$deltaHours{$projectId}}) {
my $deltaHours = $deltaHours{$projectId}{$taskId};
if (my $pmAsset = WebGUI::Asset::Wobject::ProjectManager->getProjectInstance($session, $projectId)) {
$pmAsset->updateProjectTask($taskId, $projectId, $deltaHours);
}
}
}
}
return "";
}

View file

@ -678,8 +678,6 @@ sub quote {
Returns a comma seperated string quoted and ready for insert/select into/from the database. This is typically used for a statement like "select * from someTable where field in (".$db->quoteAndJoin(\@strings).")".
B<NOTE:> This is not a regular method, but is an exported subroutine.
=head3 arrayRef
An array reference containing strings to be quoted.

View file

@ -370,10 +370,15 @@ our $I18N = {
lastUpdated => 0
},
'task untimedPredecessor error' => {
message => q|Tasks are not permitted to have predecessors that are not timed tasks.|,
lastUpdated => 1159825527,
},
'task invalidMove error' => {
message => q|The start date that you have selected for this task is invalid as it's predecessor's end date will not be met. Either remove the predecessor restriction from this task or change the end date of it's predecessor to make this date valid. |,
message => q|The start date that you have selected for this task is invalid as its predecessor's end date will not be met. Either remove the predecessor restriction from this task or change the end date of its predecessor to make this date valid.|,
lastUpdated => 0
},
},
'resource none' => {
message => q|No Resource|,
@ -799,6 +804,21 @@ Otherwise, just the duration will be displayed as text.|,
message => q|No matching groups found.|,
lastUpdated => 1157510786
},
'taskType timed label' => {
message => q|Timed|,
lastUpdated => 1159557353
},
'taskType progressive label' => {
message => q|Progressive|,
lastUpdated => 1159557353
},
'taskType milestone label' => {
message => q|Milestone|,
lastUpdated => 1159557353
},
};
1;

View file

@ -6,7 +6,12 @@ var dunits, hoursPerDay, taskLength;
var extrasPath, errorMsgs;
var taskArray;
function doCalendar (fieldId) {
function parseFloatOrNA(str) {
if (str == 'N/A') return 0.0;
else return parseFloat(str);
}
function doCalendar(fieldId) {
Calendar.setup({
inputField : fieldId,
ifFormat : "%Y-%m-%d",
@ -36,39 +41,78 @@ function closeImage() {
}
//--------------------------------------------------------------------------------------
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 configureForTaskType(form) {
switch (getTaskType(form)) {
case 'timed':
form.end.disabled = false;
form.duration.disabled = false;
form.lagTime.disabled = false;
form.dependants.disabled = false;
form.percentComplete.disabled = false;
if (!form.duration.value || parseFloatOrNA(form.duration.value) == 0)
form.duration.value = (dunits == "hrs")? hoursPerDay : 1;
if (!form.percentComplete.value || form.percentComplete.value == 'N/A')
form.percentComplete.value = 0;
if (!form.lagTime.value || form.lagTime.value == 'N/A')
form.lagTime.value = 0;
break;
case 'progressive':
form.end.value = form.start.value;
form.end.disabled = true;
form.duration.disabled = false;
form.dependants.disabled = false;
form.lagTime.value = 'N/A';
form.lagTime.disabled = true;
form.percentComplete.value = 'N/A';
form.percentComplete.disabled = true;
break;
case 'milestone':
form.end.value = form.start.value;
form.duration.value = 0;
form.duration.disabled = true;
form.lagTime.value = 'N/A';
form.lagTime.disabled = true;
form.end.disabled = true;
form.dependants.disabled = true;
form.percentComplete.value = 'N/A';
form.percentComplete.disabled = true;
break;
}
}
//--------------------------------------------------------------------------------------
function getCheckedOfNodeList(list) {
for (var i = 0; i < list.length; i++) {
if (list[i].checked) { return list[i].value; }
}
return null;
}
function setCheckedOfNodeList(list, value) {
for (var i = 0; i < list.length; i++) {
list[i].checked = (list[i].value == value);
}
return value;
}
function getTaskType(form) { return getCheckedOfNodeList(form.taskType); }
function setTaskType(form, value) { setCheckedOfNodeList(form.taskType, value); }
function isTimed(form) { return getTaskType(form) == 'timed'; }
function isUntimed(form) { return !isTimed(form); }
//--------------------------------------------------------------------------------------
function checkEditTaskForm (form) {
if(form.name.value == "") {
if (form.name.value == "") {
alert(errorMsgs.name);
return;
} else if(form.start.value == "") {
} else if (form.start.value == "") {
alert(errorMsgs.start);
return;
} else if(form.milestone.checked==false && form.end.value == "") {
} else if (isTimed(form) && form.end.value == "") {
alert(errorMsgs.end);
return;
}
@ -92,32 +136,37 @@ function adjustTaskTimeFromDuration(start, end, duration, lagTime, isTaskForm, p
//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;
// Set values if not already set.
if (start.value == "") start.value = todayIntl;
if (end.value == "") end.value = todayIntl;
if (duration.value == "") duration.value = hoursPerDay;
//Convert hours to days
// Convert hours to days.
var taskDuration = parseFloat(duration.value);
var taskTotalDuration = taskDuration + parseFloat(lagTime.value);
var taskTotalDuration = taskDuration + parseFloatOrNA(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);
// We can't have timed tasks with duration zero. Those are called "milestones".
if (taskDuration <= 0 && getTaskType(form) == 'timed') {
if (isTaskForm) {
// Convert to milestone if desired.
if (confirm("Zero duration tasks are considered milestones. Do you wish to change this task to a milestone?")) {
setTaskType(form, 'milestone');
configureForTaskType(form);
} else {
duration.value = form.orig_duration.value;
if (parseFloat(duration.value) <= 0)
duration.value = hoursPerDay;
}
} else {
duration.value = form.orig_duration.value;
// Do not let users zero out tasks from the quick view.
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;
} 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;
}
@ -138,19 +187,19 @@ function adjustTaskTimeFromDuration(start, end, duration, lagTime, isTaskForm, p
}
//--------------------------------------------------------------------------------------
function adjustTaskTimeFromDate (start, end, duration, lagTime, element, 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;
if (isTaskForm) {
if (isUntimed(form)) return;
orig_duration = form.orig_duration.value;
}
//Handle case where both start and end are empty
if(start.value == "" && end.value == "") {
if (start.value == "" && end.value == "") {
//get today's date
var today = new Date();
var todayIntl = intlDate(today);
@ -161,9 +210,9 @@ function adjustTaskTimeFromDate (start, end, duration, lagTime, element, isTaskF
//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 (start.value == "") start.value = end.value;
if(isTaskForm && orig_duration == "") {
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,"");
@ -188,7 +237,7 @@ function adjustTaskTimeFromDate (start, end, duration, lagTime, element, isTaskF
duration.value = d - lagTime.value;
} else {
//Set start/end if duration has been saved
var d = parseFloat(duration.value) + parseFloat(lagTime.value);
var d = parseFloat(duration.value) + parseFloatOrNA(lagTime.value);
if(dunits == "hrs") {
//Convert to days
d = d / hoursPerDay;
@ -351,22 +400,31 @@ function validateDependant(field,origField,seqNum,start,end,duration,lagTime,isT
var pred = field.value;
var newTask = false;
if(pred != "") {
if(seqNum == "") seqNum = taskLength+1;
if(pred < 1) {
if (seqNum == "") seqNum = taskLength+1;
if (pred < 1) {
alert(errorMsgs.noPredecessor);
field.value=origField.value;
field.value = origField.value;
return;
}
if(pred == seqNum) {
if (pred == seqNum) {
alert(errorMsgs.samePredecessor);
field.value=origField.value;
field.value = origField.value;
return;
}
if(pred > seqNum) {
if (pred > seqNum) {
alert(errorMsgs.previousPredecessor);
field.value = origField.value;
return;
}
if (taskArray[pred]["type"] != 'timed') {
alert(errorMsgs.untimedPredecessor);
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 == "") {
@ -384,7 +442,7 @@ function validateDependant(field,origField,seqNum,start,end,duration,lagTime,isT
if(newTask || (taskStartObj.getTime() < predTaskEndObj.getTime())) {
//Convert predecessor hours to days
var taskTotalDuration = parseFloat(duration.value) + parseFloat(lagTime.value);
var taskTotalDuration = parseFloat(duration.value) + parseFloatOrNA(lagTime.value);
if(dunits == "hrs") taskTotalDuration = taskTotalDuration / hoursPerDay;
var totalDurationFloor = Math.floor(taskTotalDuration);