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 = ""; } 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"), -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| |; $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| |; 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} || " "; } $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;