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 WebGUI::HTML;
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 _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]);
}

#-------------------------------------------------------------------
sub _doGanttTaskResourceDisplay {
	my $self = shift;
	my $hash = shift;
	my $task = shift;
	my @resources = @{$self->_resourceListOfTask($task->{taskId})};
	my @resourceNames = ();

	foreach my $resource (@resources) {
		my ($resourceKind, $resourceId) = @$resource{qw{resourceKind resourceId}};
		if ($resourceKind eq 'user') {
			my $u = WebGUI::User->new($self->session, $resourceId);
			my $name = $u->username;
			my $firstName = $u->profileField('firstName');
			my $lastName = $u->profileField('lastName');
			$name = "$firstName $lastName"
			    if (length $firstName and length $lastName);
			push @resourceNames, $name;
		} elsif ($resourceKind eq 'group') {
			my $g = WebGUI::Group->new($self->session, $resourceId);
			push @resourceNames, $g->name;
		} else {
			# Whee.
			push @resourceNames, "???"
		}
	}

	if (@resources) {
		$hash->{'task.hasResource'} = "true";
		$hash->{'task.resource.name'} =
		    join(', ', map { WebGUI::HTML::format($_, 'text') } @resourceNames);
	}
}

#-------------------------------------------------------------------
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 _groupSearchQuery {
	my $self = shift;
	my $exclude = shift;
	my $searchPattern = lc('%'.shift().'%');
	my @exclude = ('1', '7', split /\;/, $exclude);
	my $excludePlaceholders = '('.join(',', ('?') x @exclude).')';
	my $query = <<"SQL";
SELECT 'group' AS resourceKind, groups.groupId AS resourceId
  FROM groups
 WHERE (LOWER(groups.groupName) LIKE ?) AND (groups.groupId NOT IN $excludePlaceholders)
       AND groups.isEditable = 1
 ORDER BY groups.groupName
SQL
	my @placeholders = ($searchPattern, @exclude);
	return ($query, \@placeholders);
}

#-------------------------------------------------------------------
sub _htmlOfResourceList {
	my $self = shift;
	my %args = %{+shift};
	my @resources = @_;
	my @listItems;
	my $assetExtras = $self->session->url->extras('wobject/ProjectManager');
	my $var = {};

	$var->{assetExtras} = $assetExtras;
	$var->{resourceLoop} = [];

	my $lastOdd = 0;
	foreach my $row (@resources) {
		my $subvar = {};
		my ($resourceKind, $resourceId) = @$row{qw{resourceKind resourceId}};
		my $odd = ($lastOdd = !$lastOdd);

		$subvar->{resourceKind} = $resourceKind;
		$subvar->{resourceId} = $resourceId;
		$subvar->{opCallbackJs} = $args{opCallbackJs};
		$subvar->{opIcon} = $args{opIcon};
		$subvar->{opTitle} = $args{opTitle};
		$subvar->{assetExtras} = $assetExtras;
		$subvar->{odd} = $odd;
		$subvar->{hiddenFields} = $args{hiddenFields};

		if ($resourceKind eq 'group') {
			my $group = WebGUI::Group->new($self->session, $resourceId);
			$subvar->{resourceName} = WebGUI::HTML::format($group->name, 'text');
			$subvar->{resourceIcon} = 'groups.gif';
		} elsif ($resourceKind eq 'user') {
			my $user = WebGUI::User->new($self->session, $resourceId);
			my ($firstName, $lastName, $username) = ($user->profileField('firstName'), $user->profileField('lastName'), $user->username);
			my $displayName = do {
				if (length($firstName) && length($lastName)) { "$lastName, $firstName" }
				elsif (length($firstName)) { $firstName }
				elsif (length($lastName)) { $lastName }
				else { $username }
			};
			$subvar->{resourceName} = WebGUI::HTML::format($displayName, 'text');
			$subvar->{resourceIcon} = 'users.gif';
		} else {
			$self->session->errorHandler->fatal("Unknown kind of resource '$resourceKind'!");
		}

		push @{$var->{resourceLoop}}, $subvar;
	}

	return $self->processTemplate($var, $self->getValue('resourceListTemplateId'));
}

#-------------------------------------------------------------------
sub _innerHtmlOfResources {
	my $self = shift;
	my @resources = @_;
	my $i18n = WebGUI::International->new($self->session, 'Asset_ProjectManager');
	return $self->_htmlOfResourceList({opCallbackJs => 'taskEdit_deleteResource', opIcon => 'delete.gif', opTitle => $i18n->get('resource remove opTitle'), hiddenFields => 1}, @resources);
}

#-------------------------------------------------------------------
sub _resourceListOfTask {
	my $self         = shift;
	my $taskId       = shift;
    my $resourceList = [];
    
    unless ($taskId eq 'new') {
       my $sql = q|SELECT 
                    resourceKind, resourceId 
                  FROM 
                    PM_taskResource 
                  WHERE 
                    taskId = ? 
                  ORDER BY 
                    sequenceNumber
                 |;
       $resourceList 
           = $self->session->db->buildArrayRefOfHashRefs($sql,[$taskId]);
    }
    
	return $resourceList;    
}

#-------------------------------------------------------------------
sub _resourceSearchPopup {
	my $self = shift;
	my %args = @_;
	my $i18n = WebGUI::International->new($self->session,'Asset_ProjectManager');

	my $doSearch = $self->session->form->param('doSearch');
	my $jsCallback = $self->session->form->param('callback');
	$jsCallback =~ tr/A-Za-z0-9_//cd;
	my $selfUrlHtml = WebGUI::HTML::format($self->getUrl, 'text');
	my $assetExtras = $self->session->url->extras('wobject/ProjectManager');

	my ($search, $exclude) = map {scalar $self->session->form->param($_)} ('search', 'exclude');
	my ($searchHtml, $excludeHtml) = map {WebGUI::HTML::format($_, 'text')} ($search, $exclude);
	my $var = {};

	my $i18nprefix = $args{i18nprefix};
	foreach my $key (qw/title searchText foundMessage notFoundMessage/) {
		$var->{$key} = $i18n->get("$i18nprefix $key");
	}

	$var->{assetExtras} = $assetExtras;
	$var->{func} = $args{func};
	$var->{callback} = $jsCallback;
	$var->{exclude} = $excludeHtml;
	$var->{previousSearch} = $searchHtml;
	$var->{selfUrl} = $selfUrlHtml;

	if ($doSearch) {
		my @resources = @{$self->session->db->buildArrayRefOfHashRefs($args{queryCallback}->($exclude, $search))};
		$var->{doingSearch} = 1;
		$var->{foundResults} = scalar @resources;

		$var->{resourceDiv} = '<div id="taskEdit_resourceList_div">'.$self->_htmlOfResourceList({opCallbackJs => 'searchPopup_itemSelected', opIcon => 'add.gif', opTitle => $i18n->get('resource add opTitle'), hiddenFields => 0}, @resources).'</div>';
	} else {
		$var->{doingSearch} = 0;
	}

	return $self->processTemplate($var, $self->getValue('resourcePopupTemplateId'));
}

#-------------------------------------------------------------------
sub _userSearchQuery {
	my $self = shift;
	my $exclude = shift;
	my $searchPattern = lc('%'.shift().'%');
	my @exclude = ('1', '3', split /\;/, $exclude);
	my $excludePlaceholders = '('.join(',', ('?') x @exclude).')';

	my $query = <<"SQL";
SELECT 'user' AS resourceKind, users.userId AS resourceId
  FROM users
       LEFT JOIN userProfileData ON users.userId = userProfileData.userId
 WHERE (LOWER(lastName) LIKE ? OR LOWER(firstName) LIKE ?
        OR LOWER(users.username) LIKE ?) AND (users.userId NOT IN $excludePlaceholders)
 ORDER BY lastName, firstName
SQL
	my @placeholders  = (($searchPattern) x 3, @exclude);
	return ($query, \@placeholders);
}

#-------------------------------------------------------------------
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 $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);
	           
				# 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);
			}
		}
	  
		# Extract fractional day part.
		my $totalDurationInDaysFrac = $totalDurationInDays - floor($totalDurationInDays);

		$taskHash->{$seqNum} = { 
					'startDate'=>$task->{startDate}, 
					'endDate'=>$task->{endDate}, 
					'duration'=>$task->{duration}, 
					'dayPart'=>$totalDurationInDaysFrac
				       };
	}
}

#-------------------------------------------------------------------
sub _userCanManageProject {
    my $self = shift;
    my $user = shift;
    my $projectId = shift;
    my ($managerGroup) = $self->session->db->quickArray("select projectManager from PM_project where projectId = ?", [$projectId]);
    return $self->canView($user->userId) && ($user->isInGroup($managerGroup) || $user->isInGroup($self->get('groupToAdd')));
}

#-------------------------------------------------------------------
sub _userCanManageProjectList {
	my $self = shift;
	my $user = shift;
	return $self->canView($user->userId) && $user->isInGroup($self->get('groupToAdd'));
}

#-------------------------------------------------------------------
sub _userCanObserveProject {
	my $self = shift;
	my $user = shift;
	my $projectId = shift;
	my ($managerGroup, $observerGroup) = $self->session->db->quickArray("select projectManager, projectObserver from PM_project where projectId = ?", [$projectId]);
	return $self->canView($user->userId) && ($user->isInGroup($managerGroup) || $user->isInGroup($observerGroup) || $user->isInGroup($self->get('groupToAdd')));
}

#-------------------------------------------------------------------
sub definition {
	my $class = shift;
	my $session = shift;
	my $definition = shift;
	my $i18n = WebGUI::International->new($session,'Asset_ProjectManager');
	my $db = $session->db;
	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')
		},
		resourcePopupTemplateId =>{
			fieldType=>"template",  
			defaultValue=>'ProjectManagerTMPL0005',
			tab=>"display",
			namespace=>"ProjectManager_resourcePopup", 
			hoverHelp=>$i18n->get('resourcePopupTemplate hoverhelp'),
		    label=>$i18n->get('resourcePopupTemplate label')
		},
		resourceListTemplateId =>{
			fieldType=>"template",  
			defaultValue=>'ProjectManagerTMPL0006',
			tab=>"display",
			namespace=>"ProjectManager_resourceList", 
			hoverHelp=>$i18n->get('resourceListTemplate hoverhelp'),
		    label=>$i18n->get('resourceListTemplate 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);
}


#-------------------------------------------------------------------
#API method called by Time Tracker to return the instance of the PM wobject which this project blongs
sub getProjectInstance {
   my $class = shift;
   my $session = shift;
   my $db = $session->db;
   my $projectId = $_[0];
   return undef unless $projectId;
   my ($assetId) = $db->quickArray("select assetId from PM_project where projectId=?",[$projectId]);
   if($assetId) {
      return WebGUI::Asset->newByDynamicClass($session,$assetId);
   }
   return undef;
}

#-------------------------------------------------------------------
#API method called by Time Tracker to return all projects in all assets for which the user passed in has tasks assigned
sub getProjectList {
	my $self = shift;
	my $db = $self->session->db;
	my $userId = $_[0];
	my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups};
	my $groupIdQuery = @groupIds?
	    ('PM_taskResource.resourceId IN ('.join(',', ('?') x @groupIds).')') : '0';

	$self->session->db->buildHashRef(<<"SQL", [$userId, @groupIds]);
SELECT DISTINCT PM_project.projectId, PM_project.name
  FROM PM_project
       INNER JOIN PM_task ON PM_project.projectId = PM_task.projectId 
       INNER JOIN PM_taskResource ON PM_task.taskId = PM_taskResource.taskId
 WHERE (PM_taskResource.resourceKind = 'user' AND PM_taskResource.resourceId = ?)
       OR (PM_taskResource.resourceKind = 'group' AND $groupIdQuery)
SQL
}

#-------------------------------------------------------------------
#API method called by Time Tracker to return all tasks for the projectId passed in
sub getTaskList {
	my $self = shift;
	my $db = $self->session->db;
	my $projectId = $_[0];
	my $userId = $_[1];
	my @groupIds = @{WebGUI::User->new($self->session, $userId)->getGroups};
	my $groupIdQuery = @groupIds?
	    ('PM_taskResource.resourceId IN ('.join(',', ('?') x @groupIds).')') : '0';

	$self->session->db->buildHashRef(<<"SQL", [$projectId, $userId, @groupIds]);
SELECT DISTINCT PM_task.taskId, PM_task.taskName
  FROM PM_task
       INNER JOIN PM_taskResource ON PM_task.taskId = PM_taskResource.taskId
 WHERE PM_task.projectId = ?
       AND ((PM_taskResource.resourceKind = 'user' AND PM_taskResource.resourceId = ?)
            OR (PM_taskResource.resourceKind = 'group' AND $groupIdQuery))
SQL
}

#-------------------------------------------------------------------
sub i18n {
   my $self    = shift;
   my $session = $self->session;
   
   unless ($self->{_i18n}) {
      $self->{_i18n} 
          = WebGUI::International->new($session,'Asset_ProjectManager');
   }
   
   return $self->{_i18n};
}

#-------------------------------------------------------------------
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;
   
   return( 
            $session,
            $session->privilege,
            $session->form,
            $session->db,
            $session->datetime,
            $self->i18n,
            $session->user,
         );
}

#-------------------------------------------------------------------
# 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 $taskId = $_[0];
	my $projectId = $_[1];
	my $deltaHours = $_[2];
   	return 0 unless ($taskId && $projectId && $deltaHours);
   
	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;
	}
   
	$self->setCollateral("PM_task","taskId", { taskId=>$taskId, duration=>$task->{duration}, percentComplete=>$task->{percentComplete} });
	$self->updateProject($projectId);
	return 1;
}

#-------------------------------------------------------------------
sub updateProject {
	my $self = shift;
	my ($session,$privilege,$form,$db,$dt,$i18n,$user) = $self->setSessionVars;
	my $projectId= $_[0];
   
	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]);
}


#-------------------------------------------------------------------
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($self->_userCanManageProjectList($user)) {
		$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};

	   # Drop projects that the current user can't view.
	   next unless ($self->_userCanObserveProject($user, $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}):$i18n->get("N_A");
	   $hash->{'project.endDate.data'} = $project->{endDate}?$datetime->epochToSet($project->{endDate}):$i18n->get("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.int'} = 100 if($hash->{'project.complete.data.int'} > 100);
	   $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 $self->_userCanManageProjectList($user);
	
	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_deleteTask {
   my $self = shift;
   #Set Method Helpers
   my ($session,$privilege,$form,$db,$datetime,$i18n,$user) = $self->setSessionVars;
   
   #Set Local Vars
   my $taskId = $form->get("taskId");
   my $task = $self->getCollateral('PM_task', 'taskId', $taskId);
   my $projectId = $task->{projectId};
   my $taskRank = $task->{sequenceNumber};
	     
   #Check Privileges
   return $privilege->insufficient unless $self->_userCanManageProject($user, $projectId);
   
   #Remove dependencies to this task
   $db->write("update PM_task set dependants=NULL where projectId=? and dependants=?",[$projectId,$taskId]);
   
   #Remove task
   $self->deleteCollateral("PM_task","taskId",$taskId);
   
   #Reorder dependants and tasks
   my $tasks = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=? order by sequenceNumber",[$projectId]);
   my $taskLen = scalar(@{$tasks});
   #$eh->warn("Task Length = $taskLen");
   foreach my $tsk (@{$tasks}) {
      my $seqNum = $tsk->{sequenceNumber};
      next unless ($seqNum >= $taskRank);
	  #$eh->warn("Fixing task $seqNum");		 
	  my $dependant = $tsk->{dependants};
	  #$eh->warn("Dependant is $dependant");
	  #Only decrement the dependant if it's greater than the rank point of the deleted task
	  if($dependant >= $taskRank){
	     $dependant--;
	  }
	  #$eh->warn("New dependant is $dependant");
	  $db->write("update PM_task set dependants=? where taskId=?",[$dependant,$tsk->{taskId}]);
   }
   $self->reorderCollateral("PM_task","taskId","projectId",$projectId);
   
   return $self->www_viewProject($projectId);
}

#-------------------------------------------------------------------
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;
	
	#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;
	}


	#Check Privileges
    return $privilege->insufficient unless $self->_userCanObserveProject($user, $projectId);
	
	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 $taskType = $task->{taskType};
		my $startDate = $task->{startDate};
		my $endDate = $task->{endDate};
		my $duration = $task->{duration};
		my $lagTime = $task->{lagTime};
		my $totalDuration = $duration + $lagTime;
		my $predecessor = $task->{dependants};
		$self->_doGanttTaskResourceDisplay($hash, $task);

		if ($dunits eq 'hours') {
			foreach ($duration, $lagTime, $totalDuration) {
				$_ /= $hoursPerDay;
			}
		}

		my ($durationFloor, $lagTimeFloor, $totalDurationFloor) =
		    map {floor($_)} ($duration, $lagTime, $totalDuration);
		
		# Each day is 23 pixels so calculate the days and round
		$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);
		my @alllag_color = (0x7a, 0xb7, 0xe9);
		$hash->{'task.div.color'} = sprintf "#%02x%02x%02x",
		    ($totalDuration > 0)? do {
			    my $lerp = $lagTime / $totalDuration;
			    map { $zerolag_color[$_] +
				      ($alllag_color[$_] - $zerolag_color[$_]) * $lerp }
				(0..2);
		    } : @zerolag_color;

		$hash->{'task.div.label.left'} = $hash->{'task.div.width'} + 3;
		$hash->{'task.div.label.left'} = 12 if $hash->{'task.div.label.left'} < 12;
		
		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;
			   $totalDuration += $predDayPart;
			   $durationFloor = floor($duration);
			   $totalDurationFloor = floor($totalDuration);
		   }
		   
		}
		
		#Adjust top for MSIE
		my $isMSIE = ($session->env->get("HTTP_USER_AGENT") =~ /msie/i);
		my $divTop =  $isMSIE ? 45 : 45;
		#Start at 45 px and add 20px as the start of the new task
		#Set the propert mutiplier
		my $multiplier = $isMSIE ? 22 : 20;
		$hash->{'task.div.top'} = $divTop + ($multiplier*$taskCount);
		
		my $daysFromStart = $dt->getDaysInInterval($startMonth,$startDate);
		#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'});
		
		# 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} } );  
		$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", "lagTime":"$lagTime", "predecessor":"$predecessor", "type":"$taskType" }|;
		$taskCount++;
		   
		$taskHash->{$seq} = { 
				     'startDate'=>$task->{startDate}, 
				     'endDate'=>$task->{endDate}, 
				     'duration'=>$task->{duration}, 
				     'lagTime'=>$task->{lagTime},
				     '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"));
}

#-------------------------------------------------------------------
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 $self->_userCanManageProjectList($user);
   
   #Set Local Vars
   my $projectId = $form->get("projectId"); 
   my $project = $db->quickHashRef("select * from PM_project where projectId=?",[$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')
   );
   $f->group(
         -name=> "projectObserver",
		 -value=> $form->get("projectObserver") || $project->{projectObserver} || '7',
		 -hoverHelp=> $i18n->get('project observer hoverhelp'),
		 -label => $i18n->get('project observer 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" />
	  </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;
    my $eh = $session->errorHandler;
	
    #Check Privileges
    return $privilege->insufficient unless $self->_userCanManageProjectList($user);
    
	my $now = $dt->time();
	my $uid = $user->userId;
	my $projectId = $form->process("projectId","hidden");
	my $origProjId = $projectId;
	
	#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->{projectObserver} = $form->process("projectObserver","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($origProjId 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->{lagTime} = 0;
	   $props->{startDate} = $dt->time();
	   $props->{endDate} = $dt->time();
	   $props->{taskType} = 'milestone';
       $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   = $self->session;
   my $privilege = $session->privilege;
   my $form      = $session->form;
   my $db        = $session->db;
   my $dt        = $session->datetime;
   my $config    = $session->config;
   my $i18n      = $self->i18n;
   my $user      = $session->user;
   
   #Set variables from form data
   my $projectId = $form->get("projectId");
   my $taskId    = $form->get("taskId") || "new";
  
   #Get the project data
   my $project   = $self->getCollateral('PM_project', 'projectId', $projectId);
   
   #Get the task data
   my $task      = $self->getCollateral('PM_task', 'taskId', $taskId);
   
   #Check Privileges
   return $privilege->insufficient unless $self->_userCanManageProject($user, $projectId);
   
   my $taskType = ($task->{taskType} || 'timed');
   my $seq = $task->{sequenceNumber};
   my $disabledIfUntimed = ($taskType eq 'timed')? "" : " disabled";
   my $disabledIfMilestone = ($taskType ne 'milestone')? "" : " disabled";
   
   #Build the form header
   $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
				});
   $var->{'form.header'} .= WebGUI::Form::hidden($session, {
				-name=>"insertAt",
				-value=>$form->get("insertAt")
				});

   my ($startEpoch, $endEpoch) = $db->quickArray('SELECT startDate, startDate FROM PM_task WHERE projectId = ? ORDER BY sequenceNumber LIMIT 1', [$projectId]);
   my $dependant = $task->{dependants};
   #my $duration = $task->{duration} || (($taskType eq 'timed')? (($project->{durationUnits} eq 'hours')? $project->{hoursPerDay} : 1) : 0);
   my $duration = $task->{duration} || 0;   

   $startEpoch = $endEpoch = time unless defined $startEpoch and defined $endEpoch;
   $startEpoch = $task->{startDate} if $task->{startDate};
   $endEpoch = $task->{endDate} if $task->{endDate};
## Magic number = bad
   $endEpoch += 86400 if $taskType eq 'timed' and !$task->{duration} and !$task->{endDate};
   my ($start, $end) = ($dt->epochToSet($startEpoch), $dt->epochToSet($endEpoch));

   # Set some hidden variables to make it easy to reset data in javascript
   $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.seqNum'} = WebGUI::Form::hidden($session, {
							   -name => "seqNum",
							   -value => $seq,
							  });
   
   my $durationEvents = qq|onchange="durationChanged(this.form, '', true)"  onblur="if (this.value == 0) durationChanged(this.form, '', true)"|;
   my $startDateEvents = qq|onfocus="doCalendar(this.id)" onblur="startDateChanged(this.form, '', true)"|;
   my $endDateEvents = qq|onfocus="doCalendar(this.id)" onblur="endDateChanged(this.form, '', true)"|;

   $var->{'form.duration'} = WebGUI::Form::float($session,{
				-name => "duration",
				-value => $duration,
				-extras => qq|style="width:70%;" $durationEvents $disabledIfMilestone|
				});
   $var->{'form.duration.units'} = $self->_getDurationUnitHashAbbrev->{$project->{durationUnits}};

   $var->{'form.lagTime'} = WebGUI::Form::float($session,{
                                -name => "lagTime",
				-value => (($taskType eq 'timed')? $task->{lagTime} : 'N/A'),
				-extras => qq|style="width:70%;" $durationEvents $disabledIfUntimed|
							 });
   $var->{'form.lagTime.units'} = $self->_getDurationUnitHashAbbrev->{$project->{durationUnits}};

   $var->{'form.start'} = WebGUI::Form::text($session,{
				-name=>"start",
				-value=>$start,
				-size=>"10",
				-maxlength=>"10",
				-extras=>qq|style="width:88%;" $startDateEvents|
				});

   $var->{'form.end'} = WebGUI::Form::text($session,{
				-name=>"end",
				-value=>$end,
				-size=>"10",
				-maxlength=>"10",
				-extras=>qq|style="width:88%;" $endDateEvents $disabledIfUntimed|
				});

   $var->{'form.dependants'} = WebGUI::Form::integer($session,{
				-name=>"dependants",
				-value=>$dependant || "",
				-defaultValue=>"",
				-size=>4,
				-maxlength=>10,
				-extras=>qq|style="width:50%;" onchange="predecessorChanged(this.form, '', true)"|
				});

   my @resources = @{$self->_resourceListOfTask($taskId)};
   my ($searchUserUrlHtml, $searchGroupUrlHtml) = map {
	   my $kind = $_;
	   my $exclude = $self->session->url->escape(join ';', map {$_->{resourceId}} grep {$_->{resourceKind} eq $kind} @resources);
	   my $func = $kind.'SearchPopup';
	   WebGUI::HTML::format($self->getUrl("func=$func;callback=taskEdit_queueAddResource;exclude=$exclude"), 'text');
   } (qw/user group/);

   $var->{'form.addUser.id'} = 'taskEdit_resourceList_addUser_a';
   $var->{'form.addUser.link'} = $searchUserUrlHtml;
   $var->{'form.addUser.text'} = $i18n->get('user add popup hover');
   
   $var->{'form.addGroup.id'} = 'taskEdit_resourceList_addGroup_a';
   $var->{'form.addGroup.link'} = $searchGroupUrlHtml;
   $var->{'form.addGroup.text'} = $i18n->get('group add popup hover');

   $var->{'form.resourceDiv'} =
       '<div id="taskEdit_resourceList_div">'.$self->_innerHtmlOfResources(@resources).'</div>';

   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|onclick="configureForTaskType(this.form)"|,
				});
   $var->{'form.percentComplete'} = WebGUI::Form::float($session, {
				-name => "percentComplete",
				-value => (($taskType eq 'timed')? $task->{percentComplete} : 'N/A'),
				-extras => $disabledIfUntimed
				});
   $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");
   $var->{'assetExtras'} = $config->get("extrasURL").'/wobject/ProjectManager';
   
   $var->{'task_name_label'}        = $i18n->get('task name label');
   $var->{'task_start_label'}       = $i18n->get('task start label');
   $var->{'task_finish_label'}      = $i18n->get('task end label');
   $var->{'task_duration_label'}    = $i18n->get('task duration label');
   $var->{'task_lagTime_label'}     = $i18n->get('task lag time label');
   $var->{'task_predecessor_label'} = $i18n->get('task predecessor edit label');
   $var->{'task_complete_label'}    = $i18n->get('project complete label');
   $var->{'task_resource_label'}    = $i18n->get('task resource label');
   $var->{'task_save_label'}        = $i18n->get('task save label');

   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 $eh = $session->errorHandler;

   my $projectId = $form->get("projectId");
   my $project = $db->quickHashRef("select * from PM_project where projectId=".$db->quote($projectId));

   #Check Privileges
   return $privilege->insufficient unless $self->_userCanManageProject($user, $projectId);
   
   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} = $isUntimed? 0 : $form->process("lagTime","text");
   $props->{startDate} = $form->process("start","date");
   $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} = $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->{lastUpdateDate} = $now;
   $props->{lastUpdatedBy} = $user->userId;

   # 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) {
      #$eh->warn("Inserting at $insertAt");
	  my $tasks = $db->buildArrayRefOfHashRefs("select * from PM_task where projectId=? order by sequenceNumber",[$projectId]);
	  my $taskLen = scalar(@{$tasks});
	  #$eh->warn("Task Length = $taskLen");
	  foreach my $task (@{$tasks}) {
	     my $seqNum = $task->{sequenceNumber};
		 next unless ($seqNum >= $insertAt);
		 #$eh->warn("Fixing task $seqNum");		 
		 my $newSeq = $seqNum + 1;
		 if($seqNum eq $taskLen) {
		    $newSeq = $insertAt;
		 }
		 #$eh->warn("New seqNum is $newSeq");
		 my $dependant = $task->{dependants};
		 #$eh->warn("Dependant is $dependant");
		 #Only increment the dependant if it's greater than or equal to the insertAt point
		 if($dependant >= $insertAt){
		    $dependant++;
		 }
		 #$eh->warn("New dependant is $dependant");
		 $db->write("update PM_task set sequenceNumber=?, dependants=? where taskId=?",[$newSeq,$dependant,$task->{taskId}]);
	  }
	  $self->reorderCollateral("PM_task","taskId","projectId",$projectId);
   }
  
   $self->updateProject($projectId);
   $self->_clobberImproperDependants($projectId);
   $self->_updateDependantDates($projectId);
   
   return $self->www_viewProject($projectId,$taskId);
}

#-------------------------------------------------------------------
sub www_groupSearchPopup {
	my $self = shift;
	my %args = (func => 'groupSearchPopup',
		    i18nprefix => 'group add popup',
		    queryCallback => sub { $self->_groupSearchQuery(@_) },
		   );
	$self->_resourceSearchPopup(%args);
}

#-------------------------------------------------------------------
sub www_innerHtmlOfResources {
	my $self = shift;
	my @resources = map {
		my ($resourceKind, $resourceId) = split / /, $_, 2;
		{ resourceKind => $resourceKind, resourceId => $resourceId }
	} split /\;/, $self->session->form->param('resources');
	return $self->_innerHtmlOfResources(@resources);
}

#-------------------------------------------------------------------
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 $self->_userCanManageProject($user, $projectId);
   
   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 $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);
   }
   
   $self->updateProject($projectId);
   $self->_updateDependantDates($projectId);
   return $self->www_drawGanttChart();
}

#-------------------------------------------------------------------
sub www_userSearchPopup {
	my $self = shift;

	my %args = (func => 'userSearchPopup',
		    i18nprefix => 'user add popup',
		    queryCallback => sub { $self->_userSearchQuery(@_) },
		   );
	$self->_resourceSearchPopup(%args);
}

#-------------------------------------------------------------------
sub www_viewProject {
	my $self      = shift;
	my $var       = {};
    
	#Declare method variables
	my $session     = $self->session;
    my $privilege   = $session->privilege;
    my $form        = $session->form;
    my $db          = $session->db;
    my $dt          = $session->datetime;
    my $i18n        = $self->i18n;
    my $user        = $session->user;
    my $config      = $session->config;
	my $style       = $session->style;
	my $eh          = $session->errorHandler;
	my $projectId   = shift || $form->get("projectId");
		
    #Check Privileges
	return $privilege->insufficient unless $self->_userCanObserveProject($user, $projectId);
	
    #Set extras template variables
    my $extras            = $config->get("extrasURL");
	my $assetExtras       = $config->get("extrasURL")."/wobject/ProjectManager";	
    $var->{'extras'     } = $assetExtras;
	$var->{'extras.base'} = $extras;
    
	
	#Set page styles
	$style->setLink($assetExtras."/subModal.css", { 
                        rel=>"stylesheet", 
                        type=>"text/css", 
                    }
    );
    $style->setLink($assetExtras."/taskEdit.css", {
                        rel=>"stylesheet",
                        type=>"text/css",
                    }
    );
	$style->setLink($extras."/calendar/calendar-win2k-1.css",{
                        rel=>"stylesheet",
                        type=>"text/css",
                    }
    );
	$style->setLink($assetExtras."/cMenu.css",{
                        rel=>"stylesheet",
                        type=>"text/css",
                    }
    );
	
    #Set page scripts
	$style->setScript($assetExtras."/cMenu.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" 
                      }
    );
	$style->setScript($extras."/calendar/lang/calendar-en.js",{ 
                          type=>"text/javascript" 
                      }
    );
	$style->setScript($extras."/calendar/calendar-setup.js",{ 
                          type=>"text/javascript" 
                      }
    );
	$style->setScript($assetExtras."/projectDisplay.js",{ 
                          type=>"text/javascript" 
                      }
    );
	$style->setScript($assetExtras."/taskEdit.js",{ 
                          type=>"text/javascript" 
                      }
    );
	
	#Get Project Data
    my $sql          = q|select * from PM_project where projectId=?|;   
	my $project      = $db->quickHashRef($sql,[$projectId]);
	
    #Get User Privileges
    my $canEditTasks = $self->_userCanManageProject($user, $projectId);
	
    #Set Duration Units
    my $dunits = $self->_getDurationUnitHashAbbrev->{$project->{durationUnits}};
	
	#Set some variables for use by Java Script
	$var->{'project.durationUnits'} = $dunits;
	$var->{'project.hoursPerDay'  } = $project->{hoursPerDay} || "0";
	
	#Set Task Table Lables	
	$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");
	$var->{'form.untimedPredecessor.error' } = $i18n->get("task untimedPredecessor error");
	
    #Get Tasks
    $sql = "select * from PM_task where projectId=? order by sequenceNumber asc";
    my $data = $db->buildArrayRefOfHashRefs($sql,[$projectId]);
    
    #Build Task Data
	my @taskList = ();
	my $count = 0;
	foreach my $row (@{$data}) {
	   my $hash      = {};
	   my $seq       = $row->{sequenceNumber};
	   my $id        = $row->{taskId};
	   my $taskType  = $row->{taskType};
	   my $isUntimed = ($taskType ne 'timed');
	   my $startDate = $dt->epochToSet($row->{startDate});
	   my $endDate   = $dt->epochToSet($row->{endDate});
	   my $duration  = $row->{duration};
	   my $lagTime   = $row->{lagTime};
	   
	   $hash->{'task.number'} = $seq;
	   $hash->{'task.row.id'} = $id;
	   $hash->{'task.name'  } = $row->{taskName};
	   	  
	   if($canEditTasks) {
		   my $suffix = '_'.$id;

		   $hash->{'task.start'} = WebGUI::Form::text($session,{
                         -name=>'start'.$suffix,
			 -value=>$startDate,
			 -size=>"10",
			 -maxlength=>"10",
			 -extras=>qq<onfocus="doCalendar(this.id);" class="taskdate" onblur="startDateChanged(this.form, '$suffix', false);">
                        });
		  
		  $hash->{'task.start'} .= WebGUI::Form::hidden($session,{
			-name=>'orig_start'.$suffix,
			-value=>$startDate,
			});

		  $hash->{'task.dependants'} = WebGUI::Form::integer($session,{
			-name=>'dependants'.$suffix,
			-value=>$row->{dependants} || "",
			-defaultValue=>"", 
			-extras=>qq|class="taskdependant" onchange="predecessorChanged(this.form, '$suffix', false);"|
			});
		  $hash->{'task.dependants'} .= WebGUI::Form::hidden($session,{
			-name=>'orig_dependants'.$suffix,
			-value=>$row->{dependants},
			});

		  $hash->{'task.end'} = WebGUI::Form::text($session,{
		                                               -name=>'end'.$suffix,
								     -value=>$endDate,
								     -size=>"10",
								     -maxlength=>"10",
								     -extras=>qq|class="taskdate" onfocus="doCalendar(this.id);" onblur="endDateChanged(this.form, '$suffix', false);"|
		                                            });
		  $hash->{'task.end'} .= WebGUI::Form::hidden($session,{
			-name=>'orig_end'.$suffix,
			-value=>$endDate,
			});

		  # Don't display duration for untimed tasks.
		  if ($isUntimed) {
			$hash->{'task.duration'} = $row->{duration};
			 $hash->{'task.duration'} .= WebGUI::Form::hidden($session,{
				-name=>'duration'.$suffix,
				-value=>$duration,
				});
		  } 
          else {
			  $hash->{'task.duration'} = WebGUI::Form::float($session,{
				-name=>'duration'.$suffix,
				-value=>$duration, 
				-extras=>qq|class="taskduration" onchange="durationChanged(this.form, '$suffix', false);" |
				});
			 
	      }
	      $hash->{'task.lagTime'} = WebGUI::Form::hidden($session,{
								       -name => 'lagTime'.$suffix,
								       -value => $lagTime,
								      });
		   $hash->{'task.taskType'} = WebGUI::Form::hidden($session, {
									      -name => 'taskType'.$suffix,
									      -value => $taskType,
									      });
		   $hash->{'task.seqNum'} = WebGUI::Form::hidden($session, {
									    -name => 'seqNum'.$suffix,
									    -value => $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;
	   if($canEditTasks) {
	      my $num                           = $row->{sequenceNumber};
          $hash->{'task.edit.url'         } = $self->getUrl("func=editTask;projectId=$projectId;taskId=".$row->{taskId});
		  $hash->{'task.edit.label'       } = $i18n->get("edit task label");
		  $hash->{'task.insertAbove.url'  } = $self->getUrl("func=editTask;projectId=$projectId;taskId=new;insertAt=$num");
		  $hash->{'task.insertAbove.label'} = $i18n->echo("Insert Task Above");
		  $hash->{'task.insertBelow.url'  } = $self->getUrl("func=editTask;projectId=$projectId;taskId=new;insertAt=".($num+1));
		  $hash->{'task.insertBelow.label'} = $i18n->echo("Insert Task Below");
		  $hash->{'task.delete.url'       } = $self->getUrl("func=deleteTask;taskId=".$row->{taskId});
		  $hash->{'task.delete.label'     } = $i18n->echo("Delete Task");
	   }
	   push(@taskList, $hash);
	}
	$var->{'task.loop'} = \@taskList;
	
	#Set some javascript stuff;
	my $taskLength = scalar(@taskList);
	$var->{'project.task.length'} = $taskLength;
	
	if($canEditTasks) {
	   #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($canEditTasks) {
		$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 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"));
}


1;
