diff --git a/config/zentaopms.php b/config/zentaopms.php
index a10d6a941c..5e57c970f7 100644
--- a/config/zentaopms.php
+++ b/config/zentaopms.php
@@ -224,6 +224,7 @@ $config->openMethods[] = 'personnel.unbindwhitelist';
$config->openMethods[] = 'tree.viewhistory';
$config->openMethods[] = 'doc.createbasicinfo';
$config->openMethods[] = 'project.createguide';
+$config->openMethods[] = 'task.editteam';
/* Define the tables. */
define('TABLE_COMPANY', '`' . $config->db->prefix . 'company`');
@@ -265,6 +266,7 @@ define('TABLE_PROJECT', '`' . $config->db->prefix . 'project`');
define('TABLE_EXECUTION', '`' . $config->db->prefix . 'project`');
define('TABLE_TASK', '`' . $config->db->prefix . 'task`');
define('TABLE_TASKSPEC', '`' . $config->db->prefix . 'taskspec`');
+define('TABLE_TASKTEAM', '`' . $config->db->prefix . 'taskteam`');
define('TABLE_TEAM', '`' . $config->db->prefix . 'team`');
define('TABLE_PROJECTADMIN', '`' . $config->db->prefix . 'projectadmin`');
define('TABLE_PROJECTPRODUCT','`' . $config->db->prefix . 'projectproduct`');
diff --git a/db/update17.6.sql b/db/update17.6.sql
new file mode 100644
index 0000000000..4774842924
--- /dev/null
+++ b/db/update17.6.sql
@@ -0,0 +1,13 @@
+CREATE TABLE `zt_taskteam` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `task` mediumint(8) unsigned NOT NULL,
+ `account` char(30) NOT NULL,
+ `estimate` decimal(12,2) NOT NULL,
+ `consumed` decimal(12,2) NOT NULL,
+ `left` decimal(12,2) NOT NULL,
+ `transfer` char(30) NOT NULL,
+ `status` enum('wait','doing','done') NOT NULL DEFAULT 'wait',
+ `order` tinyint(3) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `task` (`task`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/db/zentao.sql b/db/zentao.sql
index ac6389009a..a986e9e742 100755
--- a/db/zentao.sql
+++ b/db/zentao.sql
@@ -1606,6 +1606,20 @@ CREATE TABLE IF NOT EXISTS `zt_taskspec` (
`deadline` date NOT NULL,
UNIQUE KEY `task` (`task`,`version`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+-- DROP TABLE IF EXISTS `zt_taskteam`;
+CREATE TABLE `zt_taskteam` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `task` mediumint(8) unsigned NOT NULL,
+ `account` char(30) NOT NULL,
+ `estimate` decimal(12,2) NOT NULL,
+ `consumed` decimal(12,2) NOT NULL,
+ `left` decimal(12,2) NOT NULL,
+ `transfer` char(30) NOT NULL,
+ `status` enum('wait','doing','done') NOT NULL DEFAULT 'wait',
+ `order` tinyint(3) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `task` (`task`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- DROP TABLE IF EXISTS `zt_team`;
CREATE TABLE IF NOT EXISTS `zt_team` (
`id` mediumint(8) unsigned NOT NULL auto_increment,
diff --git a/module/block/control.php b/module/block/control.php
old mode 100644
new mode 100755
index e88e1bfca3..18ba598678
--- a/module/block/control.php
+++ b/module/block/control.php
@@ -1206,6 +1206,8 @@ class block extends control
->andWhere('parent')->lt(1)
->fetch();
+ $this->weekly->save($this->session->project, $date);
+
$this->view->pv = $this->weekly->getPV($this->session->project, $today);
$this->view->ev = $this->weekly->getEV($this->session->project, $today);
$this->view->ac = $this->weekly->getAC($this->session->project, $today);
@@ -1301,23 +1303,17 @@ class block extends control
$projectID = $this->session->project;
$project = $this->loadModel('project')->getByID($projectID);
- $begin = $project->begin;
- $today = helper::today();
- $end = date('Y-m-d', strtotime($today));
+ $projectWeekly = $this->dao->select('*')->from(TABLE_WEEKLYREPORT)->where('project')->eq($projectID)->orderBy('weekStart_asc')->fetchAll('weekStart');
$charts['PV'] = '[';
$charts['EV'] = '[';
$charts['AC'] = '[';
- $i = 1;
- while($begin < $end)
+ foreach($projectWeekly as $weekStart => $data)
{
- $charts['labels'][] = $this->lang->block->time . $i . $this->lang->block->month;
- $charts['PV'] .= $this->weekly->getPV($projectID, $begin) . ',';
- $charts['EV'] .= $this->weekly->getEV($projectID, $begin) . ',';
- $charts['AC'] .= $this->weekly->getAC($projectID, $begin) . ',';
- $stageEnd = $this->weekly->getThisSunday($begin);
- $begin = date('Y-m-d', strtotime("$stageEnd + 30 day"));
- $i ++;
+ $charts['labels'][] = $weekStart;
+ $charts['PV'] .= $data->pv . ',';
+ $charts['EV'] .= $data->ev . ',';
+ $charts['AC'] .= $data->ac . ',';
}
$charts['PV'] .= ']';
diff --git a/module/block/model.php b/module/block/model.php
index 232e965288..3086c24bfb 100644
--- a/module/block/model.php
+++ b/module/block/model.php
@@ -171,7 +171,7 @@ class blockModel extends model
$tasks = $this->dao->select("count(t1.id) as tasks, count(if(t1.status = 'done', 1, null)) as doneTasks")->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_PROJECT)->alias('t2')->on("t1.project = t2.id")
->leftJoin(TABLE_EXECUTION)->alias('t3')->on("t1.execution = t3.id")
- ->leftJoin(TABLE_TEAM)->alias('t4')->on("t4.root = t1.id and t4.type = 'task' and t4.account = '{$this->app->user->account}'")
+ ->leftJoin(TABLE_TASKTEAM)->alias('t4')->on("t4.task = t1.id and t4.account = '{$this->app->user->account}'")
->where("(t1.assignedTo = '{$this->app->user->account}' or (t1.mode = 'multi' and t4.`account` = '{$this->app->user->account}') )")
->andWhere('(t2.status')->ne('suspended')
->orWhere('t3.status')->ne('suspended')
diff --git a/module/common/view/sortable.html.php b/module/common/view/sortable.html.php
index 45f3f38175..239f719745 100644
--- a/module/common/view/sortable.html.php
+++ b/module/common/view/sortable.html.php
@@ -18,7 +18,7 @@ $(document).ready(function()
var $tbody = $(this);
$tbody.sortable(
{
- selector: 'tr',
+ selector: typeof(sortSelector) == 'undefined' ? 'tr' : sortSelector,
dragCssClass: 'drag-row',
trigger: $tbody.find('.sort-handler').length ? '.sort-handler' : null,
finish: function(e)
diff --git a/module/execution/config.php b/module/execution/config.php
index af413a93f7..e3faacb40e 100644
--- a/module/execution/config.php
+++ b/module/execution/config.php
@@ -71,7 +71,6 @@ $config->execution->search['fields']['canceledBy'] = $lang->task->canceledBy
$config->execution->search['fields']['lastEditedBy'] = $lang->task->lastEditedBy;
$config->execution->search['fields']['mailto'] = $lang->task->mailto;
-$config->execution->search['fields']['finishedList'] = $lang->task->finishedList;
$config->execution->search['fields']['openedDate'] = $lang->task->openedDate;
$config->execution->search['fields']['deadline'] = $lang->task->deadline;
@@ -107,7 +106,6 @@ $config->execution->search['params']['cancelBy'] = array('operator' => '='
$config->execution->search['params']['lastEditedBy'] = array('operator' => '=', 'control' => 'select', 'values' => 'users');
$config->execution->search['params']['mailto'] = array('operator' => 'include', 'control' => 'select', 'values' => 'users');
-$config->execution->search['params']['finishedList'] = array('operator' => 'include', 'control' => 'select', 'values' => 'users');
$config->execution->search['params']['openedDate'] = array('operator' => '=', 'control' => 'input', 'values' => '', 'class' => 'date');
$config->execution->search['params']['deadline'] = array('operator' => '=', 'control' => 'input', 'values' => '', 'class' => 'date');
diff --git a/module/execution/model.php b/module/execution/model.php
index 77f71b2a11..32b6aaa4e0 100644
--- a/module/execution/model.php
+++ b/module/execution/model.php
@@ -2343,7 +2343,8 @@ class executionModel extends model
{
$this->loadModel('task');
- $tasks = $this->dao->select('id,execution,assignedTo,story,consumed,status')->from(TABLE_TASK)->where('id')->in($this->post->tasks)->fetchAll('id');
+ $execution = $this->getByID($executionID);
+ $tasks = $this->dao->select('id,execution,assignedTo,story,consumed,status')->from(TABLE_TASK)->where('id')->in($this->post->tasks)->fetchAll('id');
foreach($tasks as $task)
{
/* Save the assignedToes and stories, should linked to execution. */
@@ -2351,8 +2352,9 @@ class executionModel extends model
$stories[$task->story] = $task->story;
$data = new stdclass();
+ $data->project = $execution->project;
$data->execution = $executionID;
- $data->status = $task->consumed > 0 ? 'doing' : 'wait';
+ $data->status = $task->consumed > 0 ? 'doing' : 'wait';
if($task->status == 'cancel')
{
@@ -2931,29 +2933,6 @@ class executionModel extends model
->fetchGroup('root');
}
- /**
- * Get the skip members of the team.
- *
- * @param array $teams
- * @param string $begin
- * @param string $end
- * @access public
- * @return array
- */
- public function getTeamSkip($teams, $begin, $end)
- {
- $members = array();
- foreach($teams as $account => $team)
- {
- if($account == $end) break;
- if(!empty($begin) and $account != $begin and empty($members)) continue;
-
- $members[$account] = $team;
- }
-
- return $members;
- }
-
/**
* Get teams which can be imported.
*
@@ -3669,7 +3648,7 @@ class executionModel extends model
if(empty($tasks)) return array();
- $taskTeam = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->in(array_keys($tasks))->andWhere('type')->eq('task')->fetchGroup('root');
+ $taskTeam = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task');
if(!empty($taskTeam))
{
foreach($taskTeam as $taskID => $team) $tasks[$taskID]->team = $team;
@@ -4642,7 +4621,7 @@ class executionModel extends model
echo '
';
common::printIcon('execution', 'start', "executionID={$execution->id}", $execution, 'list', '', '', 'iframe', true);
$class = !empty($execution->children) ? 'disabled' : '';
- common::printIcon('task', 'create', "executionID={$execution->id}", $execution, 'list', '', '', $class, false, "data-app='execution'");
+ common::printIcon('task', 'create', "executionID={$execution->id}", '', 'list', '', '', $class, false, "data-app='execution'");
if($execution->type == 'stage')
{
diff --git a/module/kanban/model.php b/module/kanban/model.php
index 50bca7f45d..511694c98a 100755
--- a/module/kanban/model.php
+++ b/module/kanban/model.php
@@ -1534,19 +1534,29 @@ class kanbanModel extends model
if($browseType == 'bug') $cardList = $this->loadModel('bug')->getExecutionBugs($executionID);
if($browseType == 'task') $cardList = $this->loadModel('execution')->getKanbanTasks($executionID);
+ $multiTasks = array();
if($browseType == 'task' and $groupBy == 'assignedTo')
{
foreach($cardList as $id => $task)
{
- if($task->mode == 'multi')
- {
- $task->team = $this->dao->select('t1.account,t2.realname')->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
- ->where('t1.root')->eq($id)->andWhere('t1.type')->eq('task')->orderBy('t1.order')->fetchPairs('account');
- }
+ if($task->mode == 'multi') $multiTasks[$id] = $task;
}
}
+ $taskTeams = $this->dao->select('t1.account,t1.task,t2.realname')->from(TABLE_TASKTEAM)->alias('t1')
+ ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
+ ->where('t1.task')->in(array_keys($multiTasks))
+ ->orderBy('t1.order')
+ ->fetchGroup('task', 'account');
+ foreach($multiTasks as $taskID => $task)
+ {
+ if(!isset($taskTeams[$taskID])) continue;
+
+ $teamPairs = array();
+ foreach($taskTeams[$taskID] as $account => $team) $teamPairs[$account] = $team->realname;
+ $task->team = $teamPairs;
+ }
+
/* Get objects cards menus. */
if($browseType == 'story') $storyCardMenu = $this->getKanbanCardMenu($executionID, $cardList, 'story');
if($browseType == 'bug') $bugCardMenu = $this->getKanbanCardMenu($executionID, $cardList, 'bug');
diff --git a/module/my/model.php b/module/my/model.php
index 89101f568a..14bc238ccf 100644
--- a/module/my/model.php
+++ b/module/my/model.php
@@ -588,6 +588,7 @@ class myModel extends model
->leftJoin(TABLE_EXECUTION)->alias('t2')->on("t1.execution = t2.id")
->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id')
->leftJoin(TABLE_PROJECT)->alias('t4')->on("t1.project = t4.id")
+ ->leftJoin(TABLE_TASKTEAM)->alias('t5')->on("t1.id = t5.task")
->where($query)
->andWhere('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
@@ -598,7 +599,7 @@ class myModel extends model
->orWhere('t1.closedBy')->eq($account)
->orWhere('t1.canceledBy')->eq($account)
->orWhere('t1.finishedby', 1)->eq($account)
- ->orWhere('t1.finishedList')->like("%,{$account},%")
+ ->orWhere('t5.status')->eq("done")
->orWhere('t1.id')->in($taskIDList)
->markRight(1)
->fi()
@@ -612,7 +613,7 @@ class myModel extends model
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', false);
- $taskTeam = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->in(array_keys($tasks))->andWhere('type')->eq('task')->fetchGroup('root');
+ $taskTeam = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task');
if(!empty($taskTeam))
{
foreach($taskTeam as $taskID => $team) $tasks[$taskID]->team = $team;
diff --git a/module/programplan/model.php b/module/programplan/model.php
index 61d186ffdf..4efccd5daa 100755
--- a/module/programplan/model.php
+++ b/module/programplan/model.php
@@ -224,7 +224,7 @@ class programplanModel extends model
if(empty($selectCustom)) $selectCustom = $this->loadModel('setting')->getItem("owner={$owner}&module={$module}§ion={$section}&key={$object}");
$tasks = $this->dao->select('*')->from(TABLE_TASK)->where('deleted')->eq(0)->andWhere('execution')->in($planIdList)->orderBy('execution_asc, order_asc, id_desc')->fetchAll('id');
- $taskTeams = $this->dao->select('root,account')->from(TABLE_TEAM)->where('type')->eq('task')->andWhere('root')->in(array_keys($tasks))->fetchGroup('root', 'account');
+ $taskTeams = $this->dao->select('task,account')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task', 'account');
$users = $this->loadModel('user')->getPairs('noletter');
if($baselineID)
@@ -381,33 +381,42 @@ class programplanModel extends model
$tasksGroup = $this->dao->select('*')->from(TABLE_TASK)->where('deleted')->eq(0)->andWhere('execution')->in($planIdList)->fetchGroup('assignedTo','id');
$users = $this->loadModel('user')->getPairs('noletter');
- $tasksMap = array();
+ $tasksMap = array();
+ $multiTasks = array();
foreach($tasksGroup as $group => $tasks)
{
foreach($tasks as $id => $task)
{
- if($task->mode == 'multi')
- {
- $team = $this->dao->select('t1.*,t2.realname')->from(TABLE_TEAM)->alias('t1')
- ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
- ->where('t1.root')->eq($id)->andWhere('t1.type')->eq('task')->orderBy('t1.order')->fetchAll('account');
- foreach($team as $account => $member)
- {
- if($account == $group) continue;
- if(!isset($taskGroups[$account])) $taskGroups[$account] = array();
-
- $taskGroups[$account][$id] = clone $task;
- $taskGroups[$account][$id]->id = $id . '_' . $account;
- $taskGroups[$account][$id]->realID = $id;
- $taskGroups[$account][$id]->assignedTo = $account;
- $taskGroups[$account][$id]->realname = $member->realname;
- }
- }
-
+ if($task->mode == 'multi') $multiTasks[$id] = $group;
$tasksMap[$task->id] = $task;
}
}
+ if($multiTasks)
+ {
+ $taskTeams = $this->dao->select('t1.*,t2.realname')->from(TABLE_TASKTEAM)->alias('t1')
+ ->leftJoin(TABLE_USER)->alias('t2')->on('t1.account = t2.account')
+ ->where('t1.task')->in(array_keys($multiTasks))
+ ->orderBy('t1.order')
+ ->fetchGroup('task', 'id');
+ foreach($taskTeams as $taskID => $team)
+ {
+ $group = $multiTasks[$taskID];
+ foreach($team as $member)
+ {
+ $account = $member->account;
+ if($account == $group) continue;
+ if(!isset($taskGroups[$account])) $taskGroups[$account] = array();
+
+ $taskGroups[$account][$taskID] = clone $tasksMap[$taskID];
+ $taskGroups[$account][$taskID]->id = $taskID . '_' . $account;
+ $taskGroups[$account][$taskID]->realID = $taskID;
+ $taskGroups[$account][$taskID]->assignedTo = $account;
+ $taskGroups[$account][$taskID]->realname = $member->realname;
+ }
+ }
+ }
+
$groupID = 0;
foreach($tasksGroup as $group => $tasks)
{
diff --git a/module/report/model.php b/module/report/model.php
index 4d17beaf4a..52e00c69f4 100644
--- a/module/report/model.php
+++ b/module/report/model.php
@@ -358,10 +358,9 @@ class reportModel extends model
$taskGroups[$task->assignedTo][$task->id] = $task;
}
- $multiTaskTeams = $this->dao->select('*')->from(TABLE_TEAM)->where('type')->eq('task')
- ->andWhere('root')->in(array_keys($allTasks))
+ $multiTaskTeams = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($allTasks))
->beginIF($dept)->andWhere('account')->in(array_keys($deptUsers))->fi()
- ->fetchGroup('account', 'root');
+ ->fetchGroup('account', 'task');
foreach($multiTaskTeams as $assignedTo => $multiTasks)
{
foreach($multiTasks as $task)
diff --git a/module/task/control.php b/module/task/control.php
index 2da2dbf5b3..3a01acf696 100755
--- a/module/task/control.php
+++ b/module/task/control.php
@@ -719,12 +719,12 @@ class task extends control
/* Get edited tasks. */
$tasks = $this->dao->select('*')->from(TABLE_TASK)->where('id')->in($taskIDList)->fetchAll('id');
- $teams = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->in($taskIDList)->andWhere('type')->eq('task')->fetchGroup('root', 'account');
+ $teams = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in($taskIDList)->fetchGroup('task', 'id');
/* Get execution teams. */
$executionIDList = array();
foreach($tasks as $task) if(!in_array($task->execution, $executionIDList)) $executionIDList[] = $task->execution;
- $executionTeams = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->in($executionIDList)->andWhere('type')->eq('execution')->fetchGroup('root', 'account');
+ $executionTeams = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in($executionIDList)->fetchGroup('task', 'id');
/* Judge whether the editedTasks is too large and set session. */
$countInputVars = count($tasks) * (count(explode(',', $this->config->task->custom->batchEditFields)) + 3);
@@ -787,9 +787,9 @@ class task extends control
$this->commonAction($taskID);
$task = $this->task->getByID($taskID);
- if(!empty($task->team) and $task->mode == 'multi')
+ if(!empty($task->team) and $task->mode == 'multi' and strpos('done,cencel,closed', $task->status) === false)
{
- return $this->editTeam($executionID, $task, $kanbanGroup);
+ return $this->editTeam($executionID, $taskID, $kanbanGroup, $from);
}
if(!empty($_POST))
@@ -841,9 +841,9 @@ class task extends control
$members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
/* Compute next assignedTo. */
- if(!empty($task->team))
+ if(!empty($task->team) and strpos('done,cencel,closed', $task->status) === false)
{
- $task->nextUser = $this->task->getNextUser(array_keys($task->team), $task->assignedTo);
+ $task->nextUser = $this->task->getAssignedTo4Multi($task->team, $task, 'next');
$members = $this->task->getMemberPairs($task);
}
@@ -903,7 +903,7 @@ class task extends control
if(!is_array($taskIDList)) return print(js::locate($this->createLink('execution', 'task', "executionID=$execution"), 'parent'));
$taskIDList = array_unique($taskIDList);
- $muletipleTasks = $this->dao->select('root , account')->from(TABLE_TEAM)->where('type')->eq('task')->andWhere('root')->in($taskIDList)->fetchGroup('root', 'account');
+ $muletipleTasks = $this->dao->select('task, account')->from(TABLE_TASKTEAM)->where('task')->in($taskIDList)->fetchGroup('task', 'account');
$tasks = $this->task->getByList($taskIDList);
$this->loadModel('action');
foreach($tasks as $taskID => $task)
@@ -1098,12 +1098,15 @@ class task extends control
return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent'));
}
+ $assignedTo = empty($task->assignedTo) ? $this->app->user->account : $task->assignedTo;
+ if(!empty($task->team)) $assignedTo = $this->task->getAssignedTo4Multi($task->team, $task);
+
$this->view->title = $this->view->execution->name . $this->lang->colon .$this->lang->task->start;
$this->view->position[] = $this->lang->task->start;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
$this->view->members = $this->loadModel('user')->getTeamMemberPairs($task->execution, 'execution', 'nodeleted');
- $this->view->assignedTo = $task->assignedTo == '' ? $this->app->user->account : $task->assignedTo;
+ $this->view->assignedTo = $assignedTo;
$this->display();
}
@@ -1177,6 +1180,7 @@ class task extends control
$this->view->task = $this->task->getById($taskID);
$this->view->estimates = $this->task->getTaskEstimate($taskID);
$this->view->title = $this->lang->task->record;
+ $this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
$this->display();
}
@@ -1202,11 +1206,10 @@ class task extends control
return print(js::locate($url, 'parent'));
}
- $estimate = $this->task->getEstimateById($estimateID);
-
$this->view->title = $this->lang->task->editEstimate;
$this->view->position[] = $this->lang->task->editEstimate;
$this->view->estimate = $estimate;
+ $this->view->task = $this->task->getById($estimate->task);
$this->display();
}
@@ -1220,35 +1223,21 @@ class task extends control
*/
public function deleteEstimate($estimateID, $confirm = 'no')
{
- $estimate = $this->task->getEstimateById($estimateID);
- $task = $this->task->getById($estimate->task);
- if($confirm == 'no' and $task->consumed - $estimate->consumed != 0)
+ if($confirm == 'no')
{
return print(js::confirm($this->lang->task->confirmDeleteEstimate, $this->createLink('task', 'deleteEstimate', "estimateID=$estimateID&confirm=yes")));
}
- elseif($confirm == 'no' and $task->consumed - $estimate->consumed == 0)
- {
- return print(js::confirm($this->lang->task->confirmDeleteLastEstimate, $this->createLink('task', 'deleteEstimate', "estimateID=$estimateID&confirm=yes")));
- }
else
{
+ $estimate = $this->task->getEstimateById($estimateID);
+ $task = $this->task->getById($estimate->task);
$changes = $this->task->deleteEstimate($estimateID);
if(dao::isError()) return print(js::error(dao::getError()));
$actionID = $this->loadModel('action')->create('task', $estimate->task, 'DeleteEstimate');
$this->action->logHistory($actionID, $changes);
- if($task->consumed - $estimate->consumed == 0)
- {
- $actionID = $this->loadModel('action')->create('task', $estimate->task, 'Adjusttasktowait');
- }
- if($task->consumed - $estimate->consumed == 0)
- {
- return print(js::reload('parent.parent'));
- }
- else
- {
- return print(js::reload('parent'));
- }
+
+ return print(js::reload('parent'));
}
}
@@ -1346,21 +1335,10 @@ class task extends control
if(!empty($task->team))
{
- $teams = array_keys($task->team);
-
- $task->nextBy = $this->task->getNextUser($teams, $this->app->user->account);
- $task->myConsumed = isset($task->team[$this->app->user->account]) ? $task->team[$this->app->user->account]->consumed : 0;
-
- $lastAccount = end($teams);
- $finishedUsers = $this->task->getFinishedUsers($taskID, $teams);
- if(($lastAccount != $task->assignedTo and $task->mode == 'linear') or ($task->mode == 'multi' and count($teams) != count($finishedUsers)))
- {
- $members = $this->task->getMemberPairs($task);
- }
- else
- {
- $task->nextBy = $task->openedBy;
- }
+ $task->nextBy = $this->task->getAssignedTo4Multi($task->team, $task, 'next');
+ $task->myConsumed = 0;
+ $currentTeam = $this->task->getTeamByAccount($task->team);
+ if($currentTeam) $task->myConsumed = $currentTeam->consumed;
}
$this->view->title = $this->view->execution->name . $this->lang->colon .$this->lang->task->finish;
@@ -1817,14 +1795,8 @@ class task extends control
return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent'));
}
- if(!empty($this->view->task->team))
- {
- $members = array();
- foreach($this->view->task->team as $account => $member) $members[$account] = zget($this->view->members, $account);
- $this->view->members = $members;
- }
-
if(!isset($this->view->members[$this->view->task->finishedBy])) $this->view->members[$this->view->task->finishedBy] = $this->view->task->finishedBy;
+
$this->view->title = $this->view->execution->name . $this->lang->colon . $this->lang->task->activate;
$this->view->position[] = $this->lang->task->activate;
$this->view->users = $this->loadModel('user')->getPairs('noletter');
@@ -2107,10 +2079,7 @@ class task extends control
}
/* Get team for multiple task. */
- $taskTeam = $this->dao->select('*')->from(TABLE_TEAM)
- ->where('root')->in(array_keys($tasks))
- ->andWhere('type')->eq('task')
- ->fetchGroup('root');
+ $taskTeam = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task');
/* Process multiple task info. */
if(!empty($taskTeam))
@@ -2332,14 +2301,15 @@ class task extends control
* Update assign of multi task.
*
* @param int $requestID
- * @param object $task
+ * @param object $taskID
* @param string $kanbanGroup
+ * @param string $from
* @access public
* @return void
*/
- public function editTeam($executionID, $task, $kanbanGroup = 'default')
+ public function editTeam($executionID, $taskID, $kanbanGroup = 'default', $from = '')
{
- $taskID = $task->id;
+ $task = $this->task->getById($taskID);
$this->commonAction($taskID);
if(!empty($_POST))
@@ -2361,9 +2331,18 @@ class task extends control
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'success'));
if(isonlybody())
{
- $task = $this->task->getById($taskID);
$execution = $this->execution->getByID($task->execution);
- if(($this->app->tab == 'execution' or $this->config->vision == 'lite') and $this->app->tab == 'execution')
+
+ if(($this->app->tab == 'execution' or ($this->config->vision == 'lite' and $this->app->tab == 'project' and $this->session->kanbanview == 'kanban')) and $execution->type == 'kanban')
+ {
+ $rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
+ $kanbanData = $this->loadModel('kanban')->getRDKanban($task->execution, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
+ $kanbanData = json_encode($kanbanData);
+
+ return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
+ }
+
+ if($from == 'taskkanban')
{
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
$kanbanData = $this->loadModel('kanban')->getRDKanban($task->execution, $this->session->execLaneType ? $this->session->execLaneType : 'all', 'id_desc', 0, $kanbanGroup, $rdSearchValue);
@@ -2371,10 +2350,8 @@ class task extends control
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)"));
}
- else
- {
- return print(js::closeModal('parent.parent', 'this'));
- }
+
+ return print(js::closeModal('parent.parent', 'this'));
}
return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent'));
}
diff --git a/module/task/css/recordestimate.css b/module/task/css/recordestimate.css
index e9786a1ccf..1bfcfe1a18 100644
--- a/module/task/css/recordestimate.css
+++ b/module/task/css/recordestimate.css
@@ -1 +1,7 @@
+.table-recorded thead{background: rgb(245 245 245);}
.form-condensed .table-form {margin-bottom: 20px;}
+.hours {display: flex; float: left; transform: translate(0, 50%); margin: 0 0 0 10px;}
+.estimateTotally, .consumedTotally {border-radius: 9px; padding: 3px 7px; margin: 0 5px 0 5px; min-width: 18px;}
+.consumedTotally {background: #EAF3FF; color: #2B80FF;}
+.estimateTotally {background: #FFEFEF; color: #FF6262;}
+.nav > li.divider {margin: 0 10px 0 5px; background-color: #000; opacity: 0.06; width: 1px ; height: 15px; padding: 0 !important;}
diff --git a/module/task/js/activate.js b/module/task/js/activate.js
new file mode 100644
index 0000000000..c4aacfa326
--- /dev/null
+++ b/module/task/js/activate.js
@@ -0,0 +1,109 @@
+$('#confirmButton').click(function()
+{
+ var memberCount = '';
+ var totalEstimate = 0;
+ var totalConsumed = oldConsumed;
+ var totalLeft = 0;
+ var error = false;
+ $('select[name^=team]').each(function()
+ {
+ if($(this).val() == '') return;
+
+ memberCount++;
+
+ var $tr = $(this).closest('tr');
+ var account = $(this).find('option:selected').text();
+
+ estimate = parseFloat($tr.find('[name^=teamEstimate]').val());
+ if(!isNaN(estimate)) totalEstimate += estimate;
+ if(isNaN(estimate) || estimate == 0)
+ {
+ bootbox.alert(account + ' ' + estimateNotEmpty);
+ error = true;
+ return false;
+ }
+
+ consumed = parseFloat($tr.find('[name^=teamConsumed]').val());
+ if(!isNaN(consumed)) totalConsumed += consumed;
+
+ left = parseFloat($tr.find('[name^=teamLeft]').val());
+ if(!isNaN(left)) totalLeft += left;
+ if(!$tr.hasClass('member-done') && (isNaN(left) || left == 0))
+ {
+ bootbox.alert(account + ' ' + leftNotEmpty);
+ error = true;
+ return false;
+ }
+
+ if(estimate == 0 || isNaN(estimate))
+ {
+ $(this).val('').trigger("chosen:updated");
+ bootbox.alert(estimateNotEmpty);
+ error = true;
+ return false;
+ }
+ })
+
+ if(error) return false;
+
+ if(memberCount < 2)
+ {
+ bootbox.alert(teamMemberError);
+ return false;
+ }
+
+ $('#left').val(totalLeft);
+ updateAssignedTo();
+
+ $('.close').click();
+});
+
+/**
+ * Update assignedTo.
+ *
+ * @access public
+ * @return void
+ */
+function updateAssignedTo()
+{
+ var html = '';
+ var multiple = $('#multiple').prop('checked');
+ var assignedTo = $('#assignedTo').val();
+ if(multiple)
+ {
+ var isTeamMember = false;
+ var mode = $('#mode').val();
+ $('select[name^=team]').each(function()
+ {
+ if($(this).find('option:selected').text() == '') return;
+ if($(this).val() == currentUser) isTeamMember = true;
+
+ var account = $(this).find('option:selected').val();
+ var realName = $(this).find('option:selected').text();
+ var selected = account == assignedTo ? 'selected' : '';
+
+ html += "" + realName + " ";
+ });
+
+ if(mode == 'multi' && isTeamMember && mode != 'linear')
+ {
+ $('[name=assignedTo]').removeAttr('disabled').trigger('chosen:updated');
+ }
+ else
+ {
+ $('[name=assignedTo]').attr('disabled', 'disabled').trigger('chosen:updated');
+ }
+ }
+ else
+ {
+ for(key in members)
+ {
+ var selected = key == assignedTo ? 'selected' : '';
+ html += "" + members[key] + " ";
+ }
+ }
+
+ $('#assignedTo').html(html);
+ if(multiple && mode == 'linear' && $('#modalTeam tr.member-doing').length == 0 && $('#modalTeam tr.member-wait').length >= 1) $('[name=assignedTo]').val($('#modalTeam tr.member-wait:first').find('select[name^=team]:first').val());
+ $('#assignedTo').trigger('chosen:updated');
+}
diff --git a/module/task/js/create.js b/module/task/js/create.js
index 6239804906..0a65fc34c3 100644
--- a/module/task/js/create.js
+++ b/module/task/js/create.js
@@ -536,44 +536,6 @@ $(document).ready(function()
$('#dataPlanGroup').fixInputGroup();
});
- /* Init task team manage dialog */
- var $taskTeamEditor = $('#taskTeamEditor').batchActionForm(
- {
- idStart: 0,
- idEnd: 5,
- chosen: true,
- datetimepicker: false,
- colorPicker: false,
- });
- var taskTeamEditor = $taskTeamEditor.data('zui.batchActionForm');
-
- var adjustButtons = function()
- {
- var $deleteBtn = $taskTeamEditor.find('.btn-delete');
- if ($deleteBtn.length == 1) $deleteBtn.addClass('disabled').attr('disabled', 'disabled');
- };
-
- $taskTeamEditor.on('click', '.btn-add', function()
- {
- var $newRow = taskTeamEditor.createRow(null, $(this).closest('tr'));
- $newRow.addClass('highlight');
- setTimeout(function()
- {
- $newRow.removeClass('highlight');
- }, 1600);
- adjustButtons();
- }).on('click', '.btn-delete', function()
- {
- var $row = $(this).closest('tr');
- $row.addClass('highlight').fadeOut(700, function()
- {
- $row.remove();
- adjustButtons();
- });
- });
-
- adjustButtons();
-
$('#showAllModule').change(function()
{
var executionID = $('#execution').val();
@@ -604,45 +566,28 @@ $(document).on('click', '#testStory_chosen', function()
$obj.trigger("chosen:updated");
})
-$('#modalTeam .btn').click(function()
+$('#modalTeam tfoot .btn').click(function()
{
var team = '';
var time = 0;
var error = false;
-
- /* Unique team. */
- $('select[name^=team]').each(function(i)
- {
- value = $(this).val();
- if(value == '') return;
- $('select[name^=team]').each(function(j)
- {
- if(i <= j) return;
- if(value == $(this).val()) $(this).closest('tr').addClass('hidden');
- })
- })
- $('select[name^=team]').closest('tr.hidden').remove();
+ var mode = $('#mode').val();
$('select[name^=team]').each(function()
{
+ if($(this).val() == '') return;
+
+ var tr = $(this).closest('tr');
var account = $(this).find('option:selected').text();
- if(account != '')
- {
- team += ' ' + account;
- }
+ team += ' ' + account;
estimate = parseFloat($(this).parents('td').next('td').find('[name^=teamEstimate]').val());
- if(!isNaN(estimate))
+ if(!isNaN(estimate) && estimate > 0) time += estimate;
+ if(account != '' && (isNaN(estimate) || estimate == 0))
{
- time += estimate;
- }
-
- var requiredFieldList = ',' + requiredFields + ',';
- if(account && requiredFieldList.indexOf(',estimate,') >= 0 && (estimate == 0 || isNaN(estimate)))
- {
- alert(estimateNotEmpty);
- error = true;
- return false;
+ bootbox.alert(account + ' ' + estimateNotEmpty);
+ error = true;
+ return false;
}
});
@@ -650,7 +595,7 @@ $('#modalTeam .btn').click(function()
var teamList = team.split(" ");
if(teamList.length <= 2)
{
- alert(teamMemberError);
+ bootbox.alert(teamMemberError);
return false;
}
else
diff --git a/module/task/js/edit.js b/module/task/js/edit.js
index d0571552fa..f59094b8fd 100644
--- a/module/task/js/edit.js
+++ b/module/task/js/edit.js
@@ -70,76 +70,6 @@ function setPreview(){}
$(document).ready(function()
{
- /* show team menu. */
- $('[name=multiple]').change(function()
- {
- var checked = $(this).prop('checked');
- if(checked)
- {
- $('#teamTr').removeClass('hidden');
- $('.modeBox').removeClass('hidden');
- $('#mode').removeAttr('disabled').trigger('chosen:updated');
- $('#parent').val('');
- $('#parent').trigger('chosen:updated');
- $('#parent').closest('tr').addClass('hidden');
- $('#estimate').attr('disabled', 'disabled');
- $('#left').attr('disabled', 'disabled');
-
- var mode = $('#mode').val();
- if((mode == 'linear' && currentUser != oldAssignedTo) || !team[currentUser]) $('[name=assignedTo]').attr('disabled', 'disabled').trigger('chosen:updated');
- }
- else
- {
- $('#teamTr').addClass('hidden');
- $('.modeBox').addClass('hidden');
- $('#mode').attr('disabled', 'disabled').trigger('chosen:updated');
- $('#parent').closest('tr').removeClass('hidden');
- $('#estimate').removeAttr('disabled');
- $('#left').removeAttr('disabled');
- $('[name=assignedTo]').removeAttr('disabled').trigger('chosen:updated');
- }
-
- updateAssignedTo();
- });
-
- /* Init task team manage dialog */
- var $taskTeamEditor = $('#taskTeamEditor').batchActionForm(
- {
- idStart: 0,
- idEnd: newRowCount - 1,
- chosen: true,
- datetimepicker: false,
- colorPicker: false,
- });
- var taskTeamEditor = $taskTeamEditor.data('zui.batchActionForm');
-
- var adjustButtons = function()
- {
- var $deleteBtn = $taskTeamEditor.find('.btn-delete');
- if ($deleteBtn.length == 1) $deleteBtn.addClass('disabled').attr('disabled', 'disabled');
- };
-
- $taskTeamEditor.on('click', '.btn-add', function()
- {
- var $newRow = taskTeamEditor.createRow(null, $(this).closest('tr'));
- $newRow.addClass('highlight');
- setTimeout(function()
- {
- $newRow.removeClass('highlight');
- }, 1600);
- adjustButtons();
- }).on('click', '.btn-delete', function()
- {
- var $row = $(this).closest('tr');
- $row.addClass('highlight').fadeOut(700, function()
- {
- $row.remove();
- adjustButtons();
- });
- });
-
- adjustButtons();
-
$('#showAllModule').change(function()
{
var moduleID = $('#moduleIdBox #module').val();
@@ -153,20 +83,6 @@ $(document).ready(function()
$('#confirmButton').click(function()
{
- /* Unique team. */
- $('select[name^=team]').each(function(i)
- {
- value = $(this).val();
- if(value == '') return;
- $('select[name^=team]').each(function(j)
- {
- if(i <= j) return;
- if(value == $(this).val()) $(this).closest('tr').addClass('hidden');
- })
- });
-
- $('select[name^=team]').closest('tr.hidden').remove();
-
var memberCount = '';
var totalEstimate = 0;
var totalConsumed = oldConsumed;
@@ -174,24 +90,38 @@ $('#confirmButton').click(function()
var error = false;
$('select[name^=team]').each(function()
{
- if($(this).find('option:selected').text() == '') return;
+ if($(this).val() == '') return;
memberCount++;
- estimate = parseFloat($(this).parents('td').next('td').find('[name^=teamEstimate]').val());
- if(!isNaN(estimate)) totalEstimate += estimate;
+ var $tr = $(this).closest('tr');
+ var account = $(this).find('option:selected').text();
- consumed = parseFloat($(this).parents('td').next('td').find('[name^=teamConsumed]').val());
+ estimate = parseFloat($tr.find('[name^=teamEstimate]').val());
+ if(!isNaN(estimate)) totalEstimate += estimate;
+ if(isNaN(estimate) || estimate == 0)
+ {
+ bootbox.alert(account + ' ' + estimateNotEmpty);
+ error = true;
+ return false;
+ }
+
+ consumed = parseFloat($tr.find('[name^=teamConsumed]').val());
if(!isNaN(consumed)) totalConsumed += consumed;
- left = parseFloat($(this).parents('td').next('td').find('[name^=teamLeft]').val());
+ left = parseFloat($tr.find('[name^=teamLeft]').val());
if(!isNaN(left)) totalLeft += left;
+ if(!$tr.hasClass('member-done') && (isNaN(left) || left == 0))
+ {
+ bootbox.alert(account + ' ' + leftNotEmpty);
+ error = true;
+ return false;
+ }
- var requiredFieldList = ',' + requiredFields + ',';
- if(requiredFieldList.indexOf(',estimate,') >= 0 && (estimate == 0 || isNaN(estimate)))
+ if(estimate == 0 || isNaN(estimate))
{
$(this).val('').trigger("chosen:updated");
- alert(estimateNotEmpty);
+ bootbox.alert(estimateNotEmpty);
error = true;
return false;
}
@@ -201,13 +131,13 @@ $('#confirmButton').click(function()
if(memberCount < 2)
{
- alert(teamMemberError);
+ bootbox.alert(teamMemberError);
return false;
}
if(totalLeft == 0 && (taskStatus == 'doing' || taskStatus == 'pause'))
{
- alert(totalLeftError);
+ bootbox.alert(totalLeftError);
return false;
}
@@ -246,13 +176,13 @@ function updateAssignedTo()
html += "" + realName + " ";
});
- if(mode == 'multi' && isTeamMember)
+ if(mode == 'multi' && isTeamMember && mode != 'linear')
{
$('[name=assignedTo]').removeAttr('disabled').trigger('chosen:updated');
}
else
{
- if(currentUser != oldAssignedTo || !isTeamMember) $('[name=assignedTo]').attr('disabled', 'disabled').trigger('chosen:updated');
+ $('[name=assignedTo]').attr('disabled', 'disabled').trigger('chosen:updated');
}
}
else
@@ -265,5 +195,6 @@ function updateAssignedTo()
}
$('#assignedTo').html(html);
+ if(multiple && mode == 'linear' && $('#modalTeam tr.member-doing').length == 0 && $('#modalTeam tr.member-wait').length >= 1) $('[name=assignedTo]').val($('#modalTeam tr.member-wait:first').find('select[name^=team]:first').val());
$('#assignedTo').trigger('chosen:updated');
}
diff --git a/module/task/js/editestimate.js b/module/task/js/editestimate.js
index ddaf03ed54..d9cdb02abe 100644
--- a/module/task/js/editestimate.js
+++ b/module/task/js/editestimate.js
@@ -6,7 +6,8 @@
*/
function confirmLeft()
{
- if($('#left').val() === '0') return confirm(confirmRecord);
+ $left = $('#left');
+ if(!$left.prop('readonly') && $left.val() === '0') return confirm(confirmRecord);
}
$(function()
diff --git a/module/task/js/editteam.js b/module/task/js/editteam.js
index b41bb1b4c2..bc08f7bfbd 100644
--- a/module/task/js/editteam.js
+++ b/module/task/js/editteam.js
@@ -1,124 +1,18 @@
$(document).ready(function()
{
- /* show team menu. */
- $('[name=multiple]').change(function()
+ $('#submit').click(function()
{
- var checked = $(this).prop('checked');
- if(checked)
+ var memberCount = 0;
+ $('select[name^=team]').each(function()
{
- $('#teamTr').removeClass('hidden');
- $('.modeBox').removeClass('hidden');
- $('#mode').removeAttr('disabled').trigger('chosen:updated');
- $('#parent').val('');
- $('#parent').trigger('chosen:updated');
- $('#parent').closest('tr').addClass('hidden');
- $('#estimate').attr('disabled', 'disabled');
- $('#left').attr('disabled', 'disabled');
-
- var mode = $('#mode').val();
- if((mode == 'linear' && currentUser != oldAssignedTo) || !team[currentUser]) $('[name=assignedTo]').attr('disabled', 'disabled').trigger('chosen:updated');
- }
- else
- {
- $('#teamTr').addClass('hidden');
- $('.modeBox').addClass('hidden');
- $('#mode').attr('disabled', 'disabled').trigger('chosen:updated');
- $('#parent').closest('tr').removeClass('hidden');
- $('#estimate').removeAttr('disabled');
- $('#left').removeAttr('disabled');
- $('[name=assignedTo]').removeAttr('disabled').trigger('chosen:updated');
- }
-
- updateAssignedTo();
- });
-
- /* Init task team manage dialog */
- var $taskTeamEditor = $('#taskTeamEditor').batchActionForm(
- {
- idStart: 0,
- idEnd: newRowCount - 1,
- chosen: true,
- datetimepicker: false,
- colorPicker: false,
- });
- var taskTeamEditor = $taskTeamEditor.data('zui.batchActionForm');
-
- var adjustButtons = function()
- {
- var $deleteBtn = $taskTeamEditor.find('.btn-delete');
- if ($deleteBtn.length == 1) $deleteBtn.addClass('disabled').attr('disabled', 'disabled');
- };
-
- $taskTeamEditor.on('click', '.btn-add', function()
- {
- var $newRow = taskTeamEditor.createRow(null, $(this).closest('tr'));
- $newRow.addClass('highlight');
- setTimeout(function()
- {
- $newRow.removeClass('highlight');
- }, 1600);
- adjustButtons();
- }).on('click', '.btn-delete', function()
- {
- var $row = $(this).closest('tr');
- $row.addClass('highlight').fadeOut(700, function()
- {
- $row.remove();
- adjustButtons();
- });
- });
-
- adjustButtons();
-});
-
-$('#confirmButton').click(function()
-{
- /* Unique team. */
- $('select[name^=team]').each(function(i)
- {
- value = $(this).val();
- if(value == '') return;
- $('select[name^=team]').each(function(j)
- {
- if(i <= j) return;
- if(value == $(this).val()) $(this).closest('tr').addClass('hidden');
+ if($(this).find('option:selected').text() == '') return;
+ memberCount++;
})
+
+ if(memberCount < 2)
+ {
+ alert(teamMemberError);
+ return false;
+ }
});
-
- $('select[name^=team]').closest('tr.hidden').remove();
-
- var memberCount = '';
- var totalEstimate = 0;
- var totalConsumed = oldConsumed;
- var totalLeft = 0;
- $('select[name^=team]').each(function()
- {
- if($(this).find('option:selected').text() == '') return;
-
- memberCount++;
-
- estimate = parseFloat($(this).parents('td').next('td').find('[name^=teamEstimate]').val());
- if(!isNaN(estimate)) totalEstimate += estimate;
-
- consumed = parseFloat($(this).parents('td').next('td').find('[name^=teamConsumed]').val());
- if(!isNaN(consumed)) totalConsumed += consumed;
-
- left = parseFloat($(this).parents('td').next('td').find('[name^=teamLeft]').val());
- if(!isNaN(left)) totalLeft += left;
- })
- $('#estimate').val(totalEstimate);
- $('#consumedSpan').html(totalConsumed);
- $('#left').val(totalLeft);
-
- if(memberCount < 2)
- {
- alert(teamMemberError);
- return false;
- }
- if(totalLeft == 0 && (taskStatus == 'doing' || taskStatus == 'pause'))
- {
- alert(totalLeftError);
- return false;
- }
- $('.close').click();
});
diff --git a/module/task/lang/de.php b/module/task/lang/de.php
index bf5d6c16dc..1cbd6b7491 100644
--- a/module/task/lang/de.php
+++ b/module/task/lang/de.php
@@ -25,6 +25,7 @@ $lang->task->delayed = 'Verzögert';
$lang->task->view = "Übersicht";
$lang->task->logEfforts = "Stunden";
$lang->task->record = "Schätzung";
+$lang->task->recordedBy = "Recorded By";
$lang->task->teamConsumed = "Tean Consumed";
$lang->task->start = "Start";
$lang->task->startAction = "Start Task";
@@ -54,6 +55,7 @@ $lang->task->progressTips = 'Genutzt/(Genutzt+Rest)';
$lang->task->copy = 'Aufgabe kopieren';
$lang->task->waitTask = 'Waiting Task';
$lang->task->allModule = 'All Module';
+$lang->task->replace = 'Replace';
$lang->task->common = 'Aufgabe';
$lang->task->id = 'ID';
@@ -216,7 +218,6 @@ $lang->task->legendMisc = 'Misc.';
$lang->task->confirmDelete = "Möchten Sie diese Aufgabe löschen?";
$lang->task->confirmDeleteEstimate = "Do you want to delete it?";
-$lang->task->confirmDeleteLastEstimate = "Do you want to delete the log? After deleting the last work log, the task status will be adjusted to Not Started.";
$lang->task->copyStoryTitle = "Story kopieren";
$lang->task->afterSubmit = "Nach der Erstellung,";
$lang->task->successSaved = "Erstellt!";
@@ -232,6 +233,8 @@ $lang->task->noticeLinkStory = "Es wurde keine Story verknüpft. Sie k
$lang->task->noticeSaveRecord = 'Ihre Stunden wurden nicht gespeichrt. Bitte erst speichern.';
$lang->task->commentActions = '%s. %s, kommentiert von %s .';
$lang->task->deniedNotice = 'Nur %s kann die Aufgabe %s.';
+$lang->task->deniedStatusNotice = 'The task status is %s, the effort cannot be maintained.';
+$lang->task->transferNotice = 'Linear task cannot be transferred.';
$lang->task->noTask = 'Keine Aufagben. ';
$lang->task->createDenied = 'Aufgben erstellen it in diesem Projekt gesperrt';
$lang->task->cannotDeleteParent = 'Cannot delete parent task';
diff --git a/module/task/lang/en.php b/module/task/lang/en.php
index 2058e72cad..aedaa34ef6 100755
--- a/module/task/lang/en.php
+++ b/module/task/lang/en.php
@@ -25,6 +25,7 @@ $lang->task->delayed = 'Delayed';
$lang->task->view = "Task Detail";
$lang->task->logEfforts = "Effort";
$lang->task->record = "Estimates";
+$lang->task->recordedBy = "Recorded By";
$lang->task->teamConsumed = "Tean Consumed";
$lang->task->start = "Start";
$lang->task->startAction = "Start Task";
@@ -54,6 +55,7 @@ $lang->task->progressTips = 'Cost/(Cost+Left)';
$lang->task->copy = 'Copy Task';
$lang->task->waitTask = 'Waiting Task';
$lang->task->allModule = 'All Module';
+$lang->task->replace = 'Replace';
$lang->task->common = 'Task';
$lang->task->id = 'ID';
@@ -216,7 +218,6 @@ $lang->task->legendMisc = 'Misc.';
$lang->task->confirmDelete = "Do you want to delete this task?";
$lang->task->confirmDeleteEstimate = "Do you want to delete it?";
-$lang->task->confirmDeleteLastEstimate = "Do you want to delete the log? After deleting the last work log, the task status will be adjusted to Not Started.";
$lang->task->copyStoryTitle = "Copy Story";
$lang->task->afterSubmit = "Next ";
$lang->task->successSaved = "Created!";
@@ -232,6 +233,8 @@ $lang->task->noticeLinkStory = "No story has been linked. You can %s f
$lang->task->noticeSaveRecord = 'Your Hour is not saved. Please save it first.';
$lang->task->commentActions = '%s. %s, commented by %s .';
$lang->task->deniedNotice = 'Only the %s can %s the task.';
+$lang->task->deniedStatusNotice = 'The task status is %s, the effort cannot be maintained.';
+$lang->task->transferNotice = 'Linear task cannot be transferred.';
$lang->task->noTask = 'No tasks yet. ';
$lang->task->createDenied = 'Create Task is denied in this project';
$lang->task->cannotDeleteParent = 'Cannot delete parent task';
diff --git a/module/task/lang/fr.php b/module/task/lang/fr.php
index d676f327a7..2eab1df9e2 100644
--- a/module/task/lang/fr.php
+++ b/module/task/lang/fr.php
@@ -25,6 +25,7 @@ $lang->task->delayed = 'Ajournées';
$lang->task->view = "Détail Tâche";
$lang->task->logEfforts = "Effort";
$lang->task->record = "Estimés";
+$lang->task->recordedBy = "Recorded By";
$lang->task->teamConsumed = "Tean Consumed";
$lang->task->start = "Démarrer";
$lang->task->startAction = "Démarrer Tâche";
@@ -54,6 +55,7 @@ $lang->task->progressTips = 'Coût/(Coût+Reste)';
$lang->task->copy = 'Copier Tâche';
$lang->task->waitTask = 'Tâche en attente';
$lang->task->allModule = 'Tous Modules';
+$lang->task->replace = 'Replace';
$lang->task->common = 'Tâche';
$lang->task->id = 'ID';
@@ -216,7 +218,6 @@ $lang->task->legendMisc = 'Misc.';
$lang->task->confirmDelete = "Voulez-vous réellement supprimer cette tâche ?";
$lang->task->confirmDeleteEstimate = "Voulez-vous la supprimer ?";
-$lang->task->confirmDeleteLastEstimate = "Do you want to delete the log? After deleting the last work log, the task status will be adjusted to Not Started.";
$lang->task->copyStoryTitle = 'StoryModel ';
$lang->task->afterSubmit = "Suiv. ";
$lang->task->successSaved = "Créée !";
@@ -232,6 +233,8 @@ $lang->task->noticeLinkStory = "Aucune Story n'est associée. Vous pou
$lang->task->noticeSaveRecord = "Votre temps n'a pas été sauvé. Enregistrez-le d'abord.";
$lang->task->commentActions = '%s. %s, commenté par %s .';
$lang->task->deniedNotice = 'Seulement le %s peut %s la tâche.';
+$lang->task->deniedStatusNotice = 'The task status is %s, the effort cannot be maintained.';
+$lang->task->transferNotice = 'Linear task cannot be transferred.';
$lang->task->noTask = "Pas de tâche pour l'instant. ";
$lang->task->createDenied = 'La création de tâches est interdite dans ce projet';
$lang->task->cannotDeleteParent = 'Impossible de supprimer la tâche parente';
diff --git a/module/task/lang/vi.php b/module/task/lang/vi.php
index 42cccd53b3..10e3cdbab6 100644
--- a/module/task/lang/vi.php
+++ b/module/task/lang/vi.php
@@ -25,6 +25,7 @@ $lang->task->delayed = 'Tạm ngưng';
$lang->task->view = "Chi tiết nhiệm vụ";
$lang->task->logEfforts = "Chấm công";
$lang->task->record = "Dự tính";
+$lang->task->recordedBy = "Recorded By";
$lang->task->start = "Bắt đầu";
$lang->task->startAction = "Bắt đầu nhiệm vụ";
$lang->task->restart = "Tiếp tục";
@@ -52,6 +53,7 @@ $lang->task->progressTips = 'Đã làm/(Đã làm + Còn lại)';
$lang->task->copy = 'Copy nhiệm vụ';
$lang->task->waitTask = 'Đang đợi nhiệm vụ';
$lang->task->allModule = 'Tất cả Module';
+$lang->task->replace = 'Replace';
$lang->task->common = 'Nhiệm vụ';
$lang->task->id = 'ID';
diff --git a/module/task/lang/zh-cn.php b/module/task/lang/zh-cn.php
index 6e0cd4e8bd..943a5b1862 100755
--- a/module/task/lang/zh-cn.php
+++ b/module/task/lang/zh-cn.php
@@ -25,6 +25,7 @@ $lang->task->delayed = '延期';
$lang->task->view = "查看任务";
$lang->task->logEfforts = "记录工时";
$lang->task->record = "工时";
+$lang->task->recordedBy = "记录人";
$lang->task->teamConsumed = "团队工时";
$lang->task->start = "开始";
$lang->task->startAction = "开始任务";
@@ -54,6 +55,7 @@ $lang->task->progressTips = '已消耗/(已消耗+剩余)';
$lang->task->copy = '复制任务';
$lang->task->waitTask = '未开始的任务';
$lang->task->allModule = '所有模块';
+$lang->task->replace = '替换';
$lang->task->common = '任务';
$lang->task->id = '编号';
@@ -216,7 +218,6 @@ $lang->task->legendMisc = '其他相关';
$lang->task->confirmDelete = "您确定要删除这个任务吗?";
$lang->task->confirmDeleteEstimate = "您确定要删除这个记录吗?";
-$lang->task->confirmDeleteLastEstimate = "您确定要删除该条日志吗?删除最后一条工时日志后,该任务状态将调整为未开始。";
$lang->task->copyStoryTitle = "同{$lang->SRCommon}";
$lang->task->afterSubmit = "添加之后";
$lang->task->successSaved = "成功添加,";
@@ -232,6 +233,8 @@ $lang->task->noticeLinkStory = "没有可关联的相关{$lang->SRComm
$lang->task->noticeSaveRecord = '您有尚未保存的工时记录,请先将其保存。';
$lang->task->commentActions = '%s. %s, 由 %s 添加备注。';
$lang->task->deniedNotice = '当前任务只有%s才可以%s。';
+$lang->task->deniedStatusNotice = '当前任务状态是%s,不能维护日志。';
+$lang->task->transferNotice = '串行多人任务不能转交。';
$lang->task->noTask = '暂时没有任务。';
$lang->task->createDenied = '你不能在该项目添加任务';
$lang->task->cannotDeleteParent = '不能删除父任务。';
@@ -354,4 +357,4 @@ $lang->taskestimate = new stdclass();
$lang->taskestimate->consumed = '工时';
$lang->task->overEsStartDate = '已超出%s计划开始时间,请先修改%s计划开始时间';
-$lang->task->overEsEndDate = '已超出%s计划结束时间,请先修改%s计划结束时间';
\ No newline at end of file
+$lang->task->overEsEndDate = '已超出%s计划结束时间,请先修改%s计划结束时间';
diff --git a/module/task/model.php b/module/task/model.php
old mode 100644
new mode 100755
index 54cb9ada59..0605a0af01
--- a/module/task/model.php
+++ b/module/task/model.php
@@ -89,7 +89,7 @@ class taskModel extends model
->cleanINT('execution,story,module')
->stripTags($this->config->task->editor->create['id'], $this->config->allowedTags)
->join('mailto', ',')
- ->remove('after,files,labels,assignedTo,uid,storyEstimate,storyDesc,storyPri,team,teamEstimate,teamMember,multiple,teams,contactListMenu,selectTestStory,testStory,testPri,testEstStarted,testDeadline,testAssignedTo,testEstimate,sync,otherLane,region,lane,estStartedDitto,deadlineDitto')
+ ->remove('after,files,labels,assignedTo,uid,storyEstimate,storyDesc,storyPri,team,teamSource,teamEstimate,teamMember,multiple,teams,contactListMenu,selectTestStory,testStory,testPri,testEstStarted,testDeadline,testAssignedTo,testEstimate,sync,otherLane,region,lane,estStartedDitto,deadlineDitto')
->add('version', 1)
->get();
@@ -232,37 +232,16 @@ class taskModel extends model
foreach($taskFiles as $fileID => $taskFile) unset($taskFiles[$fileID]->id);
}
- $teams = array();
if($this->post->multiple and count(array_filter($this->post->team)) > 1)
{
- foreach($this->post->team as $row => $account)
+ $teams = $this->manageTaskTeam($task->mode, $taskID, 'wait');
+ if($teams)
{
- if(empty($account) or isset($team[$account])) continue;
- $member = new stdClass();
- $member->root = 0;
- $member->account = $account;
- $member->role = $assignedTo;
- $member->join = helper::today();
- $member->estimate = $this->post->teamEstimate[$row] ? (float)$this->post->teamEstimate[$row] : 0;
- $member->left = $member->estimate;
- $member->order = $row;
- $teams[$account] = $member;
+ $task->id = $taskID;
+ $this->computeHours4Multiple($task);
}
}
- if(!empty($teams))
- {
- foreach($teams as $team)
- {
- $team->root = $taskID;
- $team->type = 'task';
- $this->dao->insert(TABLE_TEAM)->data($team)->autoCheck()->exec();
- }
-
- $task->id = $taskID;
- $this->computeHours4Multiple($task);
- }
-
if(!dao::isError()) $this->loadModel('score')->create('task', 'create', $taskID);
$taskIdList[$assignedTo] = array('status' => 'created', 'id' => $taskID);
}
@@ -800,6 +779,7 @@ class taskModel extends model
*
* @param object $oldTask
* @param object $task
+ * @param array $team
* @param bool $autoStatus
* @access public
* @return object|bool
@@ -808,13 +788,15 @@ class taskModel extends model
{
if(!$oldTask) return false;
- if(empty($team)) $team = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->eq($oldTask->id)->andWhere('type')->eq('task')->orderBy('order')->fetchAll('account');
+ if(empty($team)) $team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($oldTask->id)->orderBy('order')->fetchAll();
if(!empty($team))
{
$now = helper::now();
- $teams = array_keys($team);
+ $oldTeam = zget($oldTask, 'team', array());
+ $members = array_map(function($member){ return $member->account; }, $team);
$currentTask = !empty($task) ? $task : new stdclass();
if(!isset($currentTask->status)) $currentTask->status = $oldTask->status;
+ $oldTask->team = $team;
$currentTask->assignedTo = $oldTask->assignedTo;
if(!empty($_POST['assignedTo']) and is_string($_POST['assignedTo']))
@@ -823,40 +805,23 @@ class taskModel extends model
}
else
{
- if(!$oldTask->assignedTo)
- {
- $firstMember = reset($team);
- $currentTask->assignedTo = $firstMember->account;
- $currentTask->assignedDate = $now;
- }
- else
- {
- if($team[$oldTask->assignedTo]->left == 0 and $team[$oldTask->assignedTo]->consumed != 0 and $this->app->rawMethod != 'deleteestimate')
- {
- if($oldTask->assignedTo != $teams[count($teams) - 1])
- {
- $currentTask->assignedTo = $this->getNextUser(array_keys($team), $oldTask->assignedTo);
- }
- else
- {
- $currentTask->assignedTo = $oldTask->openedBy;
- }
- $currentTask->assignedDate = $now;
- }
- }
+ $currentTask->assignedTo = $this->getAssignedTo4Multi($members, $oldTask);
+ if($oldTask->assignedTo != $currentTask->assignedTo) $currentTask->assignedDate = $now;
}
$currentTask->estimate = 0;
- $currentTask->consumed = 0;
$currentTask->left = 0;
foreach($team as $member)
{
$currentTask->estimate += (float)$member->estimate;
- $currentTask->consumed += (float)$member->consumed;
$currentTask->left += (float)$member->left;
}
- if($this->app->rawMethod == 'edit' and empty($oldTask->team) and isset($oldTask->consumed)) $currentTask->consumed += (float)$oldTask->consumed;
+ $efforts = $this->getTaskEstimate($oldTask->id);
+ $currentTask->consumed = 0;
+ foreach($efforts as $effort) $currentTask->consumed += (float)$effort->consumed;
+
+ $oldTask->team = $oldTeam;
if(!empty($task))
{
@@ -878,38 +843,196 @@ class taskModel extends model
if($currentTask->consumed > 0 and $currentTask->left == 0)
{
- $finisedUsers = $this->getFinishedUsers($oldTask->id, $teams);
- if(($oldTask->mode == 'linear' and isset($team[$currentTask->assignedTo]) and $oldTask->assignedTo != $teams[count($teams) - 1]) or ($oldTask->mode == 'multi' and count($finisedUsers) != count($teams)))
+ $finisedUsers = $this->getFinishedUsers($oldTask->id, $members);
+ if(count($finisedUsers) != count($team))
{
- $currentTask->status = 'doing';
- $currentTask->finishedBy = '';
- $currentTask->finishedDate = '';
+ if(strpos('cancel,pause', $oldTask->status) === false or ($oldTask->status == 'closed' and $oldTask->reason == 'done'))
+ {
+ $currentTask->status = 'doing';
+ $currentTask->finishedBy = '';
+ $currentTask->finishedDate = '';
+ }
}
- elseif(($oldTask->mode == 'linear' and $oldTask->assignedTo == $teams[count($teams) - 1]) or $oldTask->mode == 'multi')
+ elseif(strpos('wait,doing,pause', $oldTask->status) !== false)
{
$currentTask->status = 'done';
+ $currentTask->assignedTo = $oldTask->openedBy;
+ $currentTask->assignedDate = $now;
$currentTask->finishedBy = $this->app->user->account;
$currentTask->finishedDate = $task->finishedDate;
}
}
- if(($oldTask->assignedTo != $currentTask->assignedTo or $currentTask->status == 'done')
- and isset($team[$this->app->user->account]) and $team[$this->app->user->account]->left == 0
- and strpos($oldTask->finishedList, ",{$this->app->user->account},") === false)
- {
- $currentTask->finishedList = ',' . trim(trim($oldTask->finishedList, ',') . ",{$this->app->user->account}", ',') . ',';
- }
- if(($oldTask->status == 'done' or $oldTask->status == 'closed') and $currentTask->status == 'doing' and $this->post->assignedTo)
- {
- $currentTask->finishedList = ',' . trim(substr($oldTask->finishedList, 0, strpos($oldTask->finishedList, ",{$this->post->assignedTo},")), ',') . ',';
- }
-
return $currentTask;
}
$this->dao->update(TABLE_TASK)->data($currentTask)->autoCheck()->where('id')->eq($oldTask->id)->exec();
}
}
+ /**
+ * Manage multi task team members.
+ *
+ * @param string $mode
+ * @param int $taskID
+ * @param string $taskStatus
+ * @access public
+ * @return array
+ */
+ public function manageTaskTeam($mode, $taskID, $taskStatus)
+ {
+ $oldTeams = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->fetchAll();
+ $oldMembers = array_map(function($team){return $team->account;}, $oldTeams);
+
+ $this->dao->delete()->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->exec();
+
+ if($taskStatus == 'doing')
+ {
+ $efforts = $this->getTaskEstimate($taskID);
+ $doingUsers = array();
+ foreach($efforts as $i => $effort)
+ {
+ if($effort->left != 0) $doingUsers[$effort->account] = $effort->account;
+ if($effort->left == 0) unset($doingUsers[$effort->account]);
+ }
+ }
+
+ $teams = array();
+ $minStatus = 'done';
+ $changeUsers = array();
+ foreach($this->post->team as $row => $account)
+ {
+ if(empty($account)) continue;
+
+ $teamSource = $this->post->teamSource[$row];
+ $member = new stdClass();
+ $member->task = $taskID;
+ $member->order = $row;
+ $member->account = $account;
+ $member->estimate = zget($this->post->teamEstimate, $row, 0);
+ $member->consumed = zget($this->post->teamConsumed, $row, 0);
+ $member->left = zget($this->post->teamLeft, $row, 0);
+ $member->status = 'wait';
+ if($taskStatus == 'wait' and $member->estimate > 0 and $member->left == 0) $member->left = $member->estimate;
+ if($taskStatus == 'done') $member->left = 0;
+
+ if($member->left == 0 and $member->consumed > 0)
+ {
+ $member->status = 'done';
+ }
+ elseif($taskStatus == 'doing')
+ {
+ if(!empty($teamSource) and $teamSource != $account and isset($doingUsers[$teamSource])) $member->transfer = $teamSource;
+ if(isset($doingUsers[$account]) and ($mode == 'multi' or ($mode == 'linear' and $minStatus != 'wait'))) $member->status = 'doing';
+ }
+ if($minStatus != 'wait' and $member->status == 'doing') $minStatus = 'doing';
+ if($member->status == 'wait') $minStatus = 'wait';
+
+ /* Doing status is only one in linear task. */
+ if($mode == 'linear' and $member->status == 'doing') $minStatus = 'wait';
+ if($member->status == 'wait') $minStatus = 'wait';
+ if($minStatus != 'wait' and $member->status == 'doing') $minStatus = 'doing';
+
+ /* Insert or update team. */
+ if($mode == 'multi' and isset($teams[$account]))
+ {
+ $this->dao->update(TABLE_TASKTEAM)->set("estimate= estimate + {$member->estimate}")
+ ->set("`left` = `left` + {$member->left}")
+ ->set("`consumed` = `consumed` + {$member->consumed}")
+ ->where('task')->eq($member->task)
+ ->andWhere('account')->eq($member->account)
+ ->exec();
+ }
+ else
+ {
+ $this->dao->insert(TABLE_TASKTEAM)->data($member)->autoCheck()->exec();
+ }
+
+ /* Set effort left = 0 when linear task members be changed. */
+ if($mode == 'linear' and isset($oldTeams[$row]) and $oldTeams[$row]->account != $account) $changeUsers[] = $oldTeams[$row]->account;
+
+ $teams[$account] = $account;
+ }
+
+ /* Set effort left = 0 when multi task members be removed. */
+ if($mode == 'multi' and $oldMembers)
+ {
+ $removedMembers = array_diff($oldMembers, $teams);
+ $changeUsers = array_merge($changeUsers, $removedMembers);
+ }
+ if($changeUsers) $this->resetEffortLeft($taskID, $changeUsers);
+
+ return $teams;
+ }
+
+ /**
+ * Get team by account from task teams.
+ *
+ * @param array $teams
+ * @param string $account
+ * @param array $extra
+ * @access public
+ * @return object
+ */
+ public function getTeamByAccount($teams, $account = '', $extra = array('filter' => 'done'))
+ {
+ if(empty($account)) $account = $this->app->user->account;
+
+ $filter = zget($extra, 'filter', '');
+ $effortID = zget($extra, 'effortID', '');
+
+ $duplicates = array();
+ $members = array();
+ $taskID = 0;
+ foreach($teams as $team)
+ {
+ if(empty($taskID)) $taskID = $team->task;
+ if(isset($members[$team->account])) $duplicates[$team->account] = $team->account;
+ if(!isset($members[$team->account])) $members[$team->account] = 0;
+ $members[$team->account] += 1;
+ }
+
+ /*
+ * 1. No duplicate account;
+ * 2. Account is not duplicate account;
+ * 3. Not by effort;
+ * Then direct get team by account.
+ */
+ if(empty($duplicates) or (!isset($duplicates[$account])))
+ {
+ foreach($teams as $team)
+ {
+ if($team->account == $account) return $team;
+ }
+ }
+ elseif(empty($effortID))
+ {
+ foreach($teams as $team)
+ {
+ if($filter and $team->status == $filter) continue;
+ if($team->account == $account) return $team;
+ }
+ }
+ elseif($effortID)
+ {
+ $efforts = $this->getTaskEstimate($taskID, '', $effortID);
+
+ $prevTeam = null;
+ $thisTeam = null;
+ foreach($efforts as $effort)
+ {
+ $thisTeam = reset($teams);
+ if($effort->id == $effortID)
+ {
+ if($effort->account == $thisTeam->account) return $thisTeam;
+ if($effort->account == $prevTeam->account) return $prevTeam;
+ return false;
+ }
+
+ if($effort->left == 0 and $thisTeam->account == $effort->account) $prevTeam = array_shift($teams);
+ }
+ }
+ }
+
/**
* Update a task.
*
@@ -935,10 +1058,7 @@ class taskModel extends model
}
/* If a multiple task is assigned to a team member who is not the task, assign to the team member instead. */
- if(!$this->post->assignedTo and !empty($_POST['team']) and !in_array($oldTask->assignedTo, $this->post->team))
- {
- $_POST['assignedTo'] = reset($_POST['team']);
- }
+ if(!$this->post->assignedTo and !empty($_POST['team'])) $_POST['assignedTo'] = $this->getAssignedTo4Multi($_POST['team'], $oldTask);
/* When the selected parent task is a common task and has consumption, select other parent tasks. */
if($this->post->parent > 0)
@@ -993,7 +1113,7 @@ class taskModel extends model
->stripTags($this->config->task->editor->edit['id'], $this->config->allowedTags)
->cleanINT('execution,story,module')
->join('mailto', ',')
- ->remove('comment,files,labels,uid,multiple,team,teamEstimate,teamConsumed,teamLeft,contactListMenu')
+ ->remove('comment,files,labels,uid,multiple,team,teamEstimate,teamConsumed,teamLeft,teamSource,contactListMenu')
->get();
if($task->consumed < $oldTask->consumed) return print(js::error($this->lang->task->error->consumedSmall));
@@ -1008,55 +1128,12 @@ class taskModel extends model
$task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->edit['id'], $this->post->uid);
- $teams = array();
- if($this->post->multiple and count(array_unique(array_filter($this->post->team))) > 1)
+ if(count(array_filter($this->post->team)) > 1)
{
- if(strpos(',done,closed,cancel,', ",{$task->status},") === false && $this->post->assignedTo && !in_array($this->post->assignedTo, $this->post->team))
- {
- dao::$errors[] = $this->lang->task->error->assignedTo;
- return false;
- }
-
- foreach($this->post->team as $row => $account)
- {
- if(empty($account) or isset($team[$account])) continue;
-
- $member = new stdClass();
- $member->account = $account;
- $member->role = $task->assignedTo;
- $member->join = helper::today();
- $member->root = $taskID;
- $member->type = 'task';
- $member->estimate = $this->post->teamEstimate[$row] ? $this->post->teamEstimate[$row] : 0;
- $member->consumed = $this->post->teamConsumed[$row] ? $this->post->teamConsumed[$row] : 0;
- $member->left = $this->post->teamLeft[$row] === '' ? 0 : $this->post->teamLeft[$row];
- $member->order = $row;
- $teams[$account] = $member;
- if($task->status == 'done') $member->left = 0;
- }
- }
-
- /* Save team. */
- $this->dao->delete()->from(TABLE_TEAM)->where('root')->eq($taskID)->andWhere('type')->eq('task')->exec();
- if(!empty($teams))
- {
- foreach($teams as $member) $this->dao->insert(TABLE_TEAM)->data($member)->autoCheck()->exec();
-
- /* Assign the left hours to zero who will be skipped. */
- $skipMembers = $this->loadModel('execution')->getTeamSkip($oldTask->team, $oldTask->assignedTo, isset($task->assignedTo) ? $task->assignedTo : $oldTask->assignedTo);
- foreach($skipMembers as $account => $team) $this->dao->update(TABLE_TEAM)->set('left')->eq(0)->where('root')->eq($taskID)->andWhere('type')->eq('task')->andWhere('account')->eq($account)->exec();
-
- $task = $this->computeHours4Multiple($oldTask, $task, array(), $autoStatus = false);
- if($task->status == 'wait')
- {
- reset($teams);
- $task->assignedTo = key($teams);
- }
- }
- else
- {
- $task->mode = '';
+ $teams = $this->manageTaskTeam($oldTask->mode, $taskID, $task->status);
+ if(!empty($teams)) $task = $this->computeHours4Multiple($oldTask, $task, array(), $autoStatus = false);
}
+ if(empty($teams)) $task->mode = '';
$execution = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($task->execution)->fetch();
$requiredFields = "," . $this->config->task->edit->requiredFields . ",";
@@ -1521,20 +1598,6 @@ class taskModel extends model
return false;
}
- if(!empty($oldTask->team))
- {
- $skipMembers = $this->loadModel('execution')->getTeamSkip($oldTask->team, $oldTask->assignedTo, $task->assignedTo);
- foreach($skipMembers as $account => $team) $this->dao->update(TABLE_TEAM)->set('left')->eq(0)->where('root')->eq($taskID)->andWhere('type')->eq('task')->andWhere('account')->eq($account)->exec();
-
- $this->dao->update(TABLE_TEAM)->set('left')->eq($task->left)
- ->where('root')->eq($taskID)
- ->andWhere('type')->eq('task')
- ->andWhere('account')->eq($task->assignedTo)
- ->exec();
-
- $task = $this->computeHours4Multiple($oldTask, $task);
- }
-
if($oldTask->parent > 0) $this->updateParentStatus($taskID);
$task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->assignto['id'], $this->post->uid);
@@ -1559,10 +1622,15 @@ class taskModel extends model
public function updateTeam($taskID)
{
$oldTask = $this->getById($taskID);
- if($this->post->estimate < 0 or $this->post->left < 0 or $this->post->consumed < 0)
+ foreach($this->post->team as $i => $account)
{
- dao::$errors[] = $this->lang->task->error->recordMinus;
- return false;
+ if(!$account) continue;
+
+ if($this->post->teamConsumed[$i] == 0 and $this->post->teamLeft[$i] == 0)
+ {
+ dao::$errors[] = $this->lang->task->noticeTaskStart;
+ return false;
+ }
}
$now = helper::now();
@@ -1576,53 +1644,15 @@ class taskModel extends model
->setDefault('lastEditedDate', $now)
->setDefault('assignedDate', $now)
->stripTags($this->config->task->editor->assignto['id'], $this->config->allowedTags)
- ->remove('comment,showModule,team,teamEstimate,teamConsumed,teamLeft')
+ ->remove('comment,showModule,team,teamEstimate,teamConsumed,teamLeft,teamSource')
->get();
- if($task->consumed < $oldTask->consumed) return print(js::error($this->lang->task->error->consumedSmall));
-
- $teams = array();
- if(count(array_unique(array_filter($this->post->team))) > 1)
+ if(count(array_filter($this->post->team)) > 1)
{
- foreach($this->post->team as $row => $account)
- {
- if(empty($account) or isset($team[$account])) continue;
-
- $member = new stdClass();
- $member->account = $account;
- $member->join = helper::today();
- $member->root = $taskID;
- $member->type = 'task';
- $member->estimate = $this->post->teamEstimate[$row] ? $this->post->teamEstimate[$row] : 0;
- $member->consumed = $this->post->teamConsumed[$row] ? $this->post->teamConsumed[$row] : 0;
- $member->left = $this->post->teamLeft[$row] === '' ? 0 : $this->post->teamLeft[$row];
- $member->order = $row;
- $teams[$account] = $member;
- if($oldTask->status == 'done') $member->left = 0;
- }
- }
-
- /* Save team. */
- $this->dao->delete()->from(TABLE_TEAM)->where('root')->eq($taskID)->andWhere('type')->eq('task')->exec();
- if(!empty($teams))
- {
- foreach($teams as $member) $this->dao->insert(TABLE_TEAM)->data($member)->autoCheck()->exec();
-
- /* Assign the left hours to zero who will be skipped. */
- $skipMembers = $this->loadModel('execution')->getTeamSkip($oldTask->team, $oldTask->assignedTo, isset($task->assignedTo) ? $task->assignedTo : $oldTask->assignedTo);
- foreach($skipMembers as $account => $team) $this->dao->update(TABLE_TEAM)->set('left')->eq(0)->where('root')->eq($taskID)->andWhere('type')->eq('task')->andWhere('account')->eq($account)->exec();
-
- $task = $this->computeHours4Multiple($oldTask, $task, array(), $autoStatus = false);
- if($task->status == 'wait')
- {
- reset($teams);
- $task->assignedTo = key($teams);
- }
- }
- else
- {
- $task->mode = '';
+ $teams = $this->manageTaskTeam($oldTask->mode, $taskID, $task->status);
+ if(!empty($teams)) $task = $this->computeHours4Multiple($oldTask, $task);
}
+ if(empty($teams)) $task->mode = '';
if($oldTask->parent > 0) $this->updateParentStatus($taskID);
@@ -1654,14 +1684,16 @@ class taskModel extends model
parse_str($extra, $output);
$oldTask = $this->getById($taskID);
- if($oldTask->status == 'doing') dao::$errors[] = $this->lang->task->error->alreadyStarted;
if(!empty($oldTask->team))
{
- if($this->post->consumed < $oldTask->team[$this->app->user->account]->consumed) dao::$errors['consumed'] = $this->lang->task->error->consumedSmall;
+ $currentTeam = $this->getTeamByAccount($oldTask->team);
+ if($currentTeam and $this->post->consumed < $currentTeam->consumed) dao::$errors['consumed'] = $this->lang->task->error->consumedSmall;
+ if($currentTeam and $currentTeam->status == 'doing' and $oldTask->status == 'doing') dao::$errors[] = $this->lang->task->error->alreadyStarted;
}
else
{
if($this->post->consumed < $oldTask->consumed) dao::$errors['consumed'] = $this->lang->task->error->consumedSmall;
+ if($oldTask->status == 'doing') dao::$errors[] = $this->lang->task->error->alreadyStarted;
}
if(dao::isError()) return false;
@@ -1681,15 +1713,14 @@ class taskModel extends model
$task = $this->loadModel('file')->processImgURL($task, $editorIdList, $this->post->uid);
if($this->post->left == 0)
{
- if(isset($task->consumed) and $task->consumed == 0)
+ if(isset($task->consumed) and $task->consumed == 0) return dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->task->consumed);
+ if(empty($oldTask->team))
{
- dao::$errors[] = sprintf($this->lang->error->notempty, $this->lang->task->consumed);
- return false;
+ $task->status = 'done';
+ $task->finishedBy = $this->app->user->account;
+ $task->finishedDate = helper::now();
+ $task->assignedTo = $oldTask->openedBy;
}
- $task->status = 'done';
- $task->finishedBy = $this->app->user->account;
- $task->finishedDate = helper::now();
- $task->assignedTo = $oldTask->openedBy;
}
/* Record consumed and left. */
@@ -1700,30 +1731,31 @@ class taskModel extends model
$estimate->left = zget($_POST, 'left', 0);
$estimate->work = zget($task, 'work', '');
$estimate->account = $this->app->user->account;
- $estimate->consumed = !empty($oldTask->team) ? $estimate->consumed - $oldTask->team[$this->app->user->account]->consumed : $estimate->consumed - $oldTask->consumed;
+ $estimate->consumed = (!empty($oldTask->team) and $currentTeam) ? $estimate->consumed - $currentTeam->consumed : $estimate->consumed - $oldTask->consumed;
if($this->post->comment) $estimate->work = $this->post->comment;
$this->addTaskEstimate($estimate);
- if(!empty($oldTask->team))
+ if(!empty($oldTask->team) and $currentTeam)
{
- $teams = array_keys($oldTask->team);
- $assignedTo = empty($oldTask->assignedTo) ? $teams[0] : $oldTask->assignedTo;
-
$data = new stdclass();
$data->consumed = $this->post->consumed;
$data->left = $this->post->left;
+ $data->status = 'doing';
- $this->dao->update(TABLE_TEAM)->data($data)
- ->where('root')->eq($taskID)
- ->andWhere('type')->eq('task')
- ->andWhere('account')->eq($assignedTo)
- ->exec();
+ $this->dao->update(TABLE_TASKTEAM)->data($data)->where('id')->eq($currentTeam->id)->exec();
$task = $this->computeHours4Multiple($oldTask, $task);
+
+ $finishedUsers = $this->getFinishedUsers($oldTask->id, array_keys($oldTask->members));
+ if(count($finishedUsers) == count($oldTask->team))
+ {
+ $task->status = 'done';
+ $task->finishedBy = $this->app->user->account;
+ $task->finishedDate = $task->finishedDate;
+ }
}
- $this->dao->update(TABLE_TASK)->data($task)
- ->autoCheck()
+ $this->dao->update(TABLE_TASK)->data($task)->autoCheck()
->check('consumed,left', 'float')
->checkFlow()
->where('id')->eq((int)$taskID)->exec();
@@ -1763,9 +1795,10 @@ class taskModel extends model
if(dao::isError()) return false;
$estimates = array();
- $task = $this->getById($taskID);
+ $task = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch();;
+ $task->team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->orderBy('order')->fetchAll('id');
$earliestTime = '';
- foreach(array_keys($record->id) as $id)
+ foreach(array_keys($record->dates) as $id)
{
if($earliestTime == '')
{
@@ -1795,86 +1828,117 @@ class taskModel extends model
$this->loadModel('action');
- $consumed = 0;
- $left = $task->left;
- $now = helper::now();
- $lastDate = $this->dao->select('*')->from(TABLE_TASKESTIMATE)->where('task')->eq($taskID)->orderBy('date_desc')->limit(1)->fetch('date');
+ $allChanges = array();;
+ $left = $task->left;
+ $now = helper::now();
+ $oldStatus = $task->status;
+ $lastDate = $this->dao->select('*')->from(TABLE_TASKESTIMATE)->where('task')->eq($taskID)->orderBy('date_desc,id_desc')->limit(1)->fetch('date');
foreach($estimates as $estimate)
{
$this->addTaskEstimate($estimate);
- $consumed += $estimate->consumed;
$work = $estimate->work;
$estimateID = $this->dao->lastInsertID();
- $actionID = $this->action->create('task', $taskID, 'RecordEstimate', $work, (float)$estimate->consumed);
+
+ $newTask = clone $task;
+ $newTask->consumed += $estimate->consumed;
+ $newTask->lastEditedBy = $this->app->user->account;
+ $newTask->lastEditedDate = $now;
+ if(helper::isZeroDate($task->realStarted)) $newTask->realStarted = $now;
if(empty($lastDate) or $lastDate <= $estimate->date)
{
- $left = $estimate->left;
- $lastDate = $estimate->date;
+ $newTask->left = $estimate->left;
+ $lastDate = $estimate->date;
}
+
+ if($newTask->left == 0 and strpos('done,cancel,closed', $task->status) === false)
+ {
+ $newTask->status = 'done';
+ $newTask->assignedTo = $task->openedBy;
+ $newTask->assignedDate = $now;
+ $newTask->finishedBy = $this->app->user->account;
+ $newTask->finishedDate = $now;
+ $actionID = $this->action->create('task', $taskID, 'Finished', $work);
+ }
+ elseif($newTask->status == 'wait')
+ {
+ $newTask->status = 'doing';
+ $newTask->assignedTo = $this->app->user->account;
+ $newTask->assignedDate = $now;
+ $actionID = $this->action->create('task', $taskID, 'Started', $work);
+ }
+ elseif($newTask->left != 0 and strpos('done,pause,cancel,closed,pause', $task->status) !== false)
+ {
+ $newTask->status = 'doing';
+ $newTask->assignedTo = $this->app->user->account;
+ $newTask->finishedBy = '';
+ $newTask->canceledBy = '';
+ $newTask->closedBy = '';
+ $newTask->closedReason = '';
+ $newTask->finishedDate = '0000-00-00 00:00:00';
+ $newTask->canceledDate = '0000-00-00 00:00:00';
+ $newTask->closedDate = '0000-00-00 00:00:00';
+ $actionID = $this->action->create('task', $taskID, 'Activated', $work);
+ }
+ else
+ {
+ $actionID = $this->action->create('task', $taskID, 'RecordEstimate', $work, (float)$estimate->consumed);
+ }
+
+ /* Process multi-person task. Update consumed on team table. */
+ if(!empty($task->team))
+ {
+ $currentTeam = $this->getTeamByAccount($task->team, $this->app->user->account);
+ if($currentTeam)
+ {
+ $teamStatus = $estimate->left == 0 ? 'done' : 'doing';
+ $this->dao->update(TABLE_TASKTEAM)->set('left')->eq($estimate->left)->set("consumed = consumed + {$estimate->consumed}")->set('status')->eq($teamStatus)->where('id')->eq($currentTeam->id)->exec();
+ $currentTeam->consumed += $estimate->consumed;
+ $currentTeam->left = $estimate->left;
+ $currentTeam->status = $teamStatus;
+ }
+
+ $newTask = $this->computeHours4Multiple($task, $newTask, $task->team);
+ }
+
+ $changes = common::createChanges($task, $newTask, 'task');
+ if($changes and !empty($actionID)) $this->action->logHistory($actionID, $changes);
+ if($changes) $allChanges = array_merge($allChanges, $changes);
+ $task = $newTask;
}
- $data = new stdClass();
- $data->consumed = $task->consumed + $consumed;
- $data->left = $left;
- $data->status = $task->status;
- $data->lastEditedBy = $this->app->user->account;
- $data->lastEditedDate = $now;
- if(helper::isZeroDate($task->realStarted)) $data->realStarted = $now;
-
- if($left == 0 and strpos('done,cancel,closed', $task->status) === false)
+ if($allChanges)
{
- $data->status = 'done';
- $data->assignedTo = $task->openedBy;
- $data->assignedDate = $now;
- $data->finishedBy = $this->app->user->account;
- $data->finishedDate = $now;
+ $this->dao->update(TABLE_TASK)->data($task, 'team')->where('id')->eq($taskID)->exec();
- $this->action->create('task', $taskID, 'Finished');
- }
- elseif($task->status == 'wait')
- {
- $data->status = 'doing';
- $data->assignedTo = $this->app->user->account;
- $data->assignedDate = $now;
- $data->realStarted = $earliestTime;
- }
- elseif($left != 0 and strpos('done,cancel,closed,pause', $task->status) !== false)
- {
- $data->status = 'doing';
- $data->assignedTo = $this->app->user->account;
- $data->assignedDate = $now;
+ if($task->parent > 0) $this->updateParentStatus($task->id);
+ if($task->story) $this->loadModel('story')->setStage($task->story);
+ if($task->status != $oldStatus) $this->loadModel('kanban')->updateLane($task->execution, 'task', $taskID);
+ if($task->status == 'done' and !dao::isError()) $this->loadModel('score')->create('task', 'finish', $taskID);
}
- if(!empty($task->team))
- {
- $myConsumed = $task->team[$this->app->user->account]->consumed;
+ return $allChanges;
+ }
- $newTeamInfo = new stdClass();
- $newTeamInfo->consumed = $myConsumed + $consumed;
- $newTeamInfo->left = $left;
- $this->dao->update(TABLE_TEAM)->data($newTeamInfo)
- ->where('root')->eq($taskID)
- ->andWhere('type')->eq('task')
- ->andWhere('account')->eq($this->app->user->account)
- ->exec();
-
- $data = $this->computeHours4Multiple($task, $data);
- }
-
- $this->dao->update(TABLE_TASK)->data($data)->where('id')->eq($taskID)->exec();
-
- $changes = common::createChanges($task, $data);
- if(!empty($actionID)) $this->action->logHistory($actionID, $changes);
-
- if($task->parent > 0) $this->updateParentStatus($task->id);
- if($task->story) $this->loadModel('story')->setStage($task->story);
- if(isset($data->status) and $task->status != $data->status) $this->loadModel('kanban')->updateLane($task->execution, 'task', $taskID);
- if($task->status == 'done' and !dao::isError()) $this->loadModel('score')->create('task', 'finish', $taskID);
-
- return $changes;
+ /**
+ * Set effort left to 0.
+ *
+ * @param int $taskID
+ * @param array $members
+ * @access public
+ * @return void
+ */
+ public function resetEffortLeft($taskID, $members)
+ {
+ $table = $this->config->edition == 'open' ? TABLE_TASKESTIMATE : TABLE_EFFORT;
+ $this->dao->update($table)->set('`left`')->eq(0)->where('account')->in($members)
+ ->beginIF($this->config->edition == 'open')->andWhere('task')->eq($taskID)->fi()
+ ->beginIF($this->config->edition != 'open')->andWhere('objectID')->eq($taskID)->andWhere('objectType')->eq('task')->fi()
+ ->orderBy('date_desc,id_desc')
+ ->limit('1')
+ ->exec();
}
/**
@@ -1916,54 +1980,23 @@ class taskModel extends model
->get();
$currentConsumed = trim($this->post->currentConsumed);
- if(!is_numeric($currentConsumed))
- {
- dao::$errors[] = $this->lang->task->error->consumedNumber;
- return false;
- }
-
- if(empty($currentConsumed) and $oldTask->consumed == '0')
- {
- dao::$errors[] = $this->lang->task->error->consumedEmpty;
- return false;
- }
-
- if(!$this->post->realStarted)
- {
- dao::$errors[] = $this->lang->task->error->realStartedEmpty;
- return false;
- }
-
- if(!$this->post->finishedDate)
- {
- dao::$errors[] = $this->lang->task->error->finishedDateEmpty;
- return false;
- }
-
- if($this->post->realStarted > $this->post->finishedDate)
- {
- dao::$errors[] = $this->lang->task->error->finishedDateSmall;
- return false;
- }
+ if(!is_numeric($currentConsumed)) return dao::$errors[] = $this->lang->task->error->consumedNumber;
+ if(empty($currentConsumed) and $oldTask->consumed == '0') return dao::$errors[] = $this->lang->task->error->consumedEmpty;
+ if(!$this->post->realStarted) return dao::$errors[] = $this->lang->task->error->realStartedEmpty;
+ if(!$this->post->finishedDate) return dao::$errors[] = $this->lang->task->error->finishedDateEmpty;
+ if($this->post->realStarted > $this->post->finishedDate) return dao::$errors[] = $this->lang->task->error->finishedDateSmall;
/* Record consumed and left. */
if(empty($oldTask->team))
{
$consumed = $task->consumed - $oldTask->consumed;
- if($consumed < 0)
- {
- dao::$errors[] = $this->lang->task->error->consumedSmall;
- return false;
- }
+ if($consumed < 0) return dao::$errors[] = $this->lang->task->error->consumedSmall;
}
else
{
- $consumed = $task->consumed - $oldTask->team[$this->app->user->account]->consumed;
- if($consumed < 0)
- {
- dao::$errors[] = $this->lang->task->error->consumedSmall;
- return false;
- }
+ $currentTeam = $this->getTeamByAccount($oldTask->team);
+ $consumed = $currentTeam ? $task->consumed - $currentTeam->consumed : $task->consumed;
+ if($consumed < 0) return dao::$errors[] = $this->lang->task->error->consumedSmall;
}
$estimate = new stdclass();
@@ -1974,41 +2007,18 @@ class taskModel extends model
$estimate->account = $this->app->user->account;
$estimate->consumed = $consumed;
if($this->post->comment) $estimate->work = $this->post->comment;
- if(!empty($oldTask->team))
- {
- foreach($oldTask->team as $teamAccount => $team)
- {
- if($teamAccount == $this->app->user->account) continue;
- $estimate->left += $team->left;
- }
- }
if($estimate->consumed) $this->addTaskEstimate($estimate);
- if(!empty($oldTask->team))
+ if(!empty($oldTask->team) and $currentTeam)
{
- $this->dao->update(TABLE_TEAM)->set('left')->eq(0)->set('consumed')->eq($task->consumed)
- ->where('root')->eq((int)$taskID)
- ->andWhere('type')->eq('task')
- ->andWhere('account')->eq($this->app->user->account)->exec();
-
- $skipMembers = $oldTask->mode == 'linear' ? $this->loadModel('execution')->getTeamSkip($oldTask->team, $oldTask->assignedTo, $task->assignedTo) : $this->getFinishedUsers($oldTask->id, array_keys($oldTask->team));
- foreach($skipMembers as $account => $team) $this->dao->update(TABLE_TEAM)->set('left')->eq(0)->where('root')->eq($taskID)->andWhere('type')->eq('task')->andWhere('account')->eq($account)->exec();
-
+ $this->dao->update(TABLE_TASKTEAM)->set('left')->eq(0)->set('consumed')->eq($task->consumed)->set('status')->eq('done')->where('id')->eq($currentTeam->id)->exec();
$task = $this->computeHours4Multiple($oldTask, $task);
- if($oldTask->mode == 'multi' and count($skipMembers) == (count($oldTask->team) - 1) and !isset($skipMembers[$this->app->user->account]))
- {
- $task->status = 'done';
- $task->finishedBy = $this->app->user->account;
- $task->finishedDate = $task->finishedDate;
- }
}
if($task->finishedDate == substr($now, 0, 10)) $task->finishedDate = $now;
$task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->finish['id'], $this->post->uid);
- $this->dao->update(TABLE_TASK)->data($task)
- ->autoCheck()
- ->checkFlow()
+ $this->dao->update(TABLE_TASK)->data($task)->autoCheck()->checkFlow()
->where('id')->eq((int)$taskID)
->exec();
@@ -2218,7 +2228,7 @@ class taskModel extends model
->setDefault('assignedDate', helper::now())
->setDefault('activatedDate', helper::now())
->stripTags($this->config->task->editor->activate['id'], $this->config->allowedTags)
- ->remove('comment')
+ ->remove('comment,uid,multiple,team,teamEstimate,teamConsumed,teamLeft,teamSource')
->get();
if(!is_numeric($task->left))
@@ -2229,12 +2239,7 @@ class taskModel extends model
if(!empty($oldTask->team))
{
- $this->dao->update(TABLE_TEAM)->set('left')->eq($this->post->left)
- ->where('root')->eq($taskID)
- ->andWhere('type')->eq('task')
- ->andWhere('account')->eq($this->post->assignedTo)
- ->exec();
-
+ $this->manageTaskTeam($oldTask->mode, $oldTask->id, $task->status);
$task = $this->computeHours4Multiple($oldTask, $task);
}
@@ -2300,8 +2305,15 @@ class taskModel extends model
/* Check parent Task. */
if($task->parent > 0) $task->parentName = $this->dao->findById($task->parent)->from(TABLE_TASK)->fetch('name');
- $task->team = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->eq($taskID)->andWhere('type')->eq('task')->orderBy('order')->fetchAll('account');
- foreach($children as $child) $child->team = array();
+ $task->members = array();
+ $task->team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->orderBy('order')->fetchAll('id');
+ foreach($task->team as $member) $task->members[$member->account] = $member->account;
+
+ foreach($children as $child)
+ {
+ $child->team = array();
+ $child->members = array();
+ }
$task = $this->loadModel('file')->replaceImgURL($task, 'desc');
if($setImgSize) $task->desc = $this->file->setImgSize($task->desc);
@@ -2403,12 +2415,12 @@ class taskModel extends model
->from(TABLE_TASK)->alias('t1')
->leftJoin(TABLE_STORY)->alias('t2')->on('t1.story = t2.id')
->leftJoin(TABLE_USER)->alias('t3')->on('t1.assignedTo = t3.account')
- ->leftJoin(TABLE_TEAM)->alias('t4')->on('t4.root = t1.id')
+ ->leftJoin(TABLE_TASKTEAM)->alias('t4')->on('t4.task = t1.id')
->leftJoin(TABLE_MODULE)->alias('t5')->on('t1.module = t5.id')
->beginIF($this->config->edition == 'max')->leftJoin(TABLE_DESIGN)->alias('t6')->on('t1.design= t6.id')->fi()
->where('t1.execution')->eq((int)$executionID)
->beginIF($type == 'myinvolved')
- ->andWhere("((t4.`account` = '{$this->app->user->account}' AND t4.`type` = 'task') OR t1.`assignedTo` = '{$this->app->user->account}' OR t1.`finishedby` = '{$this->app->user->account}')")
+ ->andWhere("((t4.`account` = '{$this->app->user->account}') OR t1.`assignedTo` = '{$this->app->user->account}' OR t1.`finishedby` = '{$this->app->user->account}')")
->fi()
->beginIF($productID)->andWhere("((t5.root=" . (int)$productID . " and t5.type='story') OR t2.product=" . (int)$productID . ")")->fi()
->beginIF($type == 'undone')->andWhere('t1.status')->notIN('done,closed')->fi()
@@ -2416,7 +2428,7 @@ class taskModel extends model
->beginIF($type == 'assignedtome')->andWhere("(t1.assignedTo = '{$this->app->user->account}' or (t1.mode = 'multi' and t4.`account` = '{$this->app->user->account}') )")->fi()
->beginIF($type == 'finishedbyme')
->andWhere('t1.finishedby', 1)->eq($this->app->user->account)
- ->orWhere('t1.finishedList')->like("%,{$this->app->user->account},%")
+ ->orWhere('t5.status')->eq("done")
->markRight(1)
->fi()
->beginIF($type == 'delayed')->andWhere('t1.deadline')->gt('1970-1-1')->andWhere('t1.deadline')->lt(date(DT_DATE1))->andWhere('t1.status')->in('wait,doing')->fi()
@@ -2437,7 +2449,7 @@ class taskModel extends model
if(empty($tasks)) return array();
$taskList = array_keys($tasks);
- $taskTeam = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->in($taskList)->andWhere('type')->eq('task')->fetchGroup('root');
+ $taskTeam = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in($taskList)->fetchGroup('task');
if(!empty($taskTeam))
{
foreach($taskTeam as $taskID => $team) $tasks[$taskID]->team = $team;
@@ -2513,10 +2525,10 @@ class taskModel extends model
->beginIF($append)->orWhere('id')->in($append)->fi()
->fetchPairs();
+ $taskTeams = $this->dao->select('task, count(*) as count')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->groupBy('task')->fetchPairs('task', 'count');
foreach($tasks as $id => $name)
{
- $taskTeam = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->eq($id)->andWhere('type')->eq('task')->fetch();
- if(!empty($taskTeam)) unset($tasks[$id]);
+ if(!empty($taskTeams[$id])) unset($tasks[$id]);
}
return array('' => '') + $tasks ;
}
@@ -2542,7 +2554,7 @@ class taskModel extends model
->leftJoin(TABLE_EXECUTION)->alias('t2')->on("t1.execution = t2.id")
->leftJoin(TABLE_STORY)->alias('t3')->on('t1.story = t3.id')
->leftJoin(TABLE_PROJECT)->alias('t4')->on("t1.project = t4.id")
- ->leftJoin(TABLE_TEAM)->alias('t5')->on("t5.root = t1.id and t5.type = 'task' and t5.account = '{$account}'")
+ ->leftJoin(TABLE_TASKTEAM)->alias('t5')->on("t5.task = t1.id and t5.account = '{$account}'")
->where('t1.deleted')->eq(0)
->andWhere('t2.deleted')->eq(0)
->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi()
@@ -2552,7 +2564,7 @@ class taskModel extends model
->beginIF(!$this->app->user->admin)->andWhere('t1.execution')->in($this->app->user->view->sprints)->fi()
->beginIF($type == 'finishedBy')
->andWhere('t1.finishedby', 1)->eq($account)
- ->orWhere('t1.finishedList')->like("%,{$account},%")
+ ->orWhere('t5.status')->eq("done")
->markRight(1)
->fi()
->beginIF($type == 'assignedTo' and ($this->app->rawModule == 'my' or $this->app->rawModule == 'block'))->andWhere('t2.status', true)->ne('suspended')->orWhere('t4.status')->ne('suspended')->markRight(1)->fi()
@@ -2566,7 +2578,7 @@ class taskModel extends model
$this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', false);
- $taskTeam = $this->dao->select('*')->from(TABLE_TEAM)->where('root')->in(array_keys($tasks))->andWhere('type')->eq('task')->fetchGroup('root');
+ $taskTeam = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->in(array_keys($tasks))->fetchGroup('task');
if(!empty($taskTeam))
{
foreach($taskTeam as $taskID => $team) $tasks[$taskID]->team = $team;
@@ -2750,13 +2762,27 @@ class taskModel extends model
* Get task estimate.
*
* @param int $taskID
+ * @param string $account
+ * @param string $append
* @access public
- * @return object
+ * @return array
*/
- public function getTaskEstimate($taskID)
+ public function getTaskEstimate($taskID, $account = '', $append = '')
{
- return $this->dao->select('*')->from(TABLE_TASKESTIMATE)
- ->where('task')->eq($taskID)
+ if($this->config->edition == 'open')
+ {
+ return $this->dao->select('*')->from(TABLE_TASKESTIMATE)
+ ->where('task')->eq($taskID)
+ ->beginIF($account)->andWhere('account')->eq($account)->fi()
+ ->beginIF($append)->orWhere('id')->eq($append)->fi()
+ ->orderBy('date,id')
+ ->fetchAll();
+ }
+ return $this->dao->select('*')->from(TABLE_EFFORT)->where('objectID')->eq($taskID)
+ ->andWhere('objectType')->eq('task')
+ ->andWhere('deleted')->eq('0')
+ ->beginIF($account)->andWhere('account')->eq($account)->fi()
+ ->beginIF($append)->orWhere('id')->eq($append)->fi()
->orderBy('date,id')
->fetchAll();
}
@@ -2775,14 +2801,43 @@ class taskModel extends model
->fetch();
/* If the estimate is the last of its task, status of task will be checked. */
- $lastID = $this->dao->select('id')->from(TABLE_TASKESTIMATE)
- ->where('task')->eq($estimate->task)
- ->andWhere('id')->gt($estimate->id)
- ->fetch('id');
- $estimate->isLast = $lastID ? false : true;
+ $lastID = $this->dao->select('id')->from(TABLE_TASKESTIMATE)->where('task')->eq($estimate->task)->orderBy('date_desc,id_desc')->limit(1)->fetch('id');
+ $estimate->isLast = $lastID == $estimate->id;
return $estimate;
}
+ /**
+ * Check operate effort.
+ *
+ * @param object $task
+ * @param object $effort
+ * @access public
+ * @return bool
+ */
+ public function canOperateEffort($task, $effort = null)
+ {
+ if(empty($task->team)) return true;
+
+ /* Check for add effort. */
+ if(empty($effort))
+ {
+ if($task->mode == 'linear' and strpos('|done|closed|cancel|pause|', "|{$task->status}|") !== false) return false;
+ $members = array_map(function($member){ return $member->account; }, $task->team);
+ if(!in_array($this->app->user->account, $members)) return false;
+ if($task->mode == 'linear' and $this->app->user->account != $task->assignedTo) return false;
+ return true;
+ }
+
+ /* Check for edit and delete effort. */
+ if($task->mode == 'linear')
+ {
+ if(strpos('|closed|cancel|pause|', "|{$task->status}|") !== false) return false;
+ if($task->status == 'doing') return $effort->account == $this->app->user->account;
+ }
+ if($this->app->user->account == $effort->account) return true;
+ return false;
+ }
+
/**
* Update estimate.
*
@@ -2793,31 +2848,33 @@ class taskModel extends model
public function updateEstimate($estimateID)
{
$oldEstimate = $this->getEstimateById($estimateID);
- $estimate = fixer::input('post')->get();
+ $estimate = fixer::input('post')->cleanINT('consumed,left')->get();
$today = helper::today();
if($estimate->date > $today) return dao::$errors[] = $this->lang->task->error->date;
+ if($estimate->consumed < 0) return dao::$errors[] = sprintf($this->lang->error->ge, $this->lang->task->record, '0');
+ if($estimate->left < 0) return dao::$errors[] = sprintf($this->lang->error->ge, $this->lang->task->left, '0');
$task = $this->getById($oldEstimate->task);
$this->dao->update(TABLE_TASKESTIMATE)->data($estimate)
->autoCheck()
- ->check('consumed', 'notempty')
->where('id')->eq((int)$estimateID)
->exec();
$consumed = $task->consumed + $estimate->consumed - $oldEstimate->consumed;
- $lastEstimate = $this->dao->select('*')->from(TABLE_TASKESTIMATE)->where('task')->eq($task->id)->orderBy('id desc')->fetch();
+ $lastEstimate = $this->dao->select('*')->from(TABLE_TASKESTIMATE)->where('task')->eq($task->id)->orderBy('date_desc,id_desc')->limit(1)->fetch();
$left = ($lastEstimate and $estimateID == $lastEstimate->id) ? $estimate->left : $task->left;
$now = helper::now();
$data = new stdClass();
$data->consumed = $consumed;
$data->left = $left;
- $data->status = $left == 0 ? 'done' : $task->status;
+ $data->status = $task->status;
$data->lastEditedBy = $this->app->user->account;
$data->lastEditedDate = $now;
- if(!$left)
+ if(empty($left) and strpos('wait,doing,pause', $task->status) !== false)
{
+ $data->status = 'done';
$data->finishedBy = $this->app->user->account;
$data->finishedDate = $now;
$data->assignedTo = $task->openedBy;
@@ -2825,18 +2882,17 @@ class taskModel extends model
if(!empty($task->team))
{
- $oldConsumed = $task->team[$oldEstimate->account]->consumed;
+ $currentTeam = $this->getTeamByAccount($task->team, $oldEstimate->account, array('effortID' => $estimateID));
+ if($currentTeam)
+ {
+ $newTeamInfo = new stdClass();
+ $newTeamInfo->consumed = $currentTeam->consumed + $estimate->consumed - $oldEstimate->consumed;
+ if($currentTeam->status != 'done') $newTeamInfo->left = $left;
+ if($currentTeam->status != 'done' and $newTeamInfo->consumed > 0 and $left == 0) $newTeamInfo->status = 'done';
+ $this->dao->update(TABLE_TASKTEAM)->data($newTeamInfo)->where('id')->eq($currentTeam->id)->exec();
- $newTeamInfo = new stdClass();
- $newTeamInfo->consumed = $oldConsumed + $estimate->consumed - $oldEstimate->consumed;
- $newTeamInfo->left = $left;
- $this->dao->update(TABLE_TEAM)->data($newTeamInfo)
- ->where('root')->eq($oldEstimate->task)
- ->andWhere('type')->eq('task')
- ->andWhere('account')->eq($oldEstimate->account)
- ->exec();
-
- $data = $this->computeHours4Multiple($task, $data);
+ $data = $this->computeHours4Multiple($task, $data);
+ }
}
$this->dao->update(TABLE_TASK)->data($data)->where('id')->eq($task->id)->exec();
@@ -2870,45 +2926,82 @@ class taskModel extends model
{
$estimate = $this->getEstimateById($estimateID);
$task = $this->getById($estimate->task);
- $this->dao->delete()->from(TABLE_TASKESTIMATE)->where('id')->eq($estimateID)->exec();
+ $now = helper::now();
- $lastEstimate = $this->dao->select('*')->from(TABLE_TASKESTIMATE)->where('task')->eq($estimate->task)->orderBy('date desc,id desc')->limit(1)->fetch();
- $consumed = $task->consumed - $estimate->consumed;
- $left = isset($lastEstimate->left) ? $lastEstimate->left : $estimate->left;
+ $consumed = $task->consumed - $estimate->consumed;
+ $left = $task->left;
+ if($estimate->isLast)
+ {
+ $lastTwoEstimates = $this->dao->select('*')->from(TABLE_TASKESTIMATE)->where('task')->eq($estimate->task)->orderBy('date desc,id desc')->limit(2)->fetchAll();
+ $lastTwoEstimate = isset($lastTwoEstimates[1]) ? $lastTwoEstimates[1] : '';
+ if($lastTwoEstimate) $left = $lastTwoEstimate->left;
+ if(empty($lastTwoEstimate) and $left == 0) $left = $task->estimate;
+ }
$data = new stdclass();
$data->consumed = $consumed;
$data->left = $left;
$data->status = ($left == 0 && $consumed != 0) ? 'done' : $task->status;
- if($left == 0 and $consumed != 0)
+ if($consumed != 0 and $left == 0 and strpos('done,pause,cancel,closed', $task->status) === false)
{
- $data->status = 'done';
+ $data->status = 'done';
+ $data->assignedTo = $task->openedBy;
+ $data->assignedDate = $now;
+ $data->finishedBy = $this->app->user->account;
+ $data->finishedDate = $now;
}
- elseif($consumed == 0)
+ elseif($estimate->isLast and $left != 0 and strpos('done,pause,cancel,closed', $task->status) !== false)
{
- $data->status = 'wait';
- $data->left = $task->estimate;
+ $data->status = 'doing';
+ $data->finishedBy = '';
+ $data->canceledBy = '';
+ $data->closedBy = '';
+ $data->closedReason = '';
+ $data->finishedDate = '0000-00-00 00:00:00';
+ $data->canceledDate = '0000-00-00 00:00:00';
+ $data->closedDate = '0000-00-00 00:00:00';
}
else
{
$data->status = $task->status;
}
+
if(!empty($task->team))
{
- $oldConsumed = $task->team[$estimate->account]->consumed;
+ $currentTeam = $this->getTeamByAccount($task->team, $estimate->account, array('effortID' => $estimateID));
+ if($currentTeam)
+ {
+ $left = $currentTeam->left;
+ if($task->mode == 'multi')
+ {
+ $accountEstimates = $this->getTaskEstimate($currentTeam->task, $estimate->account, $estimateID);
+ $lastEstimate = array_pop($accountEstimates);
+ if($lastEstimate->id == $estimateID)
+ {
+ $lastTwoEstimate = array_pop($accountEstimates);
+ if($lastTwoEstimate) $left = $lastTwoEstimate->left;
+ }
+ }
- $newTeamInfo = new stdClass();
- $newTeamInfo->consumed = $oldConsumed - $estimate->consumed;
- $newTeamInfo->left = $left;
- $this->dao->update(TABLE_TEAM)->data($newTeamInfo)
- ->where('root')->eq($estimate->task)
- ->andWhere('type')->eq('task')
- ->andWhere('account')->eq($estimate->account)
- ->exec();
+ $newTeamInfo = new stdClass();
+ $newTeamInfo->consumed = $currentTeam->consumed - $estimate->consumed;
+ if($currentTeam->status != 'done') $newTeamInfo->left = $left;
+ if($currentTeam->status == 'done' and $left > 0 and $task->mode == 'multi') $newTeamInfo->left = $left;
- $data = $this->computeHours4Multiple($task, $data);
+ if($currentTeam->status != 'done' and $newTeamInfo->consumed > 0 and $left == 0) $newTeamInfo->status = 'done';
+ if($task->mode == 'multi' and $currentTeam->status == 'done' and $left > 0) $newTeamInfo->status = 'doing';
+ if($task->mode == 'multi' and $currentTeam->status == 'done' and ($newTeamInfo->consumed == 0 and $left == 0))
+ {
+ $newTeamInfo->status = 'doing';
+ $newTeamInfo->left = $currentTeam->estimate;
+ }
+ $this->dao->update(TABLE_TASKTEAM)->data($newTeamInfo)->where('id')->eq($currentTeam->id)->exec();
+ }
}
+ $this->dao->delete()->from(TABLE_TASKESTIMATE)->where('id')->eq($estimateID)->exec();
+ if(!empty($task->team)) $data = $this->computeHours4Multiple($task, $data);
+
$this->dao->update(TABLE_TASK)->data($data) ->where('id')->eq($estimate->task)->exec();
if($task->parent > 0) $this->updateParentStatus($task->id);
if($task->story) $this->loadModel('story')->setStage($task->story);
@@ -3423,6 +3516,29 @@ class taskModel extends model
if($action == 'recordestimate' and $task->parent == -1) return false;
if($action == 'delete' and $task->parent < 0) return false;
+ if(!empty($task->team))
+ {
+ global $app;
+ if($task->mode == 'linear')
+ {
+ if($action == 'assignto' and strpos('done,cencel,closed', $task->status) === false) return false;
+ if($action == 'start' and strpos('wait,doing', $task->status) !== false)
+ {
+ if($task->assignedTo != $app->user->account) return false;
+
+ $currentTeam = (new self())->getTeamByAccount($task->team, $app->user->account);
+ if($currentTeam and $currentTeam->status == 'wait') return true;
+ }
+ if($action == 'finish' and $task->assignedTo != $app->user->account) return false;
+ }
+ elseif($task->mode == 'multi')
+ {
+ $currentTeam = (new self())->getTeamByAccount($task->team, $app->user->account);
+ if($action == 'start' and strpos('wait,doing', $task->status) !== false and $currentTeam and $currentTeam->status == 'wait') return true;
+ if($action == 'finish' and (empty($currentTeam) or $currentTeam->status == 'done')) return false;
+ }
+ }
+
if($action == 'start') return $task->status == 'wait';
if($action == 'restart') return $task->status == 'pause';
if($action == 'pause') return $task->status == 'doing';
@@ -3666,21 +3782,26 @@ class taskModel extends model
public function printAssignedHtml($task, $users)
{
$btnTextClass = '';
+ $assignedToText = $assignedToTitle = zget($users, $task->assignedTo);
if(!empty($task->team) and $task->mode == 'multi' and $task->status != 'closed')
{
$assignedToText = $this->lang->task->team;
$teamMembers = array();
- foreach($task->team as $teamMember) $teamMembers[] = zget($users, $teamMember->account);
+ foreach($task->team as $teamMember)
+ {
+ $realname = zget($users, $teamMember->account);
+ if($this->app->user->account == $teamMember->account and $teamMember->status != 'done')
+ {
+ $task->assignedTo = $this->app->user->account;
+ $assignedToText = $realname;
+ }
+ $teamMembers[] = $realname;
+ }
+
$assignedToTitle = implode($this->lang->comma, $teamMembers);
}
- else
- {
- $assignedToText = $assignedToTitle = zget($users, $task->assignedTo);
- }
- $assignedToText = (!empty($task->team) and $task->mode == 'multi' and $task->status != 'closed') ? $this->lang->task->team : zget($users, $task->assignedTo);
-
- if(empty($task->assignedTo))
+ elseif(empty($task->assignedTo))
{
$btnTextClass = 'text-primary';
$assignedToText = $this->lang->task->noAssigned;
@@ -3735,34 +3856,32 @@ class taskModel extends model
* Get next user.
*
* @param string $users
- * @param string $current
+ * @param object $task
+ * @param string $type current|next
*
* @access public
- * @return void
+ * @return string
*/
- public function getNextUser($users, $current)
+ public function getAssignedTo4Multi($users, $task, $type = 'current')
{
+ if(empty($task->team) or $task->mode != 'linear') return $task->assignedTo;
+
+ $teamHours = array_values($task->team);
+
/* Process user */
if(!is_array($users)) $users = explode(',', trim($users, ','));
- if(!$current || !in_array($current, $users) || array_search($current, $users) == max(array_keys($users)))
- {
- return reset($users);
- }
+ $users = array_values($users);
+ if(is_object($users[0])) $users = array_map(function($member){ return $member->account; }, $users);
- $next = '';
- while(true)
+ foreach($users as $i => $account)
{
- if(current($users) == $current)
- {
- $next = next($users);
- break;
- }
- else
- {
- next($users);
- }
+ if(isset($teamHours[$i]) and $teamHours[$i]->status == 'done') continue;
+ if($type == 'current') return $account;
+ break;
}
- return $next;
+ if($type == 'next' and isset($users[$i + 1])) return $users[$i + 1];
+
+ return $task->openedBy;
}
/**
@@ -3794,15 +3913,11 @@ class taskModel extends model
*/
public function getFinishedUsers($taskID = 0, $team = array())
{
- $task = $this->getById($taskID);
- if($task->activatedDate == '') $task->activatedDate = "0000-00-00";
- return $this->dao->select('actor')->from(TABLE_ACTION)
- ->where('objectType')->eq('task')
- ->andWhere('objectID')->eq($taskID)
- ->andWhere('actor')->in($team)
- ->andWhere('action')->eq('finished')
- ->andWhere('date')->ge($task->activatedDate)
- ->fetchPairs('actor');
+ return $this->dao->select('id,account')->from(TABLE_TASKTEAM)
+ ->where('task')->eq($taskID)
+ ->andWhere('status')->eq('done')
+ ->beginIF($team)->andWhere('account')->in($team)->fi()
+ ->fetchPairs('id', 'account');
}
/**
@@ -3905,8 +4020,7 @@ class taskModel extends model
$menu .= $this->buildMenu('task', 'batchCreate', "execution=$task->execution&storyID=$task->story&moduleID=$task->module&taskID=$task->id", $task, 'view', 'split', '', '', '', "title='{$this->lang->task->children}'", $this->lang->task->children);
}
- $assignToLang = (!empty($task->team) and $task->mode == 'linear') ? $this->lang->task->transfer : $this->lang->task->assignTo;
- $menu .= $this->buildMenu('task', 'assignTo', "executionID=$task->execution&taskID=$task->id", $task, 'button', '', '', 'iframe', true, '', $assignToLang);
+ $menu .= $this->buildMenu('task', 'assignTo', "executionID=$task->execution&taskID=$task->id", $task, 'button', '', '', 'iframe', true, '', $this->lang->task->assignTo);
$menu .= $this->buildMenu('task', 'start', $params, $task, 'view', '', '', 'iframe showinonlybody', true);
$menu .= $this->buildMenu('task', 'restart', $params, $task, 'view', '', '', 'iframe showinonlybody', true);
@@ -3922,7 +4036,7 @@ class taskModel extends model
$menu .= "
";
$menu .= $this->buildMenu('task', 'edit', $params, $task, 'view', '', '', 'showinonlybody');
- $menu .= $this->buildMenu('task', 'create', "projctID={$task->execution}&storyID=0&moduleID=0&taskID=$task->id", $task, 'view', 'copy');
+ if(empty($task->team)) $menu .= $this->buildMenu('task', 'create', "projctID={$task->execution}&storyID=0&moduleID=0&taskID=$task->id", $task, 'view', 'copy');
$menu .= $this->buildMenu('task', 'delete', "executionID=$task->execution&taskID=$task->id", $task, 'view', 'trash', 'hiddenwin', 'showinonlybody', true);
if($task->parent > 0) $menu .= $this->buildMenu('task', 'view', "taskID=$task->parent", $task, 'view', 'chevron-double-up', '', '', '', '', $this->lang->task->parent);
diff --git a/module/task/view/activate.html.php b/module/task/view/activate.html.php
index d23301ad33..302e5e1c66 100644
--- a/module/task/view/activate.html.php
+++ b/module/task/view/activate.html.php
@@ -12,6 +12,14 @@
?>
+
+consumed);?>
+user->account);?>
+
+task->error->teamMember);?>
+lang->task->error->leftEmptyAB, $this->lang->task->statusList[$task->status]));?>
+error->notempty, $lang->task->estimate))?>
+error->notempty, $lang->task->left))?>
+team) and count($task->team) < 6) ? 6 - count($task->team) : 1);?>
diff --git a/module/task/view/assignto.html.php b/module/task/view/assignto.html.php
index 50f162d9d9..a04bc937ea 100644
--- a/module/task/view/assignto.html.php
+++ b/module/task/view/assignto.html.php
@@ -14,12 +14,12 @@
- team) and (!isset($task->team[$app->user->account]) or ($task->assignedTo != $app->user->account and $task->mode == 'linear'))):?>
+ members) and strpos('wait,doing,pause', $task->status) !== false and (!isset($task->members[$app->user->account]) or $task->mode == 'linear')):?>
- assignedTo != $app->user->account and $task->mode == 'linear'):?>
-
task->deniedNotice, '' . $task->assignedToRealName . ' ', $lang->task->transfer);?>
+ mode == 'linear'):?>
+
task->transferNotice;?>
task->deniedNotice, '' . $lang->task->teamMember . ' ', $lang->task->transfer);?>
@@ -38,14 +38,14 @@
@@ -278,44 +275,7 @@ foreach(explode(',', $config->task->edit->requiredFields) as $field)