webgui/lib/WebGUI/Asset/Wobject/ProjectManager.pm

1184 lines
44 KiB
Perl

package WebGUI::Asset::Wobject::ProjectManager;
$VERSION = "1.0.0";
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2006 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
use strict;
use DateTime;
use Tie::IxHash;
use WebGUI::International;
use WebGUI::Utility;
use POSIX qw(ceil floor);
use base 'WebGUI::Asset::Wobject';
#-------------------------------------------------------------------
sub _addDaysForMonth {
my $self = shift;
my $dt = $self->session->datetime;
my $eh = $self->session->errorHandler;
my $days_loop = $_[0];
my $hash = $_[1];
my $month = $_[2];
my ($monthStart,$monthEnd) = $dt->monthStartEnd($month);
my $dayOfWeek = $dt->getDayOfWeek($monthStart);
my $mondayAdjust = (7 - ($dayOfWeek-1)) % 7;
my $firstMonday = $dt->addToDateTime($monthStart,0,0,$mondayAdjust,1);
#This line of code just makes things easier to read
my $colCount = 0;
my $monday = $firstMonday;
while ($monday < $monthEnd) {
my $hash = {};
$hash->{'day.number'} = $dt->epochToHuman($monday,"%d");
#$eh->warn($hash->{'day.number'});
$monday += 604800; # Add one week to the first monday of the month
push(@{$days_loop},$hash);
$colCount++;
}
$hash->{'month.colspan'} = $colCount;
#$eh->warn($dt->epochToHuman($firstMonday));
}
#-------------------------------------------------------------------
sub _getDurationUnitHash {
my $self = shift;
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
tie my %hash, "Tie::IxHash";
%hash = ( "hours"=>$i18n->get("hours label"), "days"=>$i18n->get("days label") );
return \%hash;
}
#-------------------------------------------------------------------
sub _getDurationUnitHashAbbrev {
my $self = shift;
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
tie my %hash, "Tie::IxHash";
%hash = ( "hours"=>$i18n->get("hours label abbrev"), "days"=>$i18n->get("days label abbrev") );
return \%hash;
}
#-------------------------------------------------------------------
sub definition {
my $class = shift;
my $session = shift;
my $definition = shift;
my $i18n = WebGUI::International->new($session,'Asset_ProjectManager');
my %properties;
tie %properties, 'Tie::IxHash';
%properties = (
projectDashboardTemplateId =>{
fieldType=>"template",
defaultValue=>'ProjectManagerTMPL0001',
tab=>"display",
namespace=>"ProjectManager_dashboard",
hoverHelp=>$i18n->get('projectDashboardTemplate hoverhelp'),
label=>$i18n->get('projectDashboardTemplate label')
},
projectDisplayTemplateId => {
fieldType=>"template",
defaultValue=>'ProjectManagerTMPL0002',
tab=>"display",
namespace=>"ProjectManager_project",
hoverHelp=>$i18n->get('projectDisplayTemplate hoverhelp'),
label=>$i18n->get('projectDisplayTemplate label')
},
ganttChartTemplateId => {
fieldType=>"template",
defaultValue=>'ProjectManagerTMPL0003',
tab=>"display",
namespace=>"ProjectManager_gantt",
hoverHelp=>$i18n->get('ganttChartTemplate hoverhelp'),
label=>$i18n->get('ganttChartTemplate label')
},
editTaskTemplateId =>{
fieldType=>"template",
defaultValue=>'ProjectManagerTMPL0004',
tab=>"display",
namespace=>"ProjectManager_editTask",
hoverHelp=>$i18n->get('editTaskTemplate hoverhelp'),
label=>$i18n->get('editTaskTemplate label')
},
groupToAdd => {
fieldType=>"group",
defaultValue=>3,
tab=>"security",
hoverHelp=>$i18n->get('groupToAdd hoverhelp'),
label=>$i18n->get('groupToAdd label')
}
);
push(@{$definition}, {
assetName=>$i18n->get('assetName'),
icon=>'projManagement.gif',
autoGenerateForms=>1,
tableName=>'PM_wobject',
className=>'WebGUI::Asset::Wobject::ProjectManager',
properties=>\%properties
});
return $class->SUPER::definition($session, $definition);
}
#-------------------------------------------------------------------
sub duplicate {
my $self = shift;
my $newAsset = $self->SUPER::duplicate(shift);
return $newAsset;
}
#-------------------------------------------------------------------
sub prepareView {
my $self = shift;
$self->SUPER::prepareView();
my $template = WebGUI::Asset::Template->new($self->session, $self->get("projectDashboardTemplateId"));
$template->prepare;
$self->{_viewTemplate} = $template;
}
#-------------------------------------------------------------------
sub processErrors {
my $self = shift;
my $errors = "";
if($_[0]) {
$errors = "<ul>";
foreach (@{$_[0]}) {
$errors .= "<li>$_</li>";
}
$errors .= "</ul>";
}
return $errors;
}
#-------------------------------------------------------------------
sub purge {
my $self = shift;
#purge your wobject-specific data here. This does not include fields
# you create for your NewWobject asset/wobject table.
return $self->SUPER::purge;
}
#-------------------------------------------------------------------
sub setSessionVars {
my $self = shift;
my $session = $self->session;
my $i18n = WebGUI::International->new($session,'Asset_ProjectManager');
return ($session,$session->privilege,$session->form,$session->db,$session->datetime,$i18n,$session->user);
}
#-------------------------------------------------------------------
sub view {
my $self = shift;
my $var = $self->get;
my ($session,$privilege,$form,$db,$datetime,$i18n,$user) = $self->setSessionVars;
my $config = $session->config;
my $eh = $session->errorHandler;
$var->{'extras'} = $config->get("extrasURL")."/wobject/ProjectManager";
$var->{'project.create'} = $self->getUrl("func=editProject;projectId=new");
$var->{'project.create.label'} = $i18n->get("project new label");
#Project Table Headers
$var->{'project.name.label'} = $i18n->get("project name label");
$var->{'project.startDate.label'} = $i18n->get("project start date label");
$var->{'project.endDate.label'} = $i18n->get("project end date label");
$var->{'project.cost.label'} = $i18n->get("project cost label");
$var->{'project.complete.label'} = $i18n->get("project complete label");
$var->{'project.actions.label'} = $i18n->get("project action label");
$var->{'empty.colspan'} = 5;
if($user->isInGroup($self->get("groupToAdd"))) {
$var->{'canEditProjects'} = "true";
$var->{'empty.colspan'} = 6;
}
#Project Data
my @projects = ();
my $sth = $db->read("select * from PM_project where assetId=".$db->quote($self->get("assetId")));
while (my $project = $sth->hashRef) {
my $hash = {};
my $projectId = $project->{projectId};
$hash->{'project.view.url'} = $self->getUrl("func=viewProject;projectId=".$projectId);
$hash->{'project.name.data'} = $project->{name};
$hash->{'project.description.data'} = $project->{description};
$hash->{'project.startDate.data'} = $project->{startDate}?$datetime->epochToSet($project->{startDate}):"N/A";
$hash->{'project.endDate.data'} = $project->{endDate}?$datetime->epochToSet($project->{endDate}):"N/A";
$hash->{'project.cost.data.int'} = WebGUI::Utility::commify(int($project->{targetBudget}));
$hash->{'project.cost.data.float'} = WebGUI::Utility::commify($project->{targetBudget});
$hash->{'project.complete.data.int'} = int($project->{percentComplete});
$hash->{'project.complete.data.float'} = sprintf("%2.2f",$project->{percentComplete});
if($var->{'canEditProjects'}) {
$hash->{'project.edit.url'} = $self->getUrl("func=editProject;projectId=".$projectId);
$hash->{'project.edit.title'} = $i18n->get("project edit title");
$hash->{'project.delete.url'} = $self->getUrl("func=deleteProject;projectId=".$projectId);
$hash->{'project.delete.title'} = $i18n->get("project delete title");
}
push(@projects, $hash);
}
$sth->finish;
my $warning = $i18n->get("project delete warning");
$warning =~ s/'/\\'/g;
$var->{'project.delete.warning'} = $warning;
$var->{'noProjects'} = $i18n->get("no projects") if(scalar(@projects) == 0);
$var->{'project.loop'} = \@projects;
return $self->processTemplate($var, undef, $self->{_viewTemplate});
}
#-------------------------------------------------------------------
sub www_deleteProject {
my $self = shift;
#Set Method Helpers
my ($session,$privilege,$form,$db,$datetime,$i18n,$user) = $self->setSessionVars;
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($self->get("groupToAdd")));
my $projectId = $form->get("projectId");
#Delete Project
$db->write("delete from PM_project where projectId=?",[$projectId]);
#Delete Associated Tasks
$db->write("delete from PM_task where projectId=?",[$projectId]);
return "";
}
#-------------------------------------------------------------------
sub www_editProject {
my $self = shift;
#Set Method Helpers
my ($session,$privilege,$form,$db,$datetime,$i18n,$user) = $self->setSessionVars;
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($self->get("groupToAdd")));
#Set Local Vars
my $projectId = $form->get("projectId");
my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId));
my $addEditText = ($projectId eq "new")?$i18n->get("create project"):$i18n->get("edit project");
#Build Form
my $f = WebGUI::HTMLForm->new($self->session,-action=>$self->getUrl, -extras=>q|onsubmit="return checkform(this);"|);
$f->hidden(
-name=>"func",
-value=>"editProjectSave"
);
$f->hidden(
-name=>"projectId",
-value=>$projectId
);
$f->readOnly(
-label=>$i18n->get("project id"),
-hoverHelp => $i18n->get('project name hoverhelp'),
-value=>$projectId
);
$f->text(
-name => "name",
-value => $form->get("name") || $project->{name},
-hoverHelp => $i18n->get('project name hoverhelp'),
-label => $i18n->get('project name label')
);
$f->HTMLArea(
-name => "description",
-value => $form->get("description") || $project->{description},
-hoverHelp => $i18n->get('project description hoverhelp'),
-label => $i18n->get('project description label')
);
$f->group(
-name=> "projectManager",
-value=> $form->get("projectManager") || $project->{projectManager} || $self->get("groupToAdd"),
-hoverHelp=> $i18n->get('project manager hoverhelp'),
-label => $i18n->get('project manager label')
);
my $dunitValue = $form->get("durationUnits") || $project->{durationUnits} || "hours";
$f->selectBox(
-name=>"durationUnits",
-value=> $dunitValue,
-options=>$self->_getDurationUnitHash,
-hoverHelp => $i18n->get('duration units hoverhelp'),
-label => $i18n->get('duration units label'),
-extras=> q|onchange="if(this.value == 'hours'){ document.getElementById('hoursper').style.display='' } else { document.getElementById('hoursper').style.display='none' }"|
);
my $hpdLabel = $i18n->get('hours per day label');
my $hpdHoverHelp = $i18n->get('hours per day hoverhelp');
$hpdHoverHelp =~ s/'/\\'/g;
my $hpdValue = $form->get("hoursPerDay") || $project->{hoursPerDay} || "8.0";
my $hpdStyle = ($dunitValue eq "days"?"display:none":"");
my $html = qq|
<tr id="hoursper" style="$hpdStyle">
<td class="formDescription" onmouseover="return escape('$hpdHoverHelp')" valign="top" style="width: 180px;">
<label for="hoursPerDay_formId">$hpdLabel</label>
</td>
<td valign="top" class="tableData" style="width: *;">
<input id="hoursPerDay_formId" type="text" name="hoursPerDay" value="$hpdValue" size="11" maxlength="14" onkeyup="doInputCheck(this.form.hoursPerDay,'0123456789-.')" />
</td>
</tr>|;
$f->raw($html);
$f->float (
-name=>"targetBudget",
-value=> $form->get("targetBudget") || $project->{targetBudget} || "0.00",
-hoverHelp => $i18n->get('target budget hoverhelp'),
-label=> $i18n->get('target budget label')
);
$f->submit(
-extras=>"name='subbutton'",
-value=>$addEditText
);
my $jscript = qq|
<script language="JavaScript">
function checkform(form) {
if(form.name.value == ""){
alert("You must enter a project name");
form.subbutton.value='$addEditText';
return false;
}
return true;
}
</script>
|;
my $errors = $self->processErrors($_[0]);
my $output = $jscript."\n".$errors.$f->print;
return $self->getAdminConsole->render($output,$addEditText);
}
#-------------------------------------------------------------------
sub www_editProjectSave {
my $self = shift;
#Set Method Helpers
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($self->get("groupToAdd")));
my $now = $dt->time();
my $uid = $user->userId;
my $projectId = $form->process("projectId","hidden");
#Set Properties
my $props = {};
$props->{projectId} = $projectId;
$props->{name} = $form->process("name","text");
$props->{description} = $form->process("description","HTMLArea");
$props->{projectManager} = $form->process("projectManager","group");
$props->{durationUnits} = $form->process("durationUnits","selectBox");
$props->{hoursPerDay} = $form->process("hoursPerDay","float") || 8.0;
$props->{targetBudget} = $form->process("targetBudget","float");
if($projectId eq "new") {
$props->{creationDate} = $now;
$props->{createdBy} = $uid;
}
$props->{lastUpdateDate} = $now;
$props->{lastUpdatedBy} = $uid;
#Process Errors
my @errors = ();
push(@errors,"You must enter a project name") unless ($props->{name});
if(scalar(@errors) > 0) {
return $self->www_editProject(\@errors);
}
#Save the extended project data
my $projectId = $self->setCollateral("PM_project","projectId",$props,0,1);
if($projectId eq "new") {
#Create Project Start Milestone Task for new projects
$props = {};
$props->{taskId} = "new";
$props->{projectId} = $projectId;
$props->{taskName} = $i18n->get("project start task label");
$props->{duration} = 0;
$props->{startDate} = $dt->time();
$props->{endDate} = $dt->time();
$props->{isMilestone} = 1;
$props->{creationDate} = $now;
$props->{createdBy} = $uid;
$props->{lastUpdateDate} = $now;
$props->{lastUpdatedBy} = $uid;
#Save the extended task data
my $taskId = $self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId);
}
return $self->www_viewProject($projectId);
}
#-------------------------------------------------------------------
sub www_editTask {
my $self = shift;
my $var = {};
#Set Method Helpers
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $config = $session->config;
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));
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($project->{projectManager}));
my $isMilestone = $task->{isMilestone};
my $seq = $task->{sequenceNumber};
my $extras = ($isMilestone)?" disabled":"";
$var->{'form.header'} = WebGUI::Form::formHeader($session,{
action=>$self->getUrl,
extras=>q|name="editTaskForm"|
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"func",
-value=>"editTaskSave"
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"projectId",
-value=>$projectId
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"taskId",
-value=>$taskId
});
#Set some hidden variables to make it easy to reset data in javascript
my $duration = $task->{duration};
my $start = $dt->epochToSet($task->{startDate});
my $end = $dt->epochToSet($task->{endDate});
my $dependant = $task->{dependants};
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"orig_duration",
-value=>$duration
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"orig_start",
-value=>$start
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"orig_end",
-value=>$end
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"orig_dependant",
-value=>$dependant
});
$var->{'form.name'} = WebGUI::Form::text($session,{
-name=>"name",
-value=>$task->{taskName},
-extras=>q|style="width:95%;"|
});
$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) }" $extras|
});
$var->{'form.duration.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="adjustTaskTimeFromDate(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.form.milestone.checked==true){ this.form.end.value=this.value; }" style="width:88%;"|
});
$var->{'form.end'} = WebGUI::Form::text($session,{
-name=>"end",
-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|
});
$var->{'form.dependants'} = WebGUI::Form::integer($session,{
-name=>"dependants",
-value=>$dependant || "",
-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);"|
});
tie my %users, "Tie::IxHash";
%users = $db->buildHash("select userId,username from users where userId not in (1,3)");
%users = (""=>$i18n->get("resource none"),%users);
$var->{'form.resource'} = WebGUI::Form::selectBox($session, {
-name=>"resource",
-options=>\%users,
-value=>[$task->{resourceId}],
-extras=>$extras
});
$var->{'form.milestone'} = WebGUI::Form::checkbox($session, {
-name=>"milestone",
-value=>1,
-checked=>$task->{isMilestone},
-extras=>q|onclick="configureMilestone(this)"|
});
$var->{'form.percentComplete'} = WebGUI::Form::float($session, {
-name=>"percentComplete",
-value=>$task->{percentComplete},
-extras=>$extras
});
$var->{'form.save'} = WebGUI::Form::submit($session, {
-value=>"Save",
-extras=>q|name="subbutton"|
});
$var->{'form.footer'} = WebGUI::Form::formFooter($session);
$var->{'extras'} = $config->get("extrasURL");
return $self->processTemplate($var,$self->getValue("editTaskTemplateId"))
}
#-------------------------------------------------------------------
sub www_editTaskSave {
my $self = shift;
my $var = {};
#Set Method Helpers
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $config = $session->config;
my $projectId = $form->get("projectId");
my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId));
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($project->{projectManager}));
my $isMilestone = $form->process("milestone","checkbox");
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->{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->{resourceId} = $form->process("resource","selectBox");
$props->{percentComplete} = $isMilestone? 0 : $form->process("percentComplete","float");
my $now = $dt->time();
if($props->{taskId} eq "new") {
$props->{creationDate} = $now;
$props->{createdBy} = $user->userId;
}
$props->{lastUpdateDate} = $now;
$props->{lastUpdatedBy} = $user->userId;
#Save the extended task data
my $taskId = $self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId);
$self->_updateProject($projectId);
$self->_arrangePredecessors($project,$taskId);
return $self->www_viewProject($projectId,$taskId);
}
#-------------------------------------------------------------------
sub _updateProject {
my $self = shift;
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $eh = $session->errorHandler;
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 = (($complete / $projectTotal) * 100);
$db->write("update PM_project set startDate=?, endDate=?, percentComplete=? where projectId=?",[$minStart,$maxEnd,$projectComplete,$projectId]);
}
#-------------------------------------------------------------------
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};
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=".$db->quote($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 $durationInDays = $task->{duration};
$durationInDays = $durationInDays / $project->{hoursPerDay} if( $project->{durationUnits} == "hours" );
#$eh->warn("Duration in Days: ".$durationInDays);
my $durationFloor = floor($durationInDays);
#$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
$durationInDays += $predDayPart;
$durationFloor = floor($durationInDays);
}
#$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, $durationFloor);
#$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);
}
}
}
#Adjust duration of days to only include the part of the day used
$durationInDays = $durationInDays - floor($durationInDays);
#$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'=>$durationInDays
};
}
return;
}
#-------------------------------------------------------------------
sub www_saveExistingTasks {
my $self = shift;
my $var = {};
#Set Method Helpers
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $config = $session->config;
my $projectId = $form->get("projectId");
my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId));
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($project->{projectManager}));
my $tasks = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=".$db->quote($projectId)." order by sequenceNumber asc");
#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->{lastUpdateDate} = $dt->time();
$props->{lastUpdatedBy} = $user->userId;
$self->setCollateral("PM_task","taskId",$props,1,0,"projectId",$projectId);
}
#Rearrange predecessors
#$self->_arrangePredecessors($project);
return $self->www_drawGanttChart();
}
#-------------------------------------------------------------------
sub www_viewProject {
my $self = shift;
my $var = {};
#Set Method Helpers
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $config = $session->config;
my $style = $session->style;
my $eh = $session->errorHandler;
my $projectId = $_[0] || $form->get("projectId");
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($self->canView));
$var->{'extras'} = $config->get("extrasURL")."/wobject/ProjectManager";
$var->{'extras.base'} = $config->get("extrasURL");
#Set Some Style stuff
$style->setLink($var->{'extras'}."/subModal.css",{rel=>"stylesheet",type=>"text/css"});
$style->setLink($var->{'extras.base'}."/calendar/calendar-win2k-1.css",{rel=>"stylesheet",type=>"text/css"});
#Get Project Data
my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId));
my $canEdit = $user->isInGroup($self->get("groupToAdd"));
my $canAddTask = $user->isInGroup($project->{projectManager}) || $canEdit;
my $dunits = $self->_getDurationUnitHashAbbrev->{$project->{durationUnits}};
#Set some JavaScript stuff
$var->{'project.durationUnits'} = $dunits;
$var->{'project.hoursPerDay'} = $project->{hoursPerDay} || "0";
#Get Tasks
my $data = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=".$db->quote($projectId)." order by sequenceNumber asc");
$var->{'task.name.label'} = $i18n->get("task name label");
$var->{'task.duration.label'} = $i18n->get("task duration label");
$var->{'task.start.label'} = $i18n->get("task start label");
$var->{'task.end.label'} = $i18n->get("task end label");
$var->{'task.dependants.label'} = $i18n->get("task dependant label");
#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");
$var->{'form.greaterthan.error'} = $i18n->get("task greaterthan error");
$var->{'form.previousPredecessor.error'} = $i18n->get("task previousPredecessor error");
$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");
my @taskList = ();
my $count = 0;
foreach my $row (@{$data}) {
my $hash = {};
my $seq = $row->{sequenceNumber};
my $id = $row->{taskId};
my $isMilestone = $row->{isMilestone} || 0;
my $startDate = $dt->epochToSet($row->{startDate});
my $endDate = $dt->epochToSet($row->{endDate});
my $duration = $row->{duration};
$hash->{'task.number'} = $seq;
$hash->{'task.number.id'} = "task~~".$id."~~rowId";
$hash->{'task.name'} = $row->{taskName};
if($canEdit) {
my $startId = "start_".$id."_formId";
my $endId = "end_".$id."_formId";
my $durId = "duration_".$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'),this,false,document.getElementById('$predId'),document.getElementById('$origStartFieldId'),document.getElementById('$origEndFieldId'),$seq);"|
});
$hash->{'task.start'} .= WebGUI::Form::hidden($session,{
-name=>$origStartField,
-value=>$startDate,
-extras=>qq|id="$origStartFieldId"|
});
$hash->{'task.dependants'} = WebGUI::Form::integer($session,{
-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'));"|
});
$hash->{'task.dependants'} .= WebGUI::Form::hidden($session,{
-name=>$origDepField,
-value=>$row->{dependants},
-extras=>qq|id="$origDepFieldId"|
});
$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);"|
});
$hash->{'task.end'} .= WebGUI::Form::hidden($session,{
-name=>$origEndField,
-value=>$endDate,
-extras=>qq|id="$origEndFieldId"|
});
#Don't display uneditable fields if the task is a milestone.
if($isMilestone) {
$hash->{'task.duration'} = $row->{duration};
$hash->{'task.duration'} .= WebGUI::Form::hidden($session,{
-name=>"duration_$id",
-value=>$duration,
-extras=>qq|id="$durId"|
});
} else {
$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);" |
});
}
} else {
$hash->{'task.duration'} = $duration;
$hash->{'task.start'} = $startDate;
$hash->{'task.end'} = $endDate;
$hash->{'task.dependants'} = $row->{dependants} || "&nbsp;";
}
$hash->{'task.duration.units'} = $dunits;
$hash->{'task.isMilestone'} = "true" if($isMilestone);
if($canAddTask) {
$hash->{'task.edit.url'} = $self->getUrl("func=editTask;projectId=$projectId;taskId=".$row->{taskId});
$hash->{'task.edit.label'} = $i18n->get("edit task label");
}
push(@taskList, $hash);
}
$var->{'task.loop'} = \@taskList;
#Set some javascript stuff;
my $taskLength = scalar(@taskList);
$var->{'project.task.length'} = $taskLength;
if($canEdit) {
#Build Form for submitting on the fly updates
$var->{'form.header'} = WebGUI::Form::formHeader($session,{
action=>$self->getUrl,
extras=>q|name="editAll"|
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"func",
-value=>"saveExistingTasks"
});
$var->{'form.header'} .= WebGUI::Form::hidden($session, {
-name=>"projectId",
-value=>$projectId
});
$var->{'form.footer'} = WebGUI::Form::formFooter($session);
$var->{'project.canEdit'} = "true";
$var->{'task.resources.label'} = $i18n->get("task resources label");
$var->{'task.resources.url'} = $self->getUrl("func=manageResources");
}
if($canAddTask) {
$var->{'task.add.label'} = $i18n->get("add task label");
$var->{'task.add.url'} = $self->getUrl("func=editTask;projectId=$projectId;taskId=new");
$var->{'task.canAdd'} = "true";
}
#Rowspan of gaant chart is 4 plus number of tasks
$var->{'project.gaant.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"));
}
#-------------------------------------------------------------------
sub www_drawGanttChart {
my $self = shift;
my $var = {};
#Set Method Helpers
my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
my $config = $session->config;
my $style = $session->style;
my $eh = $session->errorHandler;
#Check Privileges
return $privilege->insufficient unless ($user->isInGroup($self->canView));
#Set up some the task data
my $projectId = $_[0];
my $taskList = $_[1] || [];
my $projVar = $_[2] || {};
my $calledByAjax = 0;
unless($projectId) {
$projectId = $form->get("projectId");
$taskList = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=".$db->quote($projectId)." order by sequenceNumber asc");
$calledByAjax = 1;
}
my ($dunits,$hoursPerDay) = $db->quickArray("select durationUnits,hoursPerDay from PM_project where projectId=".$db->quote($projectId));
$var->{'extras'} = $config->get("extrasURL")."/wobject/ProjectManager";
#Initialize display settings
my $projectDisplay = "weeks";
my $monthInterval = 5;
my $dayLimit = 150;
if($projectDisplay eq "weeks") {
$monthInterval = 3;
$dayLimit = 60;
}
#Find start and end months: Set start and end months to always display at least 4 months if display in weeks, 6 months if display in months
my ($startMonth,$endMonth);
if(scalar(@{$taskList}) > 0) {
($startMonth) = $db->quickArray("select min(startDate) from PM_task where projectId=".$db->quote($projectId));
($endMonth) = $db->quickArray("select max(endDate) from PM_task where projectId=".$db->quote($projectId));
#$eh->warn("Interval is: ".$dt->getDaysInInterval($startMonth,$endMonth));
if($dt->getDaysInInterval($startMonth,$endMonth) < 60) {
$endMonth = $dt->addToDate($startMonth,0,3);
}
} else {
$startMonth = $dt->time;
$endMonth = $dt->addToDate($startMonth,0,3);
}
#$eh->warn($dt->epochToSet($startMonth));
#$eh->warn($dt->epochToSet($endMonth));
#Build the loops of weeks and days
my @monthsLoop = ();
my @daysLoop = ();
if($projectDisplay eq "weeks") {
#Week View Below
my $dayOfWeek = $dt->getDayOfWeek($startMonth);
my $sundayAdjust = (0 - ($dayOfWeek % 7));
$startMonth = $dt->addToDateTime($startMonth,0,0,$sundayAdjust,1);
my @days = (
$i18n->get("sunday label"),
$i18n->get("monday label"),
$i18n->get("tuesday label"),
$i18n->get("wednesday label"),
$i18n->get("thursday label"),
$i18n->get("friday label"),
$i18n->get("saturday label"),
);
#$eh->warn($dt->epochToSet($sundayOfFirstWeek));
#$eh->warn($dt->epochToSet($endMonth));
my $datecounter = $startMonth;
my $counter = 0;
while($datecounter <= $endMonth || $counter++ == 1000) {
my $hash = {};
$hash->{'month.name'} = $dt->epochToHuman($datecounter,"%C %d, %Y");
$hash->{'month.colspan'} = 7;
push(@monthsLoop,$hash);
#Add 7 days for this week
foreach (@days) {
push(@daysLoop,{'day.number' => $_ });
}
#$eh->warn($dt->epochToSet($datecounter));
$datecounter = $dt->addToDateTime($datecounter,0,0,7);
}
} else {
#Months View Below
my $junk;
($startMonth,$junk) = $dt->monthStartEnd($startMonth);
($junk,$endMonth) = $dt->monthStartEnd($endMonth);
my $numMonths = ($dt->monthCount($startMonth,$endMonth) + 1);
for(my $i = 0; $i < $numMonths; $i++) {
my $hash = {};
my $month = $dt->addToDateTime($startMonth,0,$i,0,1);
$hash->{'month.name'} = $dt->epochToHuman($month,"%C %Y");
$self->_addDaysForMonth(\@daysLoop,$hash,$month);
push(@monthsLoop,$hash);
}
}
$var->{'months.loop'} = \@monthsLoop;
$var->{'days.loop'} = \@daysLoop;
#Build tasks and divs
my @taskCount = ();
my @taskDiv = ();
my $taskCount = 0;
my $pixelSize = ($projectDisplay eq "weeks")?23:3.2857;
$var->{'project.task.array'} = "{\n";
my $taskHash = {};
foreach my $task (@{$taskList}) {
my $hash = {};
my $id = $task->{taskId};
my $seq = $task->{sequenceNumber};
my $startDate = $task->{startDate};
my $endDate = $task->{endDate};
my $duration = $task->{duration};
my $predecessor = $task->{dependants};
my $resource = $task->{resourceId};
if($resource) {
$hash->{'task.hasResource'} = "true";
my $u = WebGUI::User->new($session,$resource);
my $username = $u->username;
my $firstName = $u->profileField('firstName');
my $lastName = $u->profileField('lastName');
if($firstName && $lastName) {
$username = $firstName." ".$lastName;
}
$hash->{'task.resource.name'} = $username;
}
my $durationFloor = floor($duration);
$duration = $duration / $hoursPerDay if( $dunits == "hours" );
#Set duration to 1 day if it's a milestone
#$duration = 1 unless ($duration);
#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.label.left'} = $hash->{'task.div.width'} + 3;
my $predDayPart = 0;
my $predEndDate = "";
if($predecessor) {
my $pred = $taskHash->{$predecessor};
$predEndDate = $pred->{endDate};
#Get the day part of the predecessor
$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);
}
}
#Adjust top for MSIE
my $divTop = ($session->env->get("HTTP_USER_AGENT") =~ /msie/i) ? 45 : 43;
#Start at 45 px and add 20px as the start of the new task
$hash->{'task.div.top'} = $divTop + (22*$taskCount);
#Interval includes current day so add 1
my $daysFromStart = ($dt->getDaysInInterval($startMonth,$startDate) + 1);
#Add day part of predecessor if necessary
#$eh->warn("Task $seq is currently $daysFromStart days from the first day on ".$dt->epochToHuman($startDate));
my $daysLeft = $daysFromStart;
#Only adjust for predecessor if the start date of the task falls on the same day as it's predecessors end date
if($startDate eq $predEndDate) {
$daysLeft += $predDayPart;
#$eh->warn("Adjusting this by $predDayPart days");
}
$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};
push(@taskDiv, $hash);
push(@taskCount, { 'task.counter' => $task->{sequenceNumber} } );
$var->{'project.task.array'} .= ",\n" if($taskCount > 0);
$startDate = $dt->epochToSet($startDate);
$endDate = $dt->epochToSet($endDate);
my $rduration = $task->{duration};
#Adjust duration of days to only include the part of the day used
#$eh->warn("day part is being set to: $duration - ".floor($duration)." : ".($duration-floor($duration)));
$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" }|;
$taskCount++;
$taskHash->{$seq} = {
'startDate'=>$task->{startDate},
'endDate'=>$task->{endDate},
'duration'=>$task->{duration},
'dayPart'=>$duration
};
}
$var->{'task.count.loop'} = \@taskCount;
$var->{'task.div.loop'} = \@taskDiv;
$var->{'project.task.array'} .= "\n}";
#Set Gantt Chart template vars
my $cols = scalar(@daysLoop);
$var->{'total.colspan'} = $cols;
$var->{'scrollWidth'} = $cols * 23;
#Set project template vars
$var->{'project.table.width'} = $projVar->{'project.table.width'} = 560 + $var->{'scrollWidth'};
my $scrollWidth = (1- (560 / $projVar->{'project.table.width'})) * 100;
$var->{'project.scroll.percentWidth'} = $projVar->{'project.scroll.percentWidth'} = sprintf("%2.2f",$scrollWidth);
return $self->processTemplate($var,$self->getValue("ganttChartTemplateId"));
}
1;