+ task->team . ' > ' . $task->name;?> + " . $name . '') : html::a($this->createLink('task', 'view', 'task=' . $task->id), $name);?> + + arrow . (empty($task->team) ? $lang->task->assign : $lang->task->transfer);?> + +
++ +
diff --git a/module/execution/control.php b/module/execution/control.php index c73ae2f10a..fa3608c54a 100644 --- a/module/execution/control.php +++ b/module/execution/control.php @@ -284,6 +284,8 @@ class execution extends control $allCount = 0; foreach($tasks as $task) { + if($task->mode == 'multi') $task->assignedToRealName = $this->lang->task->team; + $groupTasks[] = $task; $allCount++; if(isset($task->children)) diff --git a/module/projectrelease/view/create.html.php b/module/projectrelease/view/create.html.php index beab636930..021a0b0cd5 100644 --- a/module/projectrelease/view/create.html.php +++ b/module/projectrelease/view/create.html.php @@ -85,10 +85,4 @@ - diff --git a/module/task/control.php b/module/task/control.php index 07c34fb64d..f55ed55380 100755 --- a/module/task/control.php +++ b/module/task/control.php @@ -715,6 +715,12 @@ class task extends control public function assignTo($executionID, $taskID, $kanbanGroup = '') { $this->commonAction($taskID); + $task = $this->task->getByID($taskID); + + if(!empty($task->team) and $task->mode == 'multi') + { + return $this->editTeam($executionID, $task, $kanbanGroup); + } if(!empty($_POST)) { @@ -754,7 +760,6 @@ class task extends control } $members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted'); - $task = $this->task->getByID($taskID); /* Compute next assignedTo. */ if(!empty($task->team)) @@ -2094,4 +2099,61 @@ class task extends control } echo json_encode($task); } + + /** + * Update assign of multi task. + * + * @param int $requestID + * @param object $task + * @param string $kanbanGroup + * @access public + * @return void + */ + public function editTeam($executionID, $task, $kanbanGroup = '') + { + $taskID = $task->id; + $this->commonAction($taskID); + + if(!empty($_POST)) + { + $this->loadModel('action'); + $changes = $this->task->updateTeam($taskID); + + if(dao::isError()) + { + if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + return print(js::error(dao::getError())); + } + + $actionID = $this->action->create('task', $taskID, 'Edited'); + $this->action->logHistory($actionID, $changes); + + $this->executeHooks($taskID); + + 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($execution->type == 'kanban' and $this->app->tab == 'execution') + { + $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); + $kanbanData = json_encode($kanbanData); + + return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData)")); + } + else + { + return print(js::closeModal('parent.parent', 'this')); + } + } + return print(js::locate($this->createLink('task', 'view', "taskID=$taskID"), 'parent')); + } + + $this->view->task = $task; + $this->view->members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted'); + $this->view->users = $this->loadModel('user')->getPairs(); + $this->display('', 'editTeam'); + } } diff --git a/module/task/js/editteam.js b/module/task/js/editteam.js new file mode 100644 index 0000000000..b41bb1b4c2 --- /dev/null +++ b/module/task/js/editteam.js @@ -0,0 +1,124 @@ +$(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(); +}); + +$('#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; + 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/model.php b/module/task/model.php index c923d9b7e8..3b9144e307 100644 --- a/module/task/model.php +++ b/module/task/model.php @@ -1509,6 +1509,97 @@ class taskModel extends model if(!dao::isError()) return common::createChanges($oldTask, $task); } + /** + * Update a task team. + * + * @param int $taskID + * @access public + * @return void + */ + public function updateTeam($taskID) + { + $oldTask = $this->getById($taskID); + if($this->post->estimate < 0 or $this->post->left < 0 or $this->post->consumed < 0) + { + dao::$errors[] = $this->lang->task->error->recordMinus; + return false; + } + + $now = helper::now(); + $task = fixer::input('post') + ->add('id', $taskID) + ->setDefault('estimate, left, consumed', 0) + ->setIF(is_numeric($this->post->estimate), 'estimate', (float)$this->post->estimate) + ->setIF(is_numeric($this->post->consumed), 'consumed', (float)$this->post->consumed) + ->setIF(is_numeric($this->post->left), 'left', (float)$this->post->left) + ->setDefault('lastEditedBy', $this->app->user->account) + ->setDefault('lastEditedDate', $now) + ->setDefault('assignedDate', $now) + ->stripTags($this->config->task->editor->assignto['id'], $this->config->allowedTags) + ->remove('comment,showModule,team,teamEstimate,teamConsumed,teamLeft') + ->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) + { + 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 = ''; + } + + if($oldTask->parent > 0) $this->updateParentStatus($taskID); + + $task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->assignto['id'], $this->post->uid); + $this->dao->update(TABLE_TASK) + ->data($task) + ->autoCheck() + ->checkIF($task->estimate != false, 'estimate', 'float') + ->checkIF($task->left != false, 'left', 'float') + ->checkIF($task->consumed != false, 'consumed', 'float') + ->checkFlow() + ->where('id')->eq($taskID) + ->exec(); + + if(!dao::isError()) return common::createChanges($oldTask, $task); + } + /** * Start a task. * @@ -2253,7 +2344,7 @@ class taskModel extends model ->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() ->beginIF($type == 'needconfirm')->andWhere('t2.version > t1.storyVersion')->andWhere("t2.status = 'active'")->fi() - ->beginIF($type == 'assignedtome')->andWhere('t1.assignedTo')->eq($this->app->user->account)->fi() + ->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},%") @@ -2268,7 +2359,7 @@ class taskModel extends model ->page($pager, 't1.id') ->fetchAll('id'); - $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', ($productID or in_array($type, array('myinvolved', 'needconfirm'))) ? false : true); + $this->loadModel('common')->saveQueryCondition($this->dao->get(), 'task', ($productID or in_array($type, array('myinvolved', 'needconfirm', 'assignedtome'))) ? false : true); if(empty($tasks)) return array(); @@ -2377,6 +2468,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}'") ->where('t1.deleted')->eq(0) ->andWhere('t2.deleted')->eq(0) ->beginIF($this->config->vision)->andWhere('t1.vision')->eq($this->config->vision)->fi() @@ -2390,7 +2482,8 @@ class taskModel extends model ->markRight(1) ->fi() ->beginIF($this->app->rawModule == 'my' or $this->app->rawModule == 'block')->andWhere('(t2.status')->ne('suspended')->orWhere('t4.status')->ne('suspended')->markRight(1)->fi() - ->beginIF($type != 'all' and $type != 'finishedBy')->andWhere("t1.`$type`")->eq($account)->fi() + ->beginIF($type != 'all' and $type != 'finishedBy' and $type != 'assignedTo')->andWhere("t1.`$type`")->eq($account)->fi() + ->beginIF($type == 'assignedTo')->andWhere("(t1.assignedTo = '{$account}' or (t1.mode = 'multi' and t5.`account` = '{$account}') )")->fi() ->orderBy($orderBy) ->beginIF($limit > 0)->limit($limit)->fi() ->page($pager) @@ -3478,7 +3571,7 @@ class taskModel extends model public function printAssignedHtml($task, $users) { $btnTextClass = ''; - $assignedToText = zget($users, $task->assignedTo); + $assignedToText = (!empty($task->team) and $task->mode == 'multi') ? $this->lang->task->team : zget($users, $task->assignedTo); if(empty($task->assignedTo)) { diff --git a/module/task/view/editteam.html.php b/module/task/view/editteam.html.php new file mode 100644 index 0000000000..1a8d18503c --- /dev/null +++ b/module/task/view/editteam.html.php @@ -0,0 +1,106 @@ + + * @package task + * @version $Id: complete.html.php 935 2010-07-06 07:49:24Z jajacn@126.com $ + * @link http://www.zentao.net + */ +?> + + + +consumed);?> +team);?> + +team) < 6 ? 6 - count($task->team) : 1);?> +task->error->teamMember);?> +lang->task->error->leftEmptyAB, $this->lang->task->statusList[$task->status]));?> +