Merge branch 'master' of https://git.zcorp.cc/easycorp/zentaopms
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
helper::import(dirname(__FILE__) . 'config/form.php');
|
||||
|
||||
$config->task = new stdclass();
|
||||
$config->task->batchCreate = 10;
|
||||
|
||||
|
||||
@@ -2,6 +2,26 @@
|
||||
$config->task->form = new stdclass();
|
||||
|
||||
global $app;
|
||||
$config->task->form->create = array();
|
||||
$config->task->form->create['execution'] = array('type' => 'int', 'required' => true);
|
||||
$config->task->form->create['type'] = array('type' => 'string', 'required' => true, 'default' => '');
|
||||
$config->task->form->create['module'] = array('type' => 'int', 'required' => false);
|
||||
$config->task->form->create['story'] = array('type' => 'int', 'required' => false);
|
||||
$config->task->form->create['mode'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->task->form->create['color'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->task->form->create['name'] = array('type' => 'string', 'required' => true, 'default' => '');
|
||||
$config->task->form->create['pri'] = array('type' => 'int', 'required' => false, 'default' => 3);
|
||||
$config->task->form->create['estimate'] = array('type' => 'float', 'required' => false, 'default' => 0);
|
||||
$config->task->form->create['desc'] = array('type' => 'string', 'required' => false);
|
||||
$config->task->form->create['estStarted'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->task->form->create['deadline'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->task->form->create['vision'] = array('type' => 'string', 'required' => false, 'default' => $config->vision);
|
||||
$config->task->form->create['status'] = array('type' => 'string', 'required' => false, 'default' => 'wait');
|
||||
$config->task->form->create['openedBy'] = array('type' => 'string', 'required' => false, 'default' => $app->user->account);
|
||||
$config->task->form->create['openedDate'] = array('type' => 'string', 'required' => false, 'default' => helper::now());
|
||||
$config->task->form->create['mailto'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->create['version'] = array('type' => 'int', 'required' => false, 'default' => 1);
|
||||
|
||||
$config->task->form->assign = array();
|
||||
$config->task->form->assign['assignedTo'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->task->form->assign['left'] = array('type' => 'float', 'required' => true);
|
||||
@@ -37,3 +57,27 @@ $config->task->form->edit['canceledDate'] = array('type' => 'string', 'required'
|
||||
$config->task->form->edit['closedBy'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->task->form->edit['closedReason'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
$config->task->form->edit['closedDate'] = array('type' => 'string', 'required' => false, 'default' => '');
|
||||
|
||||
$config->task->form->edit['team'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->edit['teamSource'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->edit['teamEstimate'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->edit['teamConsumed'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->edit['teamLeft'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->edit['deleteFiles'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
|
||||
$config->task->form->batchCreate = array();
|
||||
$config->task->form->batchCreate['module'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['parent'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['story'] = array('type' => 'array', 'required' => true, 'default' => array());
|
||||
$config->task->form->batchCreate['storyEstimate'] = array('type' => 'array', 'required' => true, 'default' => array());
|
||||
$config->task->form->batchCreate['storyDesc'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['storyPri'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['name'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['color'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['type'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['assignedTo'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['estimated'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['estStarted'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['deadline'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['desc'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
$config->task->form->batchCreate['pri'] = array('type' => 'array', 'required' => false, 'default' => array());
|
||||
|
||||
+93
-444
@@ -27,31 +27,29 @@ class task extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个任务。
|
||||
* Create a task.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param int $storyID
|
||||
* @param int $moduleID
|
||||
* @param int $taskID
|
||||
* @param int $todoID
|
||||
* @param string $executionID
|
||||
* @param string $storyID
|
||||
* @param string $moduleID
|
||||
* @param string $taskID
|
||||
* @param string $todoID
|
||||
* @param string $extra
|
||||
* @param string $bugID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function create($executionID = 0, $storyID = 0, $moduleID = 0, $taskID = 0, $todoID = 0, $extra = '', $bugID = 0)
|
||||
public function create(string $executionID = '0', string $storyID = '0', string $moduleID = '0', string $taskID = '0', string $todoID = '0', string $extra = '', string $bugID = '0')
|
||||
{
|
||||
/* If you do not have permission to access any execution, go to the create execution page. */
|
||||
if(empty($this->app->user->view->sprints) and !$executionID) $this->locate($this->createLink('execution', 'create'));
|
||||
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
|
||||
parse_str($extra, $output);
|
||||
|
||||
if(!empty($executionID)) $execution = $this->execution->getById($executionID);
|
||||
$executions = $this->execution->getPairs(0, 'all', isset($execution) ? (!common::canModify('execution', $execution) ? 'noclosed' : '') : 'noclosed');
|
||||
$executionID = $this->execution->saveState($executionID, $executions);
|
||||
/* Set menu and get execution information. */
|
||||
$executionID = $this->taskZen->setMenu($executionID);
|
||||
$execution = $this->execution->getById($executionID);
|
||||
|
||||
$this->execution->setMenu($executionID);
|
||||
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($this->session->project);
|
||||
|
||||
/* Check whether the execution has permission to create tasks. */
|
||||
$this->execution->getLimitedExecution();
|
||||
$limitedExecutions = !empty($_SESSION['limitedExecutions']) ? $_SESSION['limitedExecutions'] : '';
|
||||
if(strpos(",{$limitedExecutions},", ",$executionID,") !== false)
|
||||
@@ -60,318 +58,62 @@ class task extends control
|
||||
return print(js::locate($this->createLink('execution', 'task', "executionID=$executionID")));
|
||||
}
|
||||
|
||||
$task = new stdClass();
|
||||
$task->module = $moduleID;
|
||||
$task->mode = '';
|
||||
$task->assignedTo = '';
|
||||
$task->name = '';
|
||||
$task->story = $storyID;
|
||||
$task->type = '';
|
||||
$task->pri = '3';
|
||||
$task->estimate = '';
|
||||
$task->desc = '';
|
||||
$task->estStarted = '';
|
||||
$task->deadline = '';
|
||||
$task->mailto = '';
|
||||
$task->color = '';
|
||||
if($taskID > 0)
|
||||
{
|
||||
$task = $this->task->getByID($taskID);
|
||||
$executionID = $task->execution;
|
||||
|
||||
/* Emptying consumed hours when copy task. */
|
||||
if($task->mode == 'multi')
|
||||
{
|
||||
foreach($task->team as $teamMember) $teamMember->consumed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($todoID > 0)
|
||||
{
|
||||
$todo = $this->loadModel('todo')->getById($todoID);
|
||||
$task->name = $todo->name;
|
||||
$task->pri = $todo->pri;
|
||||
$task->desc = $todo->desc;
|
||||
}
|
||||
|
||||
if($bugID > 0)
|
||||
{
|
||||
$bug = $this->loadModel('bug')->getById($bugID);
|
||||
$task->name = $bug->title;
|
||||
$task->pri = $bug->pri;
|
||||
$task->pri = !empty($bug->pri) ? $bug->pri : '3';
|
||||
$task->assignedTo = array($bug->assignedTo);
|
||||
}
|
||||
|
||||
$taskLink = $this->createLink('execution', 'browse', "executionID=$executionID&tab=task");
|
||||
|
||||
$this->loadModel('kanban');
|
||||
if($execution->type == 'kanban')
|
||||
{
|
||||
$regionPairs = $this->kanban->getRegionPairs($execution->id, 0, 'execution');
|
||||
$regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs);
|
||||
$lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'task');
|
||||
$laneID = isset($output['laneID']) ? $output['laneID'] : key($lanePairs);
|
||||
|
||||
$this->view->regionID = $regionID;
|
||||
$this->view->laneID = $laneID;
|
||||
$this->view->regionPairs = $regionPairs;
|
||||
$this->view->lanePairs = $lanePairs;
|
||||
}
|
||||
|
||||
/* Submit the data processing after creating the task form. */
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$response['result'] = 'success';
|
||||
/* Prepare the data information before creating the task. */
|
||||
$result = $this->prepareCreate($executionID, (float)$this->post->estimate, $this->post->estStarted, $this->post->deadline, (bool)$this->post->selectTestStory);
|
||||
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
|
||||
setcookie('lastTaskModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
if($this->post->execution) $executionID = (int)$this->post->execution;
|
||||
list($task, $testTasks, $existTaskID) = $result;
|
||||
if($existTaskID) return $this->send(array('result' => 'success', 'message' => sprintf($this->lang->duplicate, $this->lang->task->common), 'locate' => $this->createLink('task', 'view', "taskID={$existTaskID}")));
|
||||
|
||||
/* Create task here. */
|
||||
$tasksID = $this->task->create($executionID, $bugID);
|
||||
if(dao::isError())
|
||||
{
|
||||
$response['result'] = 'fail';
|
||||
$response['message'] = dao::getError();
|
||||
return $this->send($response);
|
||||
}
|
||||
/* Create task. */
|
||||
$taskIdList = $this->task->create($task, $this->post->assignedTo, (int)$this->post->multiple, $this->post->team, (bool)$this->post->selectTestStory);
|
||||
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
|
||||
/* if the count of tasksID is 1 then check exists. */
|
||||
if(count($tasksID) == 1)
|
||||
{
|
||||
$taskID = current($tasksID);
|
||||
if($taskID['status'] == 'exists')
|
||||
{
|
||||
$response['locate'] = $this->createLink('task', 'view', "taskID={$taskID['id']}");
|
||||
$response['message'] = sprintf($this->lang->duplicate, $this->lang->task->common);
|
||||
return $this->send($response);
|
||||
}
|
||||
}
|
||||
/* Update other data related to the task after it is created. */
|
||||
$task->id = current($taskIdList);
|
||||
$columnID = isset($output['columnID']) ? (int)$output['columnID'] : 0;
|
||||
$this->task->afterCreate($task, $taskIdList, $bugID, $todoID, $testTasks);
|
||||
$this->task->updateKanbanData($execution, $task, (int)$_POST['lane'], $columnID);
|
||||
setcookie('lastTaskModule', (int)$_POST['module'], $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
|
||||
/* Create actions. */
|
||||
$this->loadModel('action');
|
||||
foreach($tasksID as $taskID)
|
||||
{
|
||||
/* if status is exists then this task has exists not new create. */
|
||||
if($taskID['status'] == 'exists') continue;
|
||||
|
||||
$taskID = $taskID['id'];
|
||||
$this->action->create('task', $taskID, 'Opened', '');
|
||||
}
|
||||
|
||||
/* Create task in kanban. */
|
||||
$kanbanID = $execution->type == 'kanban' ? $executionID : $_POST['execution'];
|
||||
|
||||
$laneID = isset($output['laneID']) ? $output['laneID'] : 0;
|
||||
if(!empty($_POST['lane'])) $laneID = $_POST['lane'];
|
||||
|
||||
$columnID = $this->kanban->getColumnIDByLaneID($laneID, 'wait');
|
||||
if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;
|
||||
|
||||
if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($kanbanID, $laneID, $columnID, 'task', $taskID);
|
||||
if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($kanbanID, 'task');
|
||||
|
||||
/* To do status. */
|
||||
if($todoID > 0)
|
||||
{
|
||||
$this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec();
|
||||
$this->action->create('todo', $todoID, 'finished', '', "TASK:$taskID");
|
||||
|
||||
if(($this->config->edition == 'biz' || $this->config->edition == 'max') && $todo->type == 'feedback' && $todo->idvalue) $this->loadModel('feedback')->updateStatus('todo', $todo->idvalue, 'done');
|
||||
}
|
||||
|
||||
$message = $this->executeHooks($taskID);
|
||||
if($message) $this->lang->saveSuccess = $message;
|
||||
$response['message'] = $this->lang->saveSuccess;
|
||||
|
||||
/* Return task id when call the API. */
|
||||
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $taskID));
|
||||
|
||||
/* If link from no head then reload. */
|
||||
if(isonlybody())
|
||||
{
|
||||
if($this->app->tab == 'execution' or $this->config->vision == 'lite')
|
||||
{
|
||||
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
|
||||
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
|
||||
if($execution->type == 'kanban')
|
||||
{
|
||||
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
|
||||
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
|
||||
$kanbanData = json_encode($kanbanData);
|
||||
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.updateKanban($kanbanData, 0)"));
|
||||
}
|
||||
else
|
||||
{
|
||||
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
|
||||
$kanbanData = $this->kanban->getExecutionKanban($executionID, $execLaneType, $execGroupBy, $taskSearchValue);
|
||||
$kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData);
|
||||
$kanbanData = $kanbanData[$kanbanType];
|
||||
$kanbanData = json_encode($kanbanData);
|
||||
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.updateKanban(\"task\", $kanbanData)"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent'));
|
||||
}
|
||||
}
|
||||
|
||||
/* Locate the browser. */
|
||||
if($this->app->getViewType() == 'xhtml')
|
||||
{
|
||||
$taskLink = $this->createLink('task', 'view', "taskID=$taskID", 'html');
|
||||
$response['locate'] = $taskLink;
|
||||
return $this->send($response);
|
||||
}
|
||||
|
||||
if($this->post->after == 'continueAdding')
|
||||
{
|
||||
$storyParam = $this->post->story ? $this->post->story : '';
|
||||
|
||||
$response['message'] = $this->lang->task->successSaved . $this->lang->task->afterChoices['continueAdding'];
|
||||
$response['locate'] = $this->createLink('task', 'create', "executionID=$executionID&storyID={$storyParam}&moduleID=$moduleID");
|
||||
return $this->send($response);
|
||||
}
|
||||
elseif($this->post->after == 'toTaskList')
|
||||
{
|
||||
setcookie('moduleBrowseParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
$taskLink = $this->createLink('execution', 'task', "executionID=$executionID&status=unclosed¶m=0&orderBy=id_desc");
|
||||
$response['locate'] = $taskLink;
|
||||
return $this->send($response);
|
||||
}
|
||||
elseif($this->post->after == 'toStoryList')
|
||||
{
|
||||
$response['locate'] = $this->createLink('execution', 'story', "executionID=$executionID");
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
|
||||
$response['locate'] = $this->createLink('projectstory', 'story', "projectID=$projectID");
|
||||
}
|
||||
return $this->send($response);
|
||||
}
|
||||
else
|
||||
{
|
||||
$response['locate'] = $taskLink;
|
||||
return $this->send($response);
|
||||
}
|
||||
/* Get the information returned after a task is created. */
|
||||
$response = $this->taskZen->responseAfterCreate($task, $execution, $_POST['after']);
|
||||
return $this->send($response);
|
||||
}
|
||||
|
||||
$users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
|
||||
$members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
|
||||
$showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
|
||||
$moduleOptionMenu = $this->tree->getTaskOptionMenu($executionID, 0, 0, $showAllModule ? 'allModule' : '');
|
||||
/* Shows the variables needed to create the task page. */
|
||||
$this->taskZen->showCreateVars($execution, $storyID, $moduleID, $taskID, $todoID, $bugID, $extra);
|
||||
|
||||
/* Fix bug #3381. When the story module is the root module. */
|
||||
if($storyID)
|
||||
{
|
||||
$task->module = $this->dao->findByID($storyID)->from(TABLE_STORY)->fetch('module');
|
||||
}
|
||||
else
|
||||
{
|
||||
$task->module = $task->module ? $task->module : (int)$this->cookie->lastTaskModule;
|
||||
if(!isset($moduleOptionMenu[$task->module])) $task->module = 0;
|
||||
}
|
||||
|
||||
$stories = $this->story->getExecutionStoryPairs($executionID, 0, 'all', '', '', 'active');
|
||||
|
||||
/* Get block id of assinge to me. */
|
||||
$blockID = 0;
|
||||
if(isonlybody())
|
||||
{
|
||||
$blockID = $this->dao->select('id')->from(TABLE_BLOCK)
|
||||
->where('block')->eq('assingtome')
|
||||
->andWhere('module')->eq('my')
|
||||
->andWhere('account')->eq($this->app->user->account)
|
||||
->orderBy('order_desc')
|
||||
->fetch('id');
|
||||
}
|
||||
|
||||
$title = $execution->name . $this->lang->colon . $this->lang->task->create;
|
||||
$position[] = html::a($taskLink, $execution->name);
|
||||
$position[] = $this->lang->task->common;
|
||||
$position[] = $this->lang->task->create;
|
||||
|
||||
$projectID = $execution ? $execution->project : 0;
|
||||
|
||||
/* Set Custom*/
|
||||
foreach(explode(',', $this->config->task->customCreateFields) as $field) $customFields[$field] = $this->lang->task->$field;
|
||||
|
||||
if(!empty($projectID))
|
||||
{
|
||||
$executions = $this->execution->getByProject($projectID, 'all', 0, true);
|
||||
|
||||
$executionKey = 0;
|
||||
$executionModifyList = $this->execution->getByIdList(array_keys($executions));
|
||||
foreach($executionModifyList as $modifykey)
|
||||
{
|
||||
if(!common::canModify('execution', $modifykey)) $executionKey = $modifykey->id;
|
||||
if($executionKey) unset($executions[$executionKey]);
|
||||
}
|
||||
}
|
||||
|
||||
$lifetimeList = array();
|
||||
$attributeList = array();
|
||||
$executionList = $this->execution->getByIdList(array_keys($executions));
|
||||
foreach($executionList as $id => $object)
|
||||
{
|
||||
$lifetimeList[$id] = $object->lifetime;
|
||||
$attributeList[$id] = $object->attribute;
|
||||
}
|
||||
|
||||
$testStoryIdList = $this->loadModel('story')->getTestStories(array_keys($stories), $execution->id);
|
||||
/* Stories that can be used to create test tasks. */
|
||||
$testStories = array();
|
||||
foreach($stories as $storyID => $storyTitle)
|
||||
{
|
||||
if(empty($storyID) or isset($testStoryIdList[$storyID])) continue;
|
||||
$testStories[$storyID] = $storyTitle;
|
||||
}
|
||||
|
||||
$this->view->customFields = $customFields;
|
||||
$this->view->showFields = $this->config->task->custom->createFields;
|
||||
$this->view->showAllModule = $showAllModule;
|
||||
|
||||
$this->view->title = $title;
|
||||
$this->view->testStories = $testStories;
|
||||
$this->view->position = $position;
|
||||
$this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('execution', 'task', "executionID=$executionID") : '';
|
||||
$this->view->execution = $execution;
|
||||
$this->view->executions = $executions;
|
||||
$this->view->lifetimeList = $lifetimeList;
|
||||
$this->view->attributeList = $attributeList;
|
||||
$this->view->task = $task;
|
||||
$this->view->users = $users;
|
||||
$this->view->storyID = $storyID;
|
||||
$this->view->stories = $stories;
|
||||
$this->view->testStoryIdList = $testStoryIdList;
|
||||
$this->view->members = $members;
|
||||
$this->view->blockID = $blockID;
|
||||
$this->view->moduleOptionMenu = $moduleOptionMenu;
|
||||
$this->view->projectID = $projectID;
|
||||
$this->view->productID = $this->loadModel('product')->getProductIDByProject($projectID);;
|
||||
$this->view->features = $this->execution->getExecutionFeatures($execution);
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch create task.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param int $storyID
|
||||
* @param int $iframe
|
||||
* @param int $taskID
|
||||
* @param string $extra
|
||||
*
|
||||
* @param string $executionID
|
||||
* @param string $storyID
|
||||
* @param string $moduleID
|
||||
* @param string $taskID
|
||||
* @param string $iframe
|
||||
* @param string $extra
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function batchCreate($executionID = 0, $storyID = 0, $moduleID = 0, $taskID = 0, $iframe = 0, $extra = '')
|
||||
public function batchCreate(string $executionID, string $storyID = '', string $moduleID = '', string $taskID = '', string $extra = '')
|
||||
{
|
||||
$this->execution->getLimitedExecution();
|
||||
$limitedExecutions = !empty($_SESSION['limitedExecutions']) ? $_SESSION['limitedExecutions'] : '';
|
||||
if(strpos(",{$limitedExecutions},", ",$executionID,") !== false)
|
||||
/* Init vars. */
|
||||
$executionID = (int)$executionID;
|
||||
$storyID = (int)$storyID;
|
||||
$moduleID = (int)$moduleID;
|
||||
$taskID = (int)$taskID;
|
||||
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
|
||||
parse_str($extra, $output);
|
||||
|
||||
/* 判断不能访问的执行。 TODO: 提示语应改为执行。 */
|
||||
if($this->checkLimitedExecution($executionID))
|
||||
{
|
||||
echo js::alert($this->lang->task->createDenied);
|
||||
return print(js::locate($this->createLink('execution', 'task', "executionID=$executionID")));
|
||||
@@ -379,147 +121,37 @@ class task extends control
|
||||
|
||||
$execution = $this->execution->getById($executionID);
|
||||
|
||||
if($this->app->tab == 'my')
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$taskLink = $this->createLink('my', 'work', 'mode=task');
|
||||
}
|
||||
elseif($this->app->tab == 'project' and $execution->multiple)
|
||||
{
|
||||
$taskLink = $this->createLink('project', 'execution', "browseType=all&projectID={$execution->project}");
|
||||
}
|
||||
else
|
||||
{
|
||||
$taskLink = $this->createLink('execution', 'browse', "executionID=$executionID");
|
||||
/* Form data. */
|
||||
$postData = form::data($this->config->task->form->batchCreate);
|
||||
|
||||
$mails = $this->task->batchCreate($executionID, $postData, $output);
|
||||
if(dao::isError()) return print(js::error(dao::getError()));
|
||||
|
||||
/* Return task id list when call the API. */
|
||||
$taskIDList = array();
|
||||
foreach($mails as $mail) $taskIDList[] = $mail->taskID;
|
||||
if($this->viewType == 'json' or (defined('RUN_MODE') and RUN_MODE == 'api')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $taskIDList));
|
||||
|
||||
/* 生成链接。 TODO: 写配置项。 */
|
||||
$redirectedLink = $this->taskZen->getRedirectedLink($execution);
|
||||
if(!isonlybody()) return print(js::locate($redirectedLink, 'parent'));
|
||||
|
||||
/* If link from no head then reload. */
|
||||
if($this->app->tab == 'execution' or $this->config->vision == 'lite')
|
||||
{
|
||||
$kanbanData = $this->taskZen->getKanbanData($execution);
|
||||
if($execution->type == 'kanban') return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData, 0)"));
|
||||
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban(\"task\", $kanbanData)"));
|
||||
}
|
||||
return print(js::reload('parent.parent'));
|
||||
}
|
||||
|
||||
/* Set menu. */
|
||||
$this->execution->setMenu($execution->id);
|
||||
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($this->session->project);
|
||||
$this->taskZen->setMenuByTab($executionID, $execution->project);
|
||||
|
||||
/* When common task are child tasks, query whether common task are consumed. */
|
||||
$taskConsumed = 0;
|
||||
if($taskID) $taskConsumed = $this->dao->select('consumed')->from(TABLE_TASK)->where('id')->eq($taskID)->andWhere('parent')->eq(0)->fetch('consumed');
|
||||
|
||||
/* When common task are child tasks, query whether common task are consumed. */
|
||||
$taskConsumed = 0;
|
||||
if($taskID) $taskConsumed = $this->dao->select('consumed')->from(TABLE_TASK)->where('id')->eq($taskID)->andWhere('parent')->eq(0)->fetch('consumed');
|
||||
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$mails = $this->task->batchCreate($executionID, $extra);
|
||||
if(dao::isError()) return print(js::error(dao::getError()));
|
||||
|
||||
$taskIDList = array();
|
||||
foreach($mails as $mail) $taskIDList[] = $mail->taskID;
|
||||
|
||||
/* Return task id list when call the API. */
|
||||
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'idList' => $taskIDList));
|
||||
|
||||
/* If link from no head then reload. */
|
||||
if(isonlybody())
|
||||
{
|
||||
if($this->app->tab == 'execution' or $this->config->vision == 'lite')
|
||||
{
|
||||
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
|
||||
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
|
||||
if($execution->type == 'kanban')
|
||||
{
|
||||
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
|
||||
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
|
||||
$kanbanData = json_encode($kanbanData);
|
||||
|
||||
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban($kanbanData, 0)"));
|
||||
}
|
||||
else
|
||||
{
|
||||
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
|
||||
$kanbanData = $this->loadModel('kanban')->getExecutionKanban($executionID, $execLaneType, $execGroupBy, $taskSearchValue);
|
||||
$kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData);
|
||||
$kanbanData = $kanbanData[$kanbanType];
|
||||
$kanbanData = json_encode($kanbanData);
|
||||
|
||||
return print(js::closeModal('parent.parent', '', "parent.parent.updateKanban(\"task\", $kanbanData)"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return print(js::reload('parent.parent'));
|
||||
}
|
||||
}
|
||||
return print(js::locate($taskLink, 'parent'));
|
||||
}
|
||||
|
||||
$story = $this->story->getByID($storyID);
|
||||
if($story)
|
||||
{
|
||||
$moduleID = $story->module;
|
||||
$stories = $this->story->getExecutionStoryPairs($executionID, 0, 'all', $moduleID, 'short', 'active');
|
||||
}
|
||||
else
|
||||
{
|
||||
$stories = $this->story->getExecutionStoryPairs($executionID, 0, 'all', 0, 'short', 'active');
|
||||
}
|
||||
|
||||
$members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
|
||||
$showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
|
||||
$modules = $this->loadModel('tree')->getTaskOptionMenu($executionID, 0, 0, $showAllModule ? 'allModule' : '');
|
||||
|
||||
/* Set Custom*/
|
||||
foreach(explode(',', $this->config->task->customBatchCreateFields) as $field)
|
||||
{
|
||||
if($execution->type == 'stage' and strpos('estStarted,deadline', $field) !== false) continue;
|
||||
$customFields[$field] = $this->lang->task->$field;
|
||||
}
|
||||
|
||||
$showFields = $this->config->task->custom->batchCreateFields;
|
||||
if($execution->lifetime == 'ops' or $execution->attribute == 'request' or $execution->attribute == 'review')
|
||||
{
|
||||
unset($customFields['story']);
|
||||
$showFields = str_replace(',story,', ',', ",$showFields,");
|
||||
$showFields = trim($showFields, ',');
|
||||
}
|
||||
|
||||
$this->view->customFields = $customFields;
|
||||
$this->view->showFields = $showFields;
|
||||
|
||||
if($execution->type == 'kanban')
|
||||
{
|
||||
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
|
||||
parse_str($extra, $output);
|
||||
|
||||
$this->loadModel('kanban');
|
||||
$regionPairs = $this->kanban->getRegionPairs($executionID, 0, 'execution');
|
||||
$regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs);
|
||||
$lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'task');
|
||||
$laneID = isset($output['laneID']) ? $output['laneID'] : key($lanePairs);
|
||||
|
||||
$this->view->regionID = $regionID;
|
||||
$this->view->laneID = $laneID;
|
||||
$this->view->regionPairs = $regionPairs;
|
||||
$this->view->lanePairs = $lanePairs;
|
||||
}
|
||||
|
||||
$title = $execution->name . $this->lang->colon . $this->lang->task->batchCreate;
|
||||
$position[] = html::a($taskLink, $execution->name);
|
||||
$position[] = $this->lang->task->common;
|
||||
$position[] = $this->lang->task->batchCreate;
|
||||
|
||||
if($taskID) $this->view->parentTitle = $this->dao->select('name')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch('name');
|
||||
if($taskID) $this->view->parentPri = $this->dao->select('pri')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch('pri');
|
||||
|
||||
$this->view->title = $title;
|
||||
$this->view->position = $position;
|
||||
$this->view->execution = $execution;
|
||||
$this->view->stories = $stories;
|
||||
$this->view->modules = $modules;
|
||||
$this->view->parent = $taskID;
|
||||
$this->view->storyID = $storyID;
|
||||
$this->view->story = $story;
|
||||
$this->view->storyTasks = $this->task->getStoryTaskCounts(array_keys($stories), $executionID);
|
||||
$this->view->members = $members;
|
||||
$this->view->moduleID = $moduleID;
|
||||
$this->view->taskConsumed = $taskConsumed;
|
||||
$this->display();
|
||||
$this->taskZen->buildBatchCreateForm($execution, $storyID, $moduleID, $taskID, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1129,7 +761,7 @@ class task extends control
|
||||
$this->view->task = $task;
|
||||
$this->view->from = $from;
|
||||
$this->view->orderBy = $orderBy;
|
||||
$this->view->efforts = $this->task->getTaskEstimate($taskID, '', '', $orderBy);
|
||||
$this->view->efforts = $this->task->getTaskEfforts($taskID, '', '', $orderBy);
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noclosed|noletter');
|
||||
$this->view->taskEffortFold = $taskEffortFold;
|
||||
|
||||
@@ -2333,4 +1965,21 @@ class task extends control
|
||||
$this->view->users = $this->loadModel('user')->getPairs();
|
||||
$this->display('', 'editTeam');
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户在该执行中是否是受限用户。
|
||||
* Checks if the current user is a limited user in this execution.
|
||||
*
|
||||
* @param string $executionID
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function checkLimitedExecution(string $executionID): bool
|
||||
{
|
||||
$this->execution->getLimitedExecution();
|
||||
$limitedExecutions = !empty($_SESSION['limitedExecutions']) ? $_SESSION['limitedExecutions'] : '';
|
||||
|
||||
if(strpos(",{$limitedExecutions},", ",$executionID,") !== false) return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+308
-474
@@ -14,298 +14,144 @@
|
||||
class taskModel extends model
|
||||
{
|
||||
/**
|
||||
* 创建一个任务。
|
||||
* Create a task.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param int $bugID
|
||||
* @param object $rawData
|
||||
* @param array $data
|
||||
* @param bool $selectTestStory
|
||||
* @access public
|
||||
* @return void
|
||||
* @return bool|array
|
||||
*/
|
||||
public function create($executionID, $bugID)
|
||||
public function create(object $task, array $assignedToList, int $multiple, array $team, bool $selectTestStory): bool|array
|
||||
{
|
||||
if((float)$this->post->estimate < 0)
|
||||
$this->loadModel('action');
|
||||
/* Remove required fields for creating tasks based on conditions. */
|
||||
$this->taskTao->removeCreateRequiredFields($task, $selectTestStory);
|
||||
|
||||
/* Create task. */
|
||||
$taskIdList = array();
|
||||
$taskFiles = array();
|
||||
foreach($assignedToList as $assignedTo)
|
||||
{
|
||||
dao::$errors[] = $this->lang->task->error->recordMinus;
|
||||
return false;
|
||||
}
|
||||
/* If the type of task is affair and assignedTo is empty, skip it.*/
|
||||
if($task->type == 'affair' and empty($assignedTo)) continue;
|
||||
|
||||
if(!empty($this->config->limitTaskDate))
|
||||
{
|
||||
$this->checkEstStartedAndDeadline($executionID, $this->post->estStarted, $this->post->deadline);
|
||||
if(dao::isError()) return false;
|
||||
}
|
||||
/* Process the assigned person data for the task. */
|
||||
$task->assignedTo = $multiple ? '' : $assignedTo;
|
||||
if($task->assignedTo) $task->assignedDate = helper::now();
|
||||
|
||||
$executionID = (int)$executionID;
|
||||
$estStarted = null;
|
||||
$deadline = null;
|
||||
$assignedTo = '';
|
||||
$taskIdList = array();
|
||||
$taskDatas = array();
|
||||
$taskFiles = array();
|
||||
$requiredFields = "," . $this->config->task->create->requiredFields . ",";
|
||||
/* Create task. */
|
||||
$taskID = $this->taskTao->doCreate($task);
|
||||
if(!$taskID) return false;
|
||||
|
||||
if($this->post->selectTestStory)
|
||||
{
|
||||
foreach($this->post->testStory as $i => $storyID)
|
||||
/* Set attachments for tasks. */
|
||||
$taskFiles = $this->setTaskFiles($taskFiles, $taskID);
|
||||
|
||||
/* If the task is multi-task, manage the team of task and calculate the team hours. */
|
||||
if($multiple and count(array_filter($team)) > 1)
|
||||
{
|
||||
if(empty($storyID)) continue;
|
||||
$task->id = $taskID;
|
||||
$teams = $this->manageTaskTeam($task->mode, $taskID, 'wait');
|
||||
if($teams) $this->computeHours4Multiple($task);
|
||||
unset($task->id);
|
||||
}
|
||||
|
||||
/* Check required fields when create test task. */
|
||||
foreach($this->post->testStory as $i => $storyID)
|
||||
{
|
||||
if(empty($storyID)) continue;
|
||||
$estStarted = (!isset($this->post->testEstStarted[$i]) or (isset($this->post->estStartedDitto[$i]) and $this->post->estStartedDitto[$i] == 'on')) ? $estStarted : $this->post->testEstStarted[$i];
|
||||
$deadline = (!isset($this->post->testDeadline[$i]) or (isset($this->post->deadlineDitto[$i]) and $this->post->deadlineDitto[$i] == 'on')) ? $deadline : $this->post->testDeadline[$i];
|
||||
$assignedTo = (!isset($this->post->testAssignedTo[$i]) or $this->post->testAssignedTo[$i] == 'ditto') ? $assignedTo : $this->post->testAssignedTo[$i];
|
||||
$taskIdList[] = $taskID;
|
||||
|
||||
if(!empty($this->config->limitTaskDate))
|
||||
{
|
||||
$this->checkEstStartedAndDeadline($executionID, $estStarted, $deadline);
|
||||
if(dao::isError())
|
||||
{
|
||||
foreach(dao::getError() as $field => $error)
|
||||
{
|
||||
dao::$errors[] = $error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($estStarted > $deadline)
|
||||
{
|
||||
dao::$errors[] = "ID: $storyID {$this->lang->task->error->deadlineSmall}";
|
||||
return false;
|
||||
}
|
||||
|
||||
$task = new stdclass();
|
||||
$task->pri = $this->post->testPri[$i];
|
||||
$task->estStarted = $estStarted;
|
||||
$task->deadline = $deadline;
|
||||
$task->assignedTo = $assignedTo;
|
||||
$task->estimate = $this->post->testEstimate[$i];
|
||||
$task->left = $this->post->testEstimate[$i];
|
||||
|
||||
/* Check requiredFields */
|
||||
$this->dao->insert(TABLE_TASK)->data($task)->batchCheck($requiredFields, 'notempty');
|
||||
if(dao::isError())
|
||||
{
|
||||
foreach(dao::getError() as $field => $error)
|
||||
{
|
||||
dao::$errors[] = $error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$taskDatas[$i] = $task;
|
||||
}
|
||||
|
||||
$requiredFields = str_replace(",estimate,", ',', "$requiredFields");
|
||||
$requiredFields = str_replace(",story,", ',', "$requiredFields");
|
||||
$requiredFields = str_replace(",estStarted,", ',', "$requiredFields");
|
||||
$requiredFields = str_replace(",deadline,", ',', "$requiredFields");
|
||||
$requiredFields = str_replace(",module,", ',', "$requiredFields");
|
||||
}
|
||||
|
||||
$this->loadModel('file');
|
||||
$task = fixer::input('post')
|
||||
->setDefault('execution', $executionID)
|
||||
->setDefault('estimate,left,story', 0)
|
||||
->setDefault('status', 'wait')
|
||||
->setDefault('project', $this->getProjectID($executionID))
|
||||
->setIF($this->post->estimate != false, 'left', $this->post->estimate)
|
||||
->setIF($this->post->story != false, 'storyVersion', $this->loadModel('story')->getVersion($this->post->story))
|
||||
->setIF(strpos($requiredFields, 'estStarted') !== false, 'estStarted', helper::isZeroDate($this->post->estStarted) ? '' : $this->post->estStarted)
|
||||
->setIF(strpos($requiredFields, 'deadline') !== false, 'deadline', helper::isZeroDate($this->post->deadline) ? '' : $this->post->deadline)
|
||||
->setIF(strpos($requiredFields, 'estimate') !== false, 'estimate', $this->post->estimate)
|
||||
->setIF(strpos($requiredFields, 'left') !== false, 'left', $this->post->left)
|
||||
->setIF(strpos($requiredFields, 'story') !== false, 'story', $this->post->story)
|
||||
->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('openedBy', $this->app->user->account)
|
||||
->setDefault('openedDate', helper::now())
|
||||
->setDefault('vision', $this->config->vision)
|
||||
->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,teamSource,teamEstimate,teamConsumed,teamLeft,teamMember,multiple,teams,contactListMenu,selectTestStory,testStory,testPri,testEstStarted,testDeadline,testAssignedTo,testEstimate,sync,otherLane,region,lane,estStartedDitto,deadlineDitto')
|
||||
->removeIF(empty($this->post->estStarted), 'estStarted')
|
||||
->removeIF(empty($this->post->deadline), 'deadline')
|
||||
->add('version', 1)
|
||||
->get();
|
||||
|
||||
if($task->type != 'test') $this->post->set('selectTestStory', 0);
|
||||
|
||||
foreach($this->post->assignedTo as $assignedTo)
|
||||
{
|
||||
/* When type is affair and has assigned then ignore none. */
|
||||
if($task->type == 'affair' and count($this->post->assignedTo) > 1 and empty($assignedTo)) continue;
|
||||
|
||||
$task->assignedTo = $assignedTo;
|
||||
if($assignedTo) $task->assignedDate = helper::now();
|
||||
|
||||
/* Check duplicate task. */
|
||||
if($task->type != 'affair' and $task->name)
|
||||
{
|
||||
$result = $this->loadModel('common')->removeDuplicate('task', $task, "execution={$executionID} and story=" . (int)$task->story . (isset($task->feedback) ? " and feedback=" . (int)$task->feedback : ''));
|
||||
if($result['stop'])
|
||||
{
|
||||
$taskIdList[$assignedTo] = array('status' => 'exists', 'id' => $result['duplicate']);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$task = $this->loadModel('file')->processImgURL($task, $this->config->task->editor->create['id'], $this->post->uid);
|
||||
|
||||
/* Fix Bug #1525 */
|
||||
$execution = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($task->execution)->fetch();
|
||||
if($execution->lifetime == 'ops' or $execution->attribute == 'request' or $execution->attribute == 'review')
|
||||
{
|
||||
$requiredFields = str_replace(",story,", ',', "$requiredFields");
|
||||
$task->story = 0;
|
||||
}
|
||||
|
||||
if(strpos($requiredFields, ',estimate,') !== false)
|
||||
{
|
||||
if(strlen(trim($task->estimate)) == 0) dao::$errors['estimate'] = sprintf($this->lang->error->notempty, $this->lang->task->estimate);
|
||||
$requiredFields = str_replace(',estimate,', ',', $requiredFields);
|
||||
}
|
||||
|
||||
$requiredFields = trim($requiredFields, ',');
|
||||
|
||||
/* Fix Bug #2466 */
|
||||
if($this->post->multiple) $task->assignedTo = '';
|
||||
if(!$this->post->multiple or count(array_filter($this->post->team)) < 1) $task->mode = '';
|
||||
$this->dao->insert(TABLE_TASK)->data($task, $skip = 'gitlab,gitlabProject')
|
||||
->autoCheck()
|
||||
->batchCheck($requiredFields, 'notempty')
|
||||
->checkIF($task->estimate != '', 'estimate', 'float')
|
||||
->checkIF(!helper::isZeroDate($task->deadline), 'deadline', 'ge', $task->estStarted)
|
||||
->checkFlow()
|
||||
->exec();
|
||||
|
||||
if(dao::isError()) return false;
|
||||
|
||||
$taskID = $this->dao->lastInsertID();
|
||||
|
||||
if($bugID > 0)
|
||||
{
|
||||
$this->dao->update(TABLE_TASK)->set('fromBug')->eq($bugID)->where('id')->eq($taskID)->exec();
|
||||
$this->dao->update(TABLE_BUG)->set('toTask')->eq($taskID)->where('id')->eq($bugID)->exec();
|
||||
$this->loadModel('action')->create('bug', $bugID, 'converttotask', '', $taskID);
|
||||
}
|
||||
|
||||
/* Mark design version.*/
|
||||
if(isset($task->design) && !empty($task->design))
|
||||
{
|
||||
$design = $this->loadModel('design')->getByID($task->design);
|
||||
$this->dao->update(TABLE_TASK)->set('designVersion')->eq($design->version)->where('id')->eq($taskID)->exec();
|
||||
}
|
||||
|
||||
$taskSpec = new stdClass();
|
||||
$taskSpec->task = $taskID;
|
||||
$taskSpec->version = $task->version;
|
||||
$taskSpec->name = $task->name;
|
||||
|
||||
if($task->estStarted) $taskSpec->estStarted = $task->estStarted;
|
||||
if($task->deadline) $taskSpec->deadline = $task->deadline;
|
||||
|
||||
$this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec();
|
||||
if(dao::isError()) return false;
|
||||
|
||||
if($this->post->story) $this->loadModel('story')->setStage($this->post->story);
|
||||
if($this->post->selectTestStory)
|
||||
{
|
||||
$testStoryIdList = array();
|
||||
$this->loadModel('action');
|
||||
if($this->post->testStory)
|
||||
{
|
||||
foreach($this->post->testStory as $storyID)
|
||||
{
|
||||
if($storyID) $testStoryIdList[$storyID] = $storyID;
|
||||
}
|
||||
$testStories = $this->dao->select('id,title,version,module')->from(TABLE_STORY)->where('id')->in($testStoryIdList)->fetchAll('id');
|
||||
foreach($this->post->testStory as $i => $storyID)
|
||||
{
|
||||
if(!isset($testStories[$storyID])) continue;
|
||||
|
||||
$assignedTo = $taskDatas[$i]->assignedTo;
|
||||
$testEstStarted = $taskDatas[$i]->estStarted;
|
||||
$testDeadline = $taskDatas[$i]->deadline;
|
||||
|
||||
$task->parent = $taskID;
|
||||
$task->story = $storyID;
|
||||
$task->storyVersion = $testStories[$storyID]->version;
|
||||
$task->name = $this->lang->task->lblTestStory . " #{$storyID} " . $testStories[$storyID]->title;
|
||||
$task->pri = $this->post->testPri[$i];
|
||||
$task->estStarted = $testEstStarted;
|
||||
$task->deadline = $testDeadline;
|
||||
$task->assignedTo = $assignedTo;
|
||||
$task->estimate = $this->post->testEstimate[$i];
|
||||
$task->left = $this->post->testEstimate[$i];
|
||||
$task->module = $testStories[$storyID]->module;
|
||||
$this->dao->insert(TABLE_TASK)->data($task)->exec();
|
||||
|
||||
$childTaskID = $this->dao->lastInsertID();
|
||||
$this->action->create('task', $childTaskID, 'Opened');
|
||||
}
|
||||
|
||||
$this->computeWorkingHours($taskID);
|
||||
$this->computeBeginAndEnd($taskID);
|
||||
$this->dao->update(TABLE_TASK)->set('parent')->eq(-1)->where('id')->eq($taskID)->exec();
|
||||
}
|
||||
}
|
||||
$this->file->updateObjectID($this->post->uid, $taskID, 'task');
|
||||
if(!empty($taskFiles))
|
||||
{
|
||||
foreach($taskFiles as $taskFile)
|
||||
{
|
||||
$taskFile->objectID = $taskID;
|
||||
$this->dao->insert(TABLE_FILE)->data($taskFile)->exec();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$taskFileTitle = $this->file->saveUpload('task', $taskID);
|
||||
$taskFiles = $this->dao->select('*')->from(TABLE_FILE)->where('id')->in(array_keys($taskFileTitle))->fetchAll('id');
|
||||
foreach($taskFiles as $fileID => $taskFile) unset($taskFiles[$fileID]->id);
|
||||
}
|
||||
|
||||
if($this->post->multiple and count(array_filter($this->post->team)) > 1)
|
||||
{
|
||||
$teams = $this->manageTaskTeam($task->mode, $taskID, 'wait');
|
||||
if($teams)
|
||||
{
|
||||
$task->id = $taskID;
|
||||
$this->computeHours4Multiple($task);
|
||||
}
|
||||
}
|
||||
|
||||
if(!dao::isError()) $this->loadModel('score')->create('task', 'create', $taskID);
|
||||
$taskIdList[$assignedTo] = array('status' => 'created', 'id' => $taskID);
|
||||
$this->action->create('task', $taskID, 'Opened', '');
|
||||
}
|
||||
return $taskIdList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a batch task.
|
||||
* 计算多人任务工时。
|
||||
* Compute hours for multiple task.
|
||||
*
|
||||
* @param object $oldTask
|
||||
* @param object $task
|
||||
* @param array $team
|
||||
* @param bool $autoStatus
|
||||
* @access public
|
||||
* @return object|bool
|
||||
*/
|
||||
public function computeHours4Multiple(object $oldTask, object $task = null, array $team = array(), bool $autoStatus = true): object|bool
|
||||
{
|
||||
if(!$oldTask) return false;
|
||||
|
||||
/* If the team is empty, get the team from the task team table. */
|
||||
if(empty($team)) $team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($oldTask->id)->orderBy('order')->fetchAll();
|
||||
/* If the team is not empty, compute the team hours. */
|
||||
if(!empty($team))
|
||||
{
|
||||
/* Get members, old team and current task. */
|
||||
$members = array_map(function($member){return $member->account;}, $team);
|
||||
$oldTeam = zget($oldTask, 'team', array());
|
||||
$currentTask = !empty($task) ? $task : new stdclass();
|
||||
if(!isset($currentTask->status)) $currentTask->status = $oldTask->status;
|
||||
$oldTask->team = $team;
|
||||
|
||||
/* If the assignedTo is not empty, the current task assignedTo is assignedTo. */
|
||||
if(!empty($_POST['assignedTo']) and is_string($_POST['assignedTo']))
|
||||
{
|
||||
$currentTask->assignedTo = $this->post->assignedTo;
|
||||
}
|
||||
/* If assignedTo is empty, get the assignedTo for the multiply linear task. */
|
||||
else
|
||||
{
|
||||
$currentTask->assignedTo = $this->getAssignedTo4Multi($members, $oldTask);
|
||||
if($oldTask->assignedTo != $currentTask->assignedTo) $currentTask->assignedDate = helper::now();
|
||||
|
||||
$oldTask->team = $oldTeam;
|
||||
}
|
||||
|
||||
/* Compute estimate and left. */
|
||||
$currentTask->estimate = 0;
|
||||
$currentTask->left = 0;
|
||||
foreach($team as $member)
|
||||
{
|
||||
$currentTask->estimate += (float)$member->estimate;
|
||||
$currentTask->left += (float)$member->left;
|
||||
}
|
||||
|
||||
/* Get task efforts, and compute consumed. */
|
||||
$efforts = $this->getTaskEfforts($oldTask->id);
|
||||
$currentTask->consumed = 0;
|
||||
foreach($efforts as $effort) $currentTask->consumed += (float)$effort->consumed;
|
||||
|
||||
/* If task is not empty, the task status is computed and the task is returned. */
|
||||
if(!empty($task)) return $this->taskTao->computeCurrentTaskStatus($currentTask, $oldTask, $task, $autoStatus, empty($efforts), $members);
|
||||
/* If task is empty, update the current task. */
|
||||
$this->dao->update(TABLE_TASK)->data($currentTask)->autoCheck()->where('id')->eq($oldTask->id)->exec();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch create tasks.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param string $extra
|
||||
* @param object $postData
|
||||
* @param array $output
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function batchCreate($executionID, $extra = '')
|
||||
public function batchCreate(int $executionID, object $postData, array $output)
|
||||
{
|
||||
$tasks = $postData->rawdata;
|
||||
|
||||
/* Load module and init vars. */
|
||||
$this->loadModel('common');
|
||||
$this->loadModel('score');
|
||||
$this->loadModel('action');
|
||||
$this->loadModel('kanban');
|
||||
$now = helper::now();
|
||||
$mails = array();
|
||||
$storyIDs = array();
|
||||
$taskNames = array();
|
||||
$preStory = 0;
|
||||
$tasks = fixer::input('post')->get();
|
||||
$now = helper::now();
|
||||
$mails = array();
|
||||
$preStory = 0;
|
||||
|
||||
/* 运营管理界面移除泳道和泳道列。 */
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$lanes = $tasks->lane;
|
||||
@@ -313,43 +159,16 @@ class taskModel extends model
|
||||
unset($tasks->lane);
|
||||
unset($tasks->column);
|
||||
}
|
||||
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
|
||||
parse_str($extra, $output);
|
||||
|
||||
/* Judge whether the current task is a parent. */
|
||||
$parentID = !empty($this->post->parent[1]) ? $this->post->parent[1] : 0;
|
||||
$parentID = !empty($tasks->parent[1]) ? $tasks->parent[1] : 0;
|
||||
$oldParentTask = $this->dao->findById((int)$parentID)->from(TABLE_TASK)->fetch();
|
||||
|
||||
foreach($tasks->story as $key => $storyID)
|
||||
{
|
||||
if(empty($tasks->name[$key])) continue;
|
||||
if($tasks->type[$key] == 'affair') continue;
|
||||
if($tasks->type[$key] == 'ditto' && isset($tasks->type[$key - 1]) && $tasks->type[$key - 1] == 'affair') continue;
|
||||
|
||||
if($storyID == 'ditto') $storyID = $preStory;
|
||||
$preStory = $storyID;
|
||||
|
||||
if(!isset($tasks->story[$key - 1]) and $key > 1 and !empty($tasks->name[$key - 1]))
|
||||
{
|
||||
$storyIDs[] = 0;
|
||||
$taskNames[] = $tasks->name[$key - 1];
|
||||
}
|
||||
|
||||
$inNames = in_array($tasks->name[$key], $taskNames);
|
||||
if(!$inNames || ($inNames && !in_array($storyID, $storyIDs)))
|
||||
{
|
||||
$storyIDs[] = $storyID;
|
||||
$taskNames[] = $tasks->name[$key];
|
||||
}
|
||||
else
|
||||
{
|
||||
dao::$errors['message'][] = sprintf($this->lang->duplicate, $this->lang->task->common) . ' ' . $tasks->name[$key];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->loadModel('common')->removeDuplicate('task', $tasks, "execution=$executionID and story " . helper::dbIN($storyIDs));
|
||||
$tasks = $result['data'];
|
||||
/* 去除重复数据。 */
|
||||
$tasks = $this->taskTao->removeDuplicate4BatchCreate($executionID, $tasks);
|
||||
if(!$tasks) return false;
|
||||
|
||||
/* Init vars. */
|
||||
$story = 0;
|
||||
$module = 0;
|
||||
$type = '';
|
||||
@@ -359,10 +178,11 @@ class taskModel extends model
|
||||
|
||||
/* Get task data. */
|
||||
$extendFields = $this->getFlowExtendFields();
|
||||
$projectID = $this->getProjectID($executionID);
|
||||
$execution = $this->dao->findById($executionID)->from(TABLE_EXECUTION)->fetch();
|
||||
$data = array();
|
||||
foreach($tasks->name as $i => $name)
|
||||
{
|
||||
/* 给同上的变量赋值。 */
|
||||
$story = (!isset($tasks->story[$i]) or $tasks->story[$i] == 'ditto') ? $story : $tasks->story[$i];
|
||||
$module = (!isset($tasks->module[$i]) or $tasks->module[$i] == 'ditto') ? $module : $tasks->module[$i];
|
||||
$type = (!isset($tasks->type[$i]) or $tasks->type[$i] == 'ditto') ? $type : $tasks->type[$i];
|
||||
@@ -370,6 +190,7 @@ class taskModel extends model
|
||||
$estStarted = (!isset($tasks->estStarted[$i]) or isset($tasks->estStartedDitto[$i])) ? $estStarted : $tasks->estStarted[$i];
|
||||
$deadline = (!isset($tasks->deadline[$i]) or isset($tasks->deadlineDitto[$i])) ? $deadline : $tasks->deadline[$i];
|
||||
|
||||
/* 检查任务名称为空的数据。 */
|
||||
if(empty($tasks->name[$i]))
|
||||
{
|
||||
if($this->common->checkValidRow('task', $tasks, $i))
|
||||
@@ -380,87 +201,19 @@ class taskModel extends model
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[$i] = new stdclass();
|
||||
$data[$i]->story = (int)$story;
|
||||
$data[$i]->type = $type;
|
||||
$data[$i]->module = (int)$module;
|
||||
$data[$i]->assignedTo = $assignedTo;
|
||||
$data[$i]->color = $tasks->color[$i];
|
||||
$data[$i]->name = $tasks->name[$i];
|
||||
$data[$i]->desc = nl2br($tasks->desc[$i]);
|
||||
$data[$i]->pri = $tasks->pri[$i];
|
||||
$data[$i]->estimate = $tasks->estimate[$i] ? $tasks->estimate[$i] : 0;
|
||||
$data[$i]->left = $tasks->estimate[$i] ? $tasks->estimate[$i] : 0;
|
||||
$data[$i]->project = $projectID;
|
||||
$data[$i]->execution = $executionID;
|
||||
$data[$i]->estStarted = $estStarted;
|
||||
$data[$i]->deadline = $deadline;
|
||||
$data[$i]->status = 'wait';
|
||||
$data[$i]->openedBy = $this->app->user->account;
|
||||
$data[$i]->openedDate = $now;
|
||||
$data[$i]->parent = $tasks->parent[$i];
|
||||
$data[$i]->vision = isset($tasks->vision[$i]) ? $tasks->vision[$i] : 'rnd';
|
||||
if($story) $data[$i]->storyVersion = $this->loadModel('story')->getVersion($data[$i]->story);
|
||||
if($assignedTo) $data[$i]->assignedDate = $now;
|
||||
if(strpos($this->config->task->create->requiredFields, 'estStarted') !== false and empty($estStarted)) $data[$i]->estStarted = '';
|
||||
if(strpos($this->config->task->create->requiredFields, 'deadline') !== false and empty($deadline)) $data[$i]->deadline = '';
|
||||
if(isset($tasks->lanes[$i])) $data[$i]->laneID = $tasks->lanes[$i];
|
||||
|
||||
foreach($extendFields as $extendField)
|
||||
{
|
||||
$data[$i]->{$extendField->field} = $this->post->{$extendField->field}[$i];
|
||||
if(is_array($data[$i]->{$extendField->field})) $data[$i]->{$extendField->field} = join(',', $data[$i]->{$extendField->field});
|
||||
|
||||
$data[$i]->{$extendField->field} = htmlSpecialString($data[$i]->{$extendField->field});
|
||||
}
|
||||
/* 构建任务数据。 */
|
||||
$dittoFields = array('story' => $story, 'module' => $module, 'type' => $type, 'assignedTo' => $assignedTo, 'estStarted' => $estStarted, 'deadline' => $deadline);
|
||||
$data = $this->taskTao->constructData4BatchCreate($execution, $tasks, $i, $dittoFields, $extendFields);
|
||||
}
|
||||
|
||||
/* Fix bug #1525*/
|
||||
$execution = $this->dao->select('*')->from(TABLE_PROJECT)->where('id')->eq($executionID)->fetch();
|
||||
$requiredFields = ',' . $this->config->task->create->requiredFields . ',';
|
||||
if($execution->lifetime == 'ops' or $execution->attribute == 'request' or $execution->attribute == 'review') $requiredFields = str_replace(',story,', ',', $requiredFields);
|
||||
$requiredFields = trim($requiredFields, ',');
|
||||
|
||||
/* check data. */
|
||||
foreach($data as $i => $task)
|
||||
{
|
||||
if(!empty($this->config->limitTaskDate))
|
||||
{
|
||||
$this->checkEstStartedAndDeadline($executionID, $task->estStarted, $task->deadline);
|
||||
if(dao::isError()) return false;
|
||||
}
|
||||
|
||||
if(!helper::isZeroDate($task->deadline) and $task->deadline < $task->estStarted)
|
||||
{
|
||||
dao::$errors['message'][] = $this->lang->task->error->deadlineSmall;
|
||||
return false;
|
||||
}
|
||||
|
||||
if($task->estimate and !preg_match("/^[0-9]+(.[0-9]{1,3})?$/", $task->estimate))
|
||||
{
|
||||
dao::$errors['message'][] = $this->lang->task->error->estimateNumber;
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach(explode(',', $requiredFields) as $field)
|
||||
{
|
||||
$field = trim($field);
|
||||
if(empty($field)) continue;
|
||||
|
||||
if(!isset($task->$field)) continue;
|
||||
if(!empty($task->$field)) continue;
|
||||
if($field == 'estimate' and strlen(trim($task->estimate)) != 0) continue;
|
||||
|
||||
dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->task->$field);
|
||||
return false;
|
||||
}
|
||||
if($task->estimate) $task->estimate = (float)$task->estimate;
|
||||
}
|
||||
|
||||
$childTasks = null;
|
||||
/* 检查必填项。 */
|
||||
$data = $this->taskTao->checkRequired4BatchCreate($execution, $data);
|
||||
if(!$data) return false;
|
||||
|
||||
$childTasks = '';
|
||||
foreach($data as $i => $task)
|
||||
{
|
||||
/* 获取当前任务所属泳道并移除laneID属性。 */
|
||||
$laneID = isset($output['laneID']) ? $output['laneID'] : 0;
|
||||
if(isset($task->laneID))
|
||||
{
|
||||
@@ -468,32 +221,20 @@ class taskModel extends model
|
||||
unset($task->laneID);
|
||||
}
|
||||
|
||||
/* 将数据插入到任务表中。 */
|
||||
$task->version = 1;
|
||||
$this->dao->insert(TABLE_TASK)->data($task)
|
||||
->autoCheck()
|
||||
->checkIF($task->estimate != '', 'estimate', 'float')
|
||||
->checkFlow()
|
||||
->exec();
|
||||
|
||||
if(dao::isError()) return false;
|
||||
|
||||
$taskID = $this->dao->lastInsertID();
|
||||
|
||||
$taskSpec = new stdClass();
|
||||
$taskSpec->task = $taskID;
|
||||
$taskSpec->version = $task->version;
|
||||
$taskSpec->name = $task->name;
|
||||
if($task->estStarted) $taskSpec->estStarted = $task->estStarted;
|
||||
if($task->deadline) $taskSpec->deadline = $task->deadline;
|
||||
|
||||
$this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec();
|
||||
if(dao::isError()) return false;
|
||||
$taskID = $this->taskTao->doCreate($task);
|
||||
if(!$taskID) return false;
|
||||
|
||||
/* 获取子任务ID。*/
|
||||
$childTasks .= $taskID . ',';
|
||||
|
||||
/* 设置相关需求阶段。 */
|
||||
if($task->story) $this->story->setStage($task->story);
|
||||
|
||||
$this->executeHooks($taskID);
|
||||
|
||||
/* 更新看板数据。 */
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$this->kanban->addKanbanCell($executionID, $lanes[$i], $columns[$i], 'task', $taskID);
|
||||
@@ -506,51 +247,41 @@ class taskModel extends model
|
||||
if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($executionID, $laneID, $columnID, 'task', $taskID);
|
||||
}
|
||||
|
||||
/* 记录日志并计算积分。 */
|
||||
$actionID = $this->action->create('task', $taskID, 'Opened', '');
|
||||
if(!dao::isError()) $this->loadModel('score')->create('task', 'create', $taskID);
|
||||
if(!dao::isError()) $this->score->create('task', 'create', $taskID);
|
||||
|
||||
/* 将taskID和actionID存入数组中。 */
|
||||
$mails[$i] = new stdclass();
|
||||
$mails[$i]->taskID = $taskID;
|
||||
$mails[$i]->actionID = $actionID;
|
||||
}
|
||||
|
||||
if(!dao::isError()) $this->loadModel('score')->create('ajax', 'batchCreate');
|
||||
if(!dao::isError()) $this->score->create('ajax', 'batchCreate');
|
||||
|
||||
if($parentID > 0 && !empty($taskID))
|
||||
/* 处理任务拆分的情况。 */
|
||||
if($oldParentTask and !empty($taskID))
|
||||
{
|
||||
$oldParentTask = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq((int)$parentID)->fetch();
|
||||
|
||||
/* 当一个普通任务有消耗时,拆分子任务。 */
|
||||
/* When common task are child tasks and the common task has consumption, create a child task. */
|
||||
if($oldParentTask->parent == 0 and $oldParentTask->consumed > 0)
|
||||
{
|
||||
$clonedTask = clone $oldParentTask;
|
||||
$clonedTask->parent = $parentID;
|
||||
unset($clonedTask->id);
|
||||
$this->dao->insert(TABLE_TASK)->data($clonedTask)->autoCheck()->exec();
|
||||
|
||||
$clonedTaskID = $this->dao->lastInsertID();
|
||||
|
||||
$this->dao->update(TABLE_EFFORT)->set('objectID')->eq($clonedTaskID)
|
||||
->where('objectID')->eq($oldParentTask->id)
|
||||
->andWhere('objectType')->eq('task')
|
||||
->exec();
|
||||
$splitResult = $this->taskTao->splitConsumedTask($oldParentTask);
|
||||
if(!$splitResult) return false;
|
||||
}
|
||||
|
||||
$this->updateParentStatus($taskID);
|
||||
$this->computeBeginAndEnd($parentID);
|
||||
$this->taskTao->updateParentAfterSplit($parentID);
|
||||
|
||||
$task = new stdclass();
|
||||
$task->parent = '-1';
|
||||
$task->lastEditedBy = $this->app->user->account;
|
||||
$task->lastEditedDate = $now;
|
||||
$this->dao->update(TABLE_TASK)->data($task)->where('id')->eq($parentID)->exec();
|
||||
|
||||
$newParentTask = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq((int)$parentID)->fetch();
|
||||
/* 记录父任务日志。 */
|
||||
$newParentTask = $this->dao->findById((int)$parentID)->from(TABLE_TASK)->fetch();
|
||||
$changes = common::createChanges($oldParentTask, $newParentTask);
|
||||
$actionID = $this->action->create('task', $parentID, 'createChildren', '', trim($childTasks, ','));
|
||||
if(!empty($changes)) $this->action->logHistory($actionID, $changes);
|
||||
}
|
||||
|
||||
/* 更新当前执行下的任务泳道。 */
|
||||
if(!isset($output['laneID']) or !isset($output['columnID']) or !isset($lanes)) $this->kanban->updateLane($executionID, 'task');
|
||||
return $mails;
|
||||
}
|
||||
@@ -811,59 +542,6 @@ class taskModel extends model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute hours for multiple task.
|
||||
*
|
||||
* @param object $oldTask
|
||||
* @param object $task
|
||||
* @param array $team
|
||||
* @param bool $autoStatus
|
||||
* @access public
|
||||
* @return object|bool
|
||||
*/
|
||||
public function computeHours4Multiple($oldTask, $task = null, $team = array(), $autoStatus = true)
|
||||
{
|
||||
if(!$oldTask) return false;
|
||||
|
||||
if(empty($team)) $team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($oldTask->id)->orderBy('order')->fetchAll();
|
||||
if(!empty($team))
|
||||
{
|
||||
$now = helper::now();
|
||||
$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;
|
||||
|
||||
if(!empty($_POST['assignedTo']) and is_string($_POST['assignedTo']))
|
||||
{
|
||||
$currentTask->assignedTo = $this->post->assignedTo;
|
||||
}
|
||||
else
|
||||
{
|
||||
$currentTask->assignedTo = $this->getAssignedTo4Multi($members, $oldTask);
|
||||
if($oldTask->assignedTo != $currentTask->assignedTo) $currentTask->assignedDate = $now;
|
||||
|
||||
$oldTask->team = $oldTeam;
|
||||
}
|
||||
|
||||
$currentTask->estimate = 0;
|
||||
$currentTask->left = 0;
|
||||
foreach($team as $member)
|
||||
{
|
||||
$currentTask->estimate += (float)$member->estimate;
|
||||
$currentTask->left += (float)$member->left;
|
||||
}
|
||||
|
||||
$efforts = $this->getTaskEstimate($oldTask->id);
|
||||
$currentTask->consumed = 0;
|
||||
foreach($efforts as $effort) $currentTask->consumed += (float)$effort->consumed;
|
||||
|
||||
if(!empty($task)) return $this->taskTao->computeCurrentTaskStatus($currentTask, $oldTask, $task, $autoStatus, empty($efforts), $members);
|
||||
$this->dao->update(TABLE_TASK)->data($currentTask)->autoCheck()->where('id')->eq($oldTask->id)->exec();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage multi task team members.
|
||||
*
|
||||
@@ -882,7 +560,7 @@ class taskModel extends model
|
||||
|
||||
if($taskStatus == 'doing')
|
||||
{
|
||||
$efforts = $this->getTaskEstimate($taskID);
|
||||
$efforts = $this->getTaskEfforts($taskID);
|
||||
$doingUsers = array();
|
||||
foreach($efforts as $i => $effort)
|
||||
{
|
||||
@@ -1012,7 +690,7 @@ class taskModel extends model
|
||||
}
|
||||
elseif($effortID)
|
||||
{
|
||||
$efforts = $this->getTaskEstimate($taskID, '', $effortID);
|
||||
$efforts = $this->getTaskEfforts($taskID, '', $effortID);
|
||||
|
||||
$prevTeam = null;
|
||||
$thisTeam = null;
|
||||
@@ -2342,7 +2020,7 @@ class taskModel extends model
|
||||
* @access public
|
||||
* @return object|bool
|
||||
*/
|
||||
public function getById($taskID, $setImgSize = false)
|
||||
public function getByID($taskID, $setImgSize = false)
|
||||
{
|
||||
$task = $this->dao->select('t1.*, t2.id AS storyID, t2.title AS storyTitle, t2.version AS latestStoryVersion, t2.status AS storyStatus, t3.realname AS assignedToRealName')
|
||||
->from(TABLE_TASK)->alias('t1')
|
||||
@@ -2692,7 +2370,7 @@ class taskModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task estimate.
|
||||
* Get task efforts.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param string $account
|
||||
@@ -2701,7 +2379,7 @@ class taskModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getTaskEstimate($taskID, $account = '', $append = '', $orderBy = 'date,id')
|
||||
public function getTaskEfforts($taskID, $account = '', $append = '', $orderBy = 'date,id')
|
||||
{
|
||||
return $this->dao->select('*')->from(TABLE_EFFORT)->where('objectID')->eq($taskID)
|
||||
->andWhere('objectType')->eq('task')
|
||||
@@ -2942,7 +2620,7 @@ class taskModel extends model
|
||||
$left = $currentTeam->left;
|
||||
if($task->mode == 'multi')
|
||||
{
|
||||
$accountEstimates = $this->getTaskEstimate($currentTeam->task, $estimate->account, $estimateID);
|
||||
$accountEstimates = $this->getTaskEfforts($currentTeam->task, $estimate->account, $estimateID);
|
||||
$lastEstimate = array_pop($accountEstimates);
|
||||
if($lastEstimate->id == $estimateID)
|
||||
{
|
||||
@@ -4226,4 +3904,160 @@ class taskModel extends model
|
||||
if(!empty($deadline) and !helper::isZeroDate($deadline) and $deadline > $execution->end) dao::$errors['deadline'][] = $pre . sprintf($this->lang->task->error->endGtExecution, $this->lang->execution->common, $execution->end);
|
||||
if(!empty($deadline) and !helper::isZeroDate($deadline) and $deadline < $execution->begin) dao::$errors['deadline'][] = $pre . sprintf($this->lang->task->error->endLtExecution, $this->lang->execution->common, $execution->begin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建任务后的其他数据处理。
|
||||
* Other data processing after task creation.
|
||||
*
|
||||
* @param object $task
|
||||
* @param array $taskIdList
|
||||
* @param int $bugID
|
||||
* @param object $rawData
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function afterCreate(object $task, array $taskIdList, int $bugID, int $todoID, array $testTasks): bool
|
||||
{
|
||||
$this->loadModel('file');
|
||||
$this->loadModel('score');
|
||||
foreach($taskIdList as $taskID)
|
||||
{
|
||||
/* If the task comes from a bug, update the task and bug information. */
|
||||
if($bugID > 0)
|
||||
{
|
||||
$this->dao->update(TABLE_TASK)->set('fromBug')->eq($bugID)->where('id')->eq($taskID)->exec();
|
||||
$this->dao->update(TABLE_BUG)->set('toTask')->eq($taskID)->where('id')->eq($bugID)->exec();
|
||||
$this->loadModel('action')->create('bug', $bugID, 'converttotask', '', $taskID);
|
||||
}
|
||||
|
||||
/* If the task comes from a todo, update the todo information. */
|
||||
if($todoID > 0)
|
||||
{
|
||||
$this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec();
|
||||
$this->loadModel('action')->create('todo', $todoID, 'finished', '', "TASK:$taskID");
|
||||
|
||||
/* If the todo comes from a feedback, update the feedback information. */
|
||||
$todo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch();
|
||||
if($this->config->edition != 'open' and $todo->type == 'feedback' and $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->idvalue, 'done');
|
||||
}
|
||||
|
||||
/* If the task comes from a design, update the task information. */
|
||||
if(!empty($task->design))
|
||||
{
|
||||
$design = $this->loadModel('design')->getByID($task->design);
|
||||
$this->dao->update(TABLE_TASK)->set('designVersion')->eq($design->version)->where('id')->eq($taskID)->exec();
|
||||
}
|
||||
|
||||
/* If the task comes from a story, update the stage of the story. */
|
||||
if($task->story) $this->loadModel('story')->setStage($task->story);
|
||||
|
||||
/* If the current task has test subtasks, create test subtasks and update the task information. */
|
||||
if(!empty($testTasks))
|
||||
{
|
||||
$this->taskTao->createTestChildTasks($taskID, $testTasks);
|
||||
$this->computeWorkingHours($taskID);
|
||||
$this->computeBeginAndEnd($taskID);
|
||||
$this->dao->update(TABLE_TASK)->set('`parent`')->eq(-1)->where('id')->eq($taskID)->exec();
|
||||
}
|
||||
if(dao::isError()) return false;
|
||||
|
||||
/* Update file information and create score. */
|
||||
$this->file->updateObjectID($this->post->uid, $taskID, 'task');
|
||||
$this->score->create('task', 'create', $taskID);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新看板中的任务泳道数据。
|
||||
* Update the task lane data in Kanban.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param object $rawData
|
||||
* @param int $taskID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function updateKanbanData(object $execution, object $task, int $laneID, int $oldColumnID): void
|
||||
{
|
||||
$this->loadModel('kanban');
|
||||
|
||||
/* Get kanban id, lane id and column id. */
|
||||
$kanbanID = $execution->type == 'kanban' ? $execution->id : $task->execution;
|
||||
$laneID = empty($laneID) ? 0 : $laneID;
|
||||
$columnID = $this->kanban->getColumnIDByLaneID($laneID, 'wait');
|
||||
if(empty($columnID)) $columnID = $oldColumnID;
|
||||
|
||||
/* If both of lane id and column id are not empty, add task to the kanban cell. */
|
||||
if($laneID and $columnID) $this->kanban->addKanbanCell($kanbanID, $laneID, $columnID, 'task', $task->id);
|
||||
/* If lane id or column id is empty, update the task type lane of the kanban. */
|
||||
if(!$laneID or !$columnID) $this->kanban->updateLane($kanbanID, 'task');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置任务的附件。
|
||||
* Set attachments for tasks.
|
||||
*
|
||||
* @param array $taskFiles
|
||||
* @param int $taskID
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function setTaskFiles(array $taskFiles, int $taskID): array
|
||||
{
|
||||
/* If taskFiles is not empty, create task files. */
|
||||
if(!empty($taskFiles))
|
||||
{
|
||||
foreach($taskFiles as $taskFile)
|
||||
{
|
||||
$taskFile->objectID = $taskID;
|
||||
$this->dao->insert(TABLE_FILE)->data($taskFile)->exec();
|
||||
}
|
||||
}
|
||||
/* If taskFiles is empty, get task files. */
|
||||
else
|
||||
{
|
||||
$taskFileTitle = $this->loadModel('file')->saveUpload('task', $taskID);
|
||||
$taskFiles = $this->dao->select('*')->from(TABLE_FILE)->where('id')->in(array_keys($taskFileTitle))->fetchAll('id');
|
||||
foreach($taskFiles as $fileID => $taskFile) unset($taskFiles[$fileID]->id);
|
||||
}
|
||||
|
||||
return $taskFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联需求的测试类型的子任务。
|
||||
* Create a subtask for the test type story with the story.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param object[] $testTasks
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function createTestChildTasks(int $taskID, array $testTasks): void
|
||||
{
|
||||
$this->loadModel('action');
|
||||
|
||||
/* Get the stories of the test tasks. */
|
||||
$testStoryIdList = array_keys($testTasks);
|
||||
$testStories = $this->dao->select('id,title,version,module')->from(TABLE_STORY)->where('id')->in($testStoryIdList)->fetchAll('id');
|
||||
foreach($testStoryIdList as $i => $storyID)
|
||||
{
|
||||
/* If the story id is not exist, skip it. */
|
||||
if(!isset($testStories[$storyID])) continue;
|
||||
|
||||
/* Construct a task and create it. */
|
||||
$task = $testTasks[$storyID];
|
||||
$task->parent = $taskID;
|
||||
$task->storyVersion = $testStories[$storyID]->version;
|
||||
$task->name = $this->lang->task->lblTestStory . " #{$storyID} " . $testStories[$storyID]->title;
|
||||
$task->module = $testStories[$storyID]->module;
|
||||
$this->dao->insert(TABLE_TASK)->data($task)->exec();
|
||||
|
||||
/* Get task id and create a action. */
|
||||
$childTaskID = $this->dao->lastInsertID();
|
||||
$this->action->create('task', $childTaskID, 'Opened');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+285
-9
@@ -13,8 +13,45 @@ declare(strict_types=1);
|
||||
class taskTao extends taskModel
|
||||
{
|
||||
/**
|
||||
* Compute progress of a task.
|
||||
* 创建一个任务。
|
||||
* Create a task.
|
||||
*
|
||||
* @param object $task
|
||||
* @access protected
|
||||
* @return int|bool
|
||||
*/
|
||||
protected function doCreate(object $task): int|bool
|
||||
{
|
||||
/* Insert task data. */
|
||||
$this->dao->insert(TABLE_TASK)->data($task)
|
||||
->checkIF($task->estimate != '', 'estimate', 'float')
|
||||
->autoCheck()
|
||||
->batchCheck($this->config->task->create->requiredFields, 'notempty')
|
||||
->checkFlow()
|
||||
->exec();
|
||||
|
||||
if(dao::isError()) return false;
|
||||
|
||||
/* Get task id. */
|
||||
$taskID = (int)$this->dao->lastInsertID();
|
||||
|
||||
/* Insert task desc data. */
|
||||
$taskSpec = new stdClass();
|
||||
$taskSpec->task = $taskID;
|
||||
$taskSpec->version = $task->version;
|
||||
$taskSpec->name = $task->name;
|
||||
$taskSpec->estStarted = $task->estStarted ? $task->estStarted : null;
|
||||
$taskSpec->deadline = $task->deadline ? $task->deadline : null;
|
||||
$this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec();
|
||||
|
||||
if(dao::isError()) return false;
|
||||
|
||||
return $taskID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务的进度。
|
||||
* Compute progress of a task.
|
||||
*
|
||||
* @param object $task
|
||||
* @access protected
|
||||
@@ -28,8 +65,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute progress of task list, include its' children.
|
||||
* 计算任务列表中每个任务的进度,包括子任务。
|
||||
* Compute progress of task list, include its' children.
|
||||
*
|
||||
* @param object[] $tasks
|
||||
* @access protected
|
||||
@@ -49,8 +86,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tasks under execution by executionID(Todo).
|
||||
* 获取执行下的任务。
|
||||
* Fetch tasks under execution by executionID(Todo).
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param int $productID
|
||||
@@ -110,6 +147,7 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过任务类型查找用户的任务。
|
||||
* Fetch user tasks by type.
|
||||
*
|
||||
* @param string $account
|
||||
@@ -153,8 +191,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task team by id list.
|
||||
* 通过任务ID列表查询任务团队信息。
|
||||
* Get task team by id list.
|
||||
*
|
||||
* @param array $taskIdList
|
||||
* @access protected
|
||||
@@ -166,8 +204,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task list by report.
|
||||
* 根据报表条件查询任务.
|
||||
* Get task list by report.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $condition
|
||||
@@ -182,8 +220,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assignedTo for the multiply linear task.
|
||||
* 获取多人串行任务的指派人。
|
||||
* Get the assignedTo for the multiply linear task.
|
||||
*
|
||||
* @param string|array $members
|
||||
* @param object $task
|
||||
@@ -216,8 +254,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the hierarchy of tasks to a parent-child structure.
|
||||
* 将任务的层级改为父子结构。
|
||||
* Change the hierarchy of tasks to a parent-child structure.
|
||||
*
|
||||
* @param object[] $tasks
|
||||
* @param object[] $parentTasks
|
||||
@@ -245,8 +283,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the status of the current task.
|
||||
* 计算当前任务的状态。
|
||||
* Compute the status of the current task.
|
||||
*
|
||||
* @param object $currentTask
|
||||
* @param object $oldTask
|
||||
@@ -259,15 +297,18 @@ class taskTao extends taskModel
|
||||
*/
|
||||
protected function computeCurrentTaskStatus(object $currentTask, object $oldTask, object $task, bool $autoStatus, bool $hasEfforts, array $members): object
|
||||
{
|
||||
/* If the status is not automatic, return the current task. */
|
||||
if(!$autoStatus) return $currentTask;
|
||||
|
||||
if($currentTask->consumed == 0 and $hasEfforts)
|
||||
/* If consumed of the current task is empty and current task has no efforts, the current task status should be wait. */
|
||||
if($currentTask->consumed == 0 and !$hasEfforts)
|
||||
{
|
||||
if(!isset($task->status)) $currentTask->status = 'wait';
|
||||
$currentTask->finishedBy = null;
|
||||
$currentTask->finishedDate = null;
|
||||
}
|
||||
|
||||
/* If neither consumed nor left of the current task is empty, the current task status should be doing. */
|
||||
if($currentTask->consumed > 0 && $currentTask->left > 0)
|
||||
{
|
||||
$currentTask->status = 'doing';
|
||||
@@ -275,9 +316,11 @@ class taskTao extends taskModel
|
||||
$currentTask->finishedDate = null;
|
||||
}
|
||||
|
||||
/* If consumed of the current task is not empty and left of the current task is empty, the current task status should be done or doing. */
|
||||
if($currentTask->consumed > 0 and $currentTask->left == 0)
|
||||
{
|
||||
$finisedUsers = $this->getFinishedUsers($oldTask->id, $members);
|
||||
/* If the number of finisher is less than the number of team members , the current task status should be doing. */
|
||||
if(count($finisedUsers) != count($members))
|
||||
{
|
||||
if(strpos('cancel,pause', $oldTask->status) === false or ($oldTask->status == 'closed' and $oldTask->reason == 'done'))
|
||||
@@ -287,6 +330,7 @@ class taskTao extends taskModel
|
||||
$currentTask->finishedDate = null;
|
||||
}
|
||||
}
|
||||
/* If status of old task is wait or doing or pause, the current task status should be done. */
|
||||
elseif(strpos('wait,doing,pause', $oldTask->status) !== false)
|
||||
{
|
||||
$currentTask->status = 'done';
|
||||
@@ -332,4 +376,236 @@ class taskTao extends taskModel
|
||||
->where('id')->eq($taskID)
|
||||
->exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件移除创建任务的必填项。
|
||||
* Remove required fields for creating tasks based on conditions.
|
||||
*
|
||||
* @param object $task
|
||||
* @param bool $selectTestStory
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function removeCreateRequiredFields(object $task, bool $selectTestStory): void
|
||||
{
|
||||
/* Get create required fields and the execution of the task. */
|
||||
$requiredFields = "," . $this->config->task->create->requiredFields . ",";
|
||||
$execution = $this->dao->findByID($task->execution)->from(TABLE_PROJECT)->fetch();
|
||||
|
||||
/* If the lifetime if the execution is ops and the attribute of execution is request or review, remove story from required fields. */
|
||||
if($execution->lifetime == 'ops' or in_array($execution->attribute, array('request', 'review')))
|
||||
{
|
||||
$requiredFields = str_replace(",story,", ',', "$requiredFields");
|
||||
}
|
||||
|
||||
/* If the type of the task is test and select story is true, remove some required fields. */
|
||||
if($task->type == 'test' and $selectTestStory)
|
||||
{
|
||||
$requiredFields = str_replace(array(",estimate,", ",story,", ",estStarted,", ",deadline,", ",module,"), ',', "$requiredFields");
|
||||
}
|
||||
|
||||
$this->config->task->create->requiredFields = trim($requiredFields, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* 在批量创建之前移除post数据中重复的数据。
|
||||
* Remove the duplicate data before batch create tasks.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param object $tasks
|
||||
* @access protected
|
||||
* @return object|false
|
||||
*/
|
||||
protected function removeDuplicate4BatchCreate(int $executionID, object $tasks): object|false
|
||||
{
|
||||
$storyIDs = array();
|
||||
$taskNames = array();
|
||||
|
||||
foreach($tasks->story as $key => $storyID)
|
||||
{
|
||||
/* 过滤事务型和任务名称为空的数据。 */
|
||||
if(empty($tasks->name[$key])) continue;
|
||||
if($tasks->type[$key] == 'affair') continue;
|
||||
if($tasks->type[$key] == 'ditto' and isset($tasks->type[$key - 1]) and $tasks->type[$key - 1] == 'affair') continue;
|
||||
|
||||
if($storyID == 'ditto') $storyID = $preStory;
|
||||
$preStory = $storyID;
|
||||
|
||||
if(!isset($tasks->story[$key - 1]) and $key > 1 and !empty($tasks->name[$key - 1]))
|
||||
{
|
||||
$storyIDs[] = 0;
|
||||
$taskNames[] = $tasks->name[$key - 1];
|
||||
}
|
||||
|
||||
/* 判断Post传过来的任务有没有重复数据。 */
|
||||
$hasExistsName = in_array($tasks->name[$key], $taskNames);
|
||||
if($hasExistsName and in_array($storyID, $storyIDs))
|
||||
{
|
||||
dao::$errors['message'][] = sprintf($this->lang->duplicate, $this->lang->task->common) . ' ' . $tasks->name[$key];
|
||||
return false;
|
||||
}
|
||||
|
||||
$storyIDs[] = $storyID;
|
||||
$taskNames[] = $tasks->name[$key];
|
||||
}
|
||||
|
||||
/* 去重并赋值。 */
|
||||
$result = $this->loadModel('common')->removeDuplicate('task', $tasks, "execution=$executionID and story " . helper::dbIN($storyIDs));
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建任务之前构造数据。
|
||||
* Construct data before batch create tasks.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param object $tasks
|
||||
* @param int $index
|
||||
* @param array $dittoFields
|
||||
* @param array $extendFields
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function constructData4BatchCreate(object $execution, object $tasks, int $index, array $dittoFields, array $extendFields): array
|
||||
{
|
||||
extract($dittoFields);
|
||||
$now = helper::now();
|
||||
|
||||
$data[$index] = new stdclass();
|
||||
$data[$index]->story = (int)$story;
|
||||
$data[$index]->type = $type;
|
||||
$data[$index]->module = (int)$module;
|
||||
$data[$index]->assignedTo = $assignedTo;
|
||||
$data[$index]->color = $tasks->color[$index];
|
||||
$data[$index]->name = $tasks->name[$index];
|
||||
$data[$index]->desc = nl2br($tasks->desc[$index]);
|
||||
$data[$index]->pri = $tasks->pri[$index];
|
||||
$data[$index]->estimate = $tasks->estimate[$index] ? $tasks->estimate[$index] : 0;
|
||||
$data[$index]->left = $tasks->estimate[$index] ? $tasks->estimate[$index] : 0;
|
||||
$data[$index]->project = $execution->project;
|
||||
$data[$index]->execution = $execution->id;
|
||||
$data[$index]->estStarted = $estStarted;
|
||||
$data[$index]->deadline = $deadline;
|
||||
$data[$index]->status = 'wait';
|
||||
$data[$index]->openedBy = $this->app->user->account;
|
||||
$data[$index]->openedDate = $now;
|
||||
$data[$index]->parent = $tasks->parent[$index];
|
||||
$data[$index]->vision = isset($tasks->vision[$index]) ? $tasks->vision[$index] : 'rnd';
|
||||
if($story) $data[$index]->storyVersion = (int)$this->dao->findById($data[$index]->story)->from(TABLE_STORY)->fetch('version');
|
||||
if($assignedTo) $data[$index]->assignedDate = $now;
|
||||
if(strpos($this->config->task->create->requiredFields, 'estStarted') !== false and empty($estStarted)) $data[$index]->estStarted = '';
|
||||
if(strpos($this->config->task->create->requiredFields, 'deadline') !== false and empty($deadline)) $data[$index]->deadline = '';
|
||||
if(isset($tasks->lanes[$index])) $data[$index]->laneID = $tasks->lanes[$index];
|
||||
|
||||
/* 附加工作流字段。 */
|
||||
foreach($extendFields as $extendField)
|
||||
{
|
||||
$data[$index]->{$extendField->field} = $tasks->{$extendField->field}[$index];
|
||||
if(is_array($data[$index]->{$extendField->field})) $data[$index]->{$extendField->field} = join(',', $data[$index]->{$extendField->field});
|
||||
|
||||
$data[$index]->{$extendField->field} = htmlSpecialString($data[$index]->{$extendField->field});
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建前检查必填项
|
||||
* Check required fields before batch create tasks.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param object[] $data
|
||||
* @access protected
|
||||
* @return object[]|false
|
||||
*/
|
||||
protected function checkRequired4BatchCreate(object $execution, array $data): array|false
|
||||
{
|
||||
/* 设置必填项。 */
|
||||
$requiredFields = ',' . $this->config->task->create->requiredFields . ',';
|
||||
if($execution->lifetime == 'ops' or $execution->attribute == 'request' or $execution->attribute == 'review') $requiredFields = str_replace(',story,', ',', $requiredFields);
|
||||
$requiredFields = trim($requiredFields, ',');
|
||||
|
||||
/* check data. */
|
||||
foreach($data as $i => $task)
|
||||
{
|
||||
/* 检查任务是否开启了起止日期必填的配置(limitTaskDate)。 */
|
||||
if(!empty($this->config->limitTaskDate))
|
||||
{
|
||||
$this->checkEstStartedAndDeadline($execution->id, $task->estStarted, $task->deadline);
|
||||
if(dao::isError()) return false;
|
||||
}
|
||||
|
||||
/* 检查任务截止日期是否为空以及是否小于预计开始日期。 */
|
||||
if(!helper::isZeroDate($task->deadline) and $task->deadline < $task->estStarted)
|
||||
{
|
||||
dao::$errors['message'][] = $this->lang->task->error->deadlineSmall;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 检查任务预计是否为数字类型。 */
|
||||
if($task->estimate and !preg_match("/^[0-9]+(.[0-9]{1,3})?$/", $task->estimate))
|
||||
{
|
||||
dao::$errors['message'][] = $this->lang->task->error->estimateNumber;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 验证必填字段。 */
|
||||
$requiredFields = array_filter(explode(',', $requiredFields));
|
||||
foreach($requiredFields as $field)
|
||||
{
|
||||
if(empty($task->$field))
|
||||
{
|
||||
dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->task->$field);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if($task->estimate) $task->estimate = (float)$task->estimate;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拆分已消耗的任务。
|
||||
* Split the consumed task.
|
||||
*
|
||||
* @param object $parentTask
|
||||
* @access protected
|
||||
* @return bool
|
||||
*/
|
||||
protected function splitConsumedTask(object $parentTask): bool
|
||||
{
|
||||
$clonedTask = clone $parentTask;
|
||||
$clonedTask->parent = $parentTask->id;
|
||||
unset($clonedTask->id);
|
||||
$this->dao->insert(TABLE_TASK)->data($clonedTask)->autoCheck()->exec();
|
||||
if(dao::isError()) return false;
|
||||
|
||||
$clonedTaskID = $this->dao->lastInsertID();
|
||||
|
||||
$this->dao->update(TABLE_EFFORT)->set('objectID')->eq($clonedTaskID)
|
||||
->where('objectID')->eq($parentTask->id)
|
||||
->andWhere('objectType')->eq('task')
|
||||
->exec();
|
||||
if(dao::isError()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拆分之后更新父任务的parent、lastEditedBy、lastEditedDate字段。
|
||||
* Update the parent's parent, lastEditedBy, and lastEditedDate fields after split.
|
||||
*
|
||||
* @param int $parentID
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function updateParentAfterSplit(int $parentID): void
|
||||
{
|
||||
$task = new stdclass();
|
||||
$task->parent = '-1';
|
||||
$task->lastEditedBy = $this->app->user->account;
|
||||
$task->lastEditedDate = helper::now();
|
||||
$this->dao->update(TABLE_TASK)->data($task)->where('id')->eq($parentID)->exec();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ su('admin');
|
||||
|
||||
/**
|
||||
|
||||
title=taskModel->getTaskEstimate();
|
||||
title=taskModel->getTaskEfforts();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
@@ -19,4 +19,4 @@ $waitstart = array('assignedTo' => 'user92','consumed' => '10');
|
||||
|
||||
$task = new taskTest();
|
||||
$task->startTest($taskID,$waitstart);
|
||||
r($task->getTaskEstimateTest($taskID)) && p('0:task') && e('55'); // 查看任务预计
|
||||
r($task->getTaskEffortsTest($taskID)) && p('0:task') && e('55'); // 查看任务预计
|
||||
@@ -652,7 +652,7 @@ class taskTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get task estimate.
|
||||
* Test get task efforts.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param string $account
|
||||
@@ -660,9 +660,9 @@ class taskTest
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function getTaskEstimateTest($taskID, $account = '', $append = '')
|
||||
public function getTaskEffortsTest($taskID, $account = '', $append = '')
|
||||
{
|
||||
$object = $this->objectModel->getTaskEstimate($taskID, $account, $append);
|
||||
$object = $this->objectModel->getTaskEfforts($taskID, $account, $append);
|
||||
if(dao::isError())
|
||||
{
|
||||
$error = dao::getError();
|
||||
|
||||
+774
-1
@@ -11,6 +11,16 @@ declare(strict_types=1);
|
||||
*/
|
||||
class taskZen extends task
|
||||
{
|
||||
|
||||
/**
|
||||
* 执行数组。
|
||||
* Execution's [id] => 'name' pairs.
|
||||
*
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $executionPairs = array();
|
||||
|
||||
/**
|
||||
* 准备创建数据。
|
||||
* Prepare edit data.
|
||||
@@ -53,7 +63,6 @@ class taskZen extends task
|
||||
->setIF($oldTask->parent < 0, 'left', $oldTask->left)
|
||||
|
||||
->setIF($oldTask->name != $postData->name || $oldTask->estStarted != $postData->estStarted || $oldTask->deadline != $postData->deadline, 'version', $oldTask->version + 1)
|
||||
|
||||
->add('lastEditedDate', $now)
|
||||
->stripTags($this->config->task->editor->edit['id'], $this->config->allowedTags)
|
||||
->join('mailto', ',')
|
||||
@@ -255,4 +264,768 @@ class taskZen extends task
|
||||
$this->view->users = $this->loadModel('user')->getPairs();
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置任务二级导航。
|
||||
* Set the task secondary navigation.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @access protected
|
||||
* @return int
|
||||
*/
|
||||
protected function setMenu(int $executionID): int
|
||||
{
|
||||
$execution = $this->execution->getById($executionID);
|
||||
|
||||
/* If the admin denied modification of closed executions, only query not closed executions. */
|
||||
$queryMode = $execution && common::canModify('execution', $execution) ? 'all' : 'noclosed';
|
||||
|
||||
/* Get executions the current user can access. */
|
||||
$this->executionPairs = $this->execution->getPairs(0, 'all', $queryMode);
|
||||
|
||||
/* Call checkAccess method to judge the user can access the execution or not, if not return the first one he can access. */
|
||||
$executionID = $this->execution->checkAccess($executionID, $this->executionPairs);
|
||||
|
||||
/* Set Menu. */
|
||||
$this->execution->setMenu($executionID);
|
||||
if($this->app->tab == 'project') $this->project->setMenu($this->session->project);
|
||||
|
||||
return $executionID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化任务信息。
|
||||
* Initialize task information.
|
||||
*
|
||||
* @param int $storyID
|
||||
* @param int $moduleID
|
||||
* @param int $taskID
|
||||
* @param int $todoID
|
||||
* @param int $bugID
|
||||
* @access protected
|
||||
* @return object
|
||||
*/
|
||||
protected function initTaskData(int $storyID, int $moduleID, int $taskID, int $todoID, int $bugID): object
|
||||
{
|
||||
/* Init task data. */
|
||||
$task = new stdClass();
|
||||
$task->module = 0;
|
||||
$task->mode = '';
|
||||
$task->assignedTo = '';
|
||||
$task->name = '';
|
||||
$task->story = 0;
|
||||
$task->type = '';
|
||||
$task->pri = 3;
|
||||
$task->estimate = '';
|
||||
$task->desc = '';
|
||||
$task->estStarted = null;
|
||||
$task->deadline = null;
|
||||
$task->mailto = '';
|
||||
$task->color = '';
|
||||
|
||||
return $this->setTaskInfoByObjectID($task, $storyID, $moduleID, $taskID, $todoID, $bugID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过传入的对象ID初始化任务信息。
|
||||
* Set task information by incoming object id.
|
||||
*
|
||||
* @param object $task
|
||||
* @param int $storyID
|
||||
* @param int $moduleID
|
||||
* @param int $taskID
|
||||
* @param int $todoID
|
||||
* @param int $bugID
|
||||
* @access protected
|
||||
* @return object
|
||||
*/
|
||||
protected function setTaskInfoByObjectID(object $task, int $storyID, int $moduleID, int $taskID, int $todoID, int $bugID): object
|
||||
{
|
||||
$task->module = $moduleID;
|
||||
|
||||
/* If exist task, copy task information by task id. */
|
||||
if($taskID > 0)
|
||||
{
|
||||
$task = $this->task->getByID($taskID);
|
||||
$executionID = $task->execution;
|
||||
|
||||
/* Emptying consumed hours when copy task. */
|
||||
if($task->mode == 'multi')
|
||||
{
|
||||
foreach($task->team as $teamMember) $teamMember->consumed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* If exist todo, copy todo information by todo id. */
|
||||
if($todoID > 0)
|
||||
{
|
||||
$todo = $this->loadModel('todo')->getById($todoID);
|
||||
$task->name = $todo->name;
|
||||
$task->pri = $todo->pri;
|
||||
$task->desc = $todo->desc;
|
||||
}
|
||||
|
||||
/* If exist bug, copy bug information by bug id. */
|
||||
if($bugID > 0)
|
||||
{
|
||||
$bug = $this->loadModel('bug')->getById($bugID);
|
||||
$task->name = $bug->title;
|
||||
$task->pri = !empty($bug->pri) ? $bug->pri : '3';
|
||||
$task->assignedTo = array($bug->assignedTo);
|
||||
}
|
||||
|
||||
/* If exist story, copy story module by story id. */
|
||||
if($storyID)
|
||||
{
|
||||
$task->story = $storyID;
|
||||
$task->module = $this->dao->findByID($storyID)->from(TABLE_STORY)->fetch('module');
|
||||
}
|
||||
elseif(!$moduleID)
|
||||
{
|
||||
$task->module = (int)$this->cookie->lastTaskModule;
|
||||
}
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示看板相关变量。
|
||||
* Show related variable about the Kanban.
|
||||
*
|
||||
* @param array $output
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function showKanbanRelatedVars(array $output): void
|
||||
{
|
||||
$this->loadModel('kanban');
|
||||
|
||||
$regionID = isset($output['regionID']) ? (int)$output['regionID'] : 0;
|
||||
$laneID = isset($output['laneID']) ? (int)$output['laneID'] : 0;
|
||||
$regionPairs = $this->kanban->getRegionPairs($execution->id, 0, 'execution');
|
||||
$regionID = $regionID ? $regionID : key($regionPairs);
|
||||
$lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'task');
|
||||
$laneID = $laneID ? $laneID : key($lanePairs);
|
||||
|
||||
$this->view->regionID = $regionID;
|
||||
$this->view->laneID = $laneID;
|
||||
$this->view->regionPairs = $regionPairs;
|
||||
$this->view->lanePairs = $lanePairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置地盘待处理区块的ID。
|
||||
* Set the ID of the block to be processed on the my.
|
||||
*
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function setAssignedToMeBlockID(): void
|
||||
{
|
||||
/* Get block id of assinge to me. */
|
||||
$blockID = 0;
|
||||
if(isonlybody())
|
||||
{
|
||||
$blockID = $this->dao->select('id')->from(TABLE_BLOCK)
|
||||
->where('block')->eq('assingtome')
|
||||
->andWhere('module')->eq('my')
|
||||
->andWhere('account')->eq($this->app->user->account)
|
||||
->orderBy('order_desc')
|
||||
->fetch('id');
|
||||
}
|
||||
|
||||
$this->view->blockID = $blockID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行相关数据。
|
||||
* Set execution related data.
|
||||
*
|
||||
* @param object $execution
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function setExecutionData(object $execution): void
|
||||
{
|
||||
$projectID = $execution ? $execution->project : 0;
|
||||
$lifetimeList = array();
|
||||
$attributeList = array();
|
||||
if(!empty($projectID))
|
||||
{
|
||||
$executionKey = 0;
|
||||
$executions = $this->execution->getByProject($projectID, 'all', 0, true);
|
||||
$executionList = $this->execution->getByIdList(array_keys($executions));
|
||||
foreach($executionList as $executionItem)
|
||||
{
|
||||
if(!common::canModify('execution', $executionItem)) $executionKey = $executionItem->id;
|
||||
if($executionKey) unset($executions[$executionKey]);
|
||||
if(!$executionKey) continue;
|
||||
|
||||
$lifetimeList[$executionKey] = $executionItem->lifetime;
|
||||
$attributeList[$executionKey] = $executionItem->attribute;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$executions = $this->executionPairs;
|
||||
$executionList = $this->execution->getByIdList(array_keys($executions));
|
||||
foreach($executionList as $executionItem)
|
||||
{
|
||||
$executionKey = $executionItem->id;
|
||||
$lifetimeList[$executionKey] = $executionItem->lifetime;
|
||||
$attributeList[$executionKey] = $executionItem->attribute;
|
||||
}
|
||||
}
|
||||
|
||||
$this->view->projectID = $projectID;
|
||||
$this->view->executions = $executions;
|
||||
$this->view->lifetimeList = $lifetimeList;
|
||||
$this->view->attributeList = $attributeList;
|
||||
$this->view->productID = $this->loadModel('product')->getProductIDByProject($projectID);
|
||||
$this->view->features = $this->execution->getExecutionFeatures($execution);
|
||||
$this->view->users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
|
||||
$this->view->members = $this->user->getTeamMemberPairs($execution->id, 'execution', 'nodeleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理创建任务的请求数据。
|
||||
* Process the request data for the creation task.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param object $formData
|
||||
* @access private
|
||||
* @return object
|
||||
*/
|
||||
private function prepareTask4Create(int $executionID, object $formData): object
|
||||
{
|
||||
$rawData = $formData->rawdata;
|
||||
$execution = $this->dao->findById($rawData->execution)->from(TABLE_EXECUTION)->fetch();
|
||||
$team = !empty($rawData->team) ? array_filter($rawData->team) : array();
|
||||
$task = $formData->setDefault('execution', $executionID)
|
||||
->setDefault('project', $this->task->getProjectID($executionID))
|
||||
->setIF($rawData->estimate != false, 'left', $rawData->estimate)
|
||||
->setIF(isset($rawData->story), 'storyVersion', isset($rawData->story) ? $this->loadModel('story')->getVersion($rawData->story) : 0)
|
||||
->setIF(empty($rawData->multiple) || count($team) < 1, 'mode', '')
|
||||
->setIF($execution and ($execution->lifetime == 'ops' or in_array($execution->attribute, array('request', 'review'))), 'story', 0)
|
||||
->stripTags($this->config->task->editor->create['id'], $this->config->allowedTags)
|
||||
->join('mailto', ',')
|
||||
->get();
|
||||
|
||||
/* Processing image link. */
|
||||
return $this->loadModel('file')->processImgURL($task, $this->config->task->editor->create['id'], $rawData->uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查传入的创建数据是否符合要求。
|
||||
* Check if the incoming creation data meets the requirements.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param float $estimate
|
||||
* @param string $estStarted
|
||||
* @param string $deadline
|
||||
* @access private
|
||||
* @return bool
|
||||
*/
|
||||
private function checkCreate(int $executionID, float $estimate, string $estStarted, string $deadline): bool
|
||||
{
|
||||
/* Check if the estimate is positive. */
|
||||
if($estimate < 0)
|
||||
{
|
||||
dao::$errors['estimate'] = $this->lang->task->error->recordMinus;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the task start and end date must be between the execution start and end date, check if the task start and end date accord with the conditions. */
|
||||
if(!empty($this->config->limitTaskDate))
|
||||
{
|
||||
$this->task->checkEstStartedAndDeadline($executionID, $estStarted, $deadline);
|
||||
if(dao::isError()) return false;
|
||||
}
|
||||
|
||||
/* Check start and end date. */
|
||||
if(!helper::isZeroDate($deadline) and $estStarted > $deadline)
|
||||
{
|
||||
dao::$errors['deadline'] = $this->lang->task->error->deadlineSmall;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查规定时间内是否创建了同名任务。
|
||||
* Check whether a task with the same name is created within the specified time.
|
||||
*
|
||||
* @param object $task
|
||||
* @access private
|
||||
* @return int
|
||||
*/
|
||||
private function checkDuplicateName($task): int
|
||||
{
|
||||
/* Check duplicate task. */
|
||||
if($task->type != 'affair' and $task->name)
|
||||
{
|
||||
$result = $this->loadModel('common')->removeDuplicate('task', $task, "execution={$task->execution} and story=" . (int)$task->story . (isset($task->feedback) ? " and feedback=" . (int)$task->feedback : ''));
|
||||
if($result['stop']) return zget($result, 'duplicate', 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理关联需求的测试子任务的请求数据。
|
||||
* Process request data for test subtasks related to stories.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param object $formData
|
||||
* @access private
|
||||
* @return array|bool
|
||||
*/
|
||||
private function prepareTestTasks4Create(int $executionID, object $formData): array|bool
|
||||
{
|
||||
/* Set data for the type of test task that has linked stories. */
|
||||
$testTasks = array();
|
||||
$rawData = $formData->rawdata;
|
||||
foreach($rawData->testStory as $i => $storyID)
|
||||
{
|
||||
if(empty($storyID)) continue;
|
||||
|
||||
/* Process the ditto option as a concrete value. */
|
||||
$estStarted = !isset($rawData->testEstStarted[$i]) || (isset($rawData->estStartedDitto[$i]) && $rawData->estStartedDitto[$i] == 'on') ? $estStarted : $rawData->testEstStarted[$i];
|
||||
$deadline = !isset($rawData->testDeadline[$i]) || (isset($rawData->deadlineDitto[$i]) && $rawData->deadlineDitto[$i] == 'on') ? $deadline : $rawData->testDeadline[$i];
|
||||
$assignedTo = !isset($rawData->testAssignedTo[$i]) || $rawData->testAssignedTo[$i] == 'ditto' ? $assignedTo : $rawData->testAssignedTo[$i];
|
||||
|
||||
/* Set task data. */
|
||||
$task = new stdclass();
|
||||
$task->execution = $executionID;
|
||||
$task->story = $storyID;
|
||||
$task->pri = $rawData->testPri[$i];
|
||||
$task->estStarted = $estStarted;
|
||||
$task->deadline = $deadline;
|
||||
$task->assignedTo = $assignedTo;
|
||||
$task->estimate = (float)$rawData->testEstimate[$i];
|
||||
$task->left = (float)$rawData->testEstimate[$i];
|
||||
|
||||
$testTasks[$storyID] = $task;
|
||||
}
|
||||
return $testTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查关联需求的测试类型任务数据格式是否符合要求。
|
||||
* Check if the test type task data format of the linked stories meets the requirements.
|
||||
*
|
||||
* @param object[] $tasks
|
||||
* @access private
|
||||
* @return bool
|
||||
*/
|
||||
private function checkTestTasks(array $tasks): bool
|
||||
{
|
||||
foreach($tasks as $task)
|
||||
{
|
||||
/* Check if the estimate is positive. */
|
||||
if($task->estimate < 0)
|
||||
{
|
||||
dao::$errors[] = "ID: {$task->story} {$this->lang->task->error->recordMinus}";
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If the task start and end date must be between the execution start and end date, check if the task start and end date accord with the conditions. */
|
||||
if(!empty($this->config->limitTaskDate))
|
||||
{
|
||||
$this->checkEstStartedAndDeadline($task->execuiton, $task->estStarted, $task->deadline);
|
||||
if(dao::isError())
|
||||
{
|
||||
foreach(dao::getError() as $field => $error)
|
||||
{
|
||||
dao::$errors[] = "ID: {$task->story} {$error}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check start and end date. */
|
||||
if($task->estStarted > $task->deadline)
|
||||
{
|
||||
dao::$errors[] = "ID: {$task->story} {$this->lang->task->error->deadlineSmall}";
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Checking the required fields of task data. */
|
||||
$this->dao->insert(TABLE_TASK)->data($task)->batchCheck($this->config->task->create->requiredFields, 'notempty');
|
||||
if(dao::isError())
|
||||
{
|
||||
foreach(dao::getError() as $field => $error)
|
||||
{
|
||||
dao::$errors[] = "ID: {$task->story} {$error}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理创建任务后的返回信息。
|
||||
* The information returned after processing the creation task.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param object $execution
|
||||
* @param string $afterChoice continueAdding|toTaskList|toStoryList
|
||||
* @access protected
|
||||
* @return array|bool
|
||||
*/
|
||||
protected function responseAfterCreate(object $task, object $execution, string $afterChoice): array|bool
|
||||
{
|
||||
/* If there is a database error, return the error message. */
|
||||
if(dao::isError()) return array('result' => 'fail', 'message' => dao::getError());
|
||||
|
||||
/* Return task id when call the API. */
|
||||
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $taskID);
|
||||
|
||||
$taskID = $task->id;
|
||||
$response['result'] = 'success';
|
||||
$response['message'] = $this->lang->saveSuccess;
|
||||
|
||||
/* Send Webhook notifications. */
|
||||
$message = $this->executeHooks($taskID);
|
||||
if($message) $response['message'] = $message;
|
||||
|
||||
/* Processing the return information of pop-up windows. */
|
||||
if(isonlybody())
|
||||
{
|
||||
/* If it is Kanban execution, refresh the Kanban statically through callback. */
|
||||
if($this->app->tab == 'execution' or $this->config->vision == 'lite')
|
||||
{
|
||||
$kanbanData = $this->getKanbanData($execution);
|
||||
$response['closeModal'] = true;
|
||||
$response['callback'] = $execution->type == 'kanban' ? "parent.updateKanban($kanbanData, 0)" : "parent.updateKanban(\"task\", $kanbanData)";
|
||||
return $response;
|
||||
}
|
||||
$response['locate'] = 'parent';
|
||||
return $response;
|
||||
}
|
||||
|
||||
/* Locate the browser. */
|
||||
if($this->app->getViewType() == 'xhtml')
|
||||
{
|
||||
$response['locate'] = $this->createLink('task', 'view', "taskID=$taskID", 'html');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/* Process the return information for selecting a jump after creation. */
|
||||
return $this->getLocateAfterCreate($task, $execution->id, $afterChoice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理创建后选择跳转的返回信息。
|
||||
* Process the return information for selecting a jump after creation.
|
||||
*
|
||||
* @param object $task
|
||||
* @param int $executionID
|
||||
* @param string $afterChoice
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getLocateAfterCreate(object $task, int $executionID, string $afterChoice): array
|
||||
{
|
||||
/* Set the universal return value. */
|
||||
$response['result'] = 'success';
|
||||
$response['message'] = $this->lang->saveSuccess;
|
||||
$response['locate'] = $this->createLink('execution', 'browse', "executionID=$executionID&tab=task");
|
||||
|
||||
/* Set the response to continue adding task to story. */
|
||||
$executionID = $task->execution;
|
||||
if($afterChoice == 'continueAdding')
|
||||
{
|
||||
$storyID = $task->story ? $task->story : 0;
|
||||
$moduleID = $task->module ? $task->module : 0;
|
||||
$response['message'] = $this->lang->task->successSaved . $this->lang->task->afterChoices['continueAdding'];
|
||||
$response['locate'] = $this->createLink('task', 'create', "executionID=$executionID&storyID=$storyID&moduleID=$moduleID");
|
||||
}
|
||||
/* Set the response to return task list. */
|
||||
elseif($afterChoice == 'toTaskList')
|
||||
{
|
||||
setcookie('moduleBrowseParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
$response['locate'] = $this->createLink('execution', 'task', "executionID=$executionID&status=unclosed¶m=0&orderBy=id_desc");
|
||||
}
|
||||
/* Set the response to return story list. */
|
||||
elseif($afterChoice == 'toStoryList')
|
||||
{
|
||||
$response['locate'] = $this->createLink('execution', 'story', "executionID=$executionID");
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$projectID = $this->execution->getProjectID($executionID);
|
||||
$response['locate'] = $this->createLink('projectstory', 'story', "projectID=$projectID");
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示需求相关变量。
|
||||
* Show requirements related variables.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function showStoryVars(int $executionID): void
|
||||
{
|
||||
$stories = $this->story->getExecutionStoryPairs($executionID, 0, 'all', '', '', 'active');
|
||||
$testStoryIdList = $this->loadModel('story')->getTestStories(array_keys($stories), $executionID);
|
||||
$testStories = array();
|
||||
foreach($stories as $testStoryID => $storyTitle)
|
||||
{
|
||||
if(empty($testStoryID) or isset($testStoryIdList[$testStoryID])) continue;
|
||||
$testStories[$testStoryID] = $storyTitle;
|
||||
}
|
||||
$this->view->testStories = $testStories;
|
||||
$this->view->testStoryIdList = $testStoryIdList;
|
||||
$this->view->stories = $stories;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示创建任务的相关变量。
|
||||
* Show the variables associated with the creation task.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param int $storyID
|
||||
* @param int $moduleID
|
||||
* @param int $taskID
|
||||
* @param int $todoID
|
||||
* @param int $bugID
|
||||
* @param string $extra
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function showCreateVars(object $execution, int $storyID, int $moduleID, int $taskID, int $todoID, int $bugID, string $extra)
|
||||
{
|
||||
$extra = str_replace(array(',', ' '), array('&', ''), $extra);
|
||||
parse_str($extra, $output);
|
||||
|
||||
$executionID = $execution->id;
|
||||
$task = $this->initTaskData($storyID, $moduleID, $taskID, $todoID, $bugID);
|
||||
|
||||
$showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
|
||||
$moduleOptionMenu = $this->tree->getTaskOptionMenu($executionID, 0, 0, $showAllModule ? 'allModule' : '');
|
||||
if(!$storyID and !isset($moduleOptionMenu[$task->module])) $task->module = 0;
|
||||
|
||||
$this->setAssignedToMeBlockID();
|
||||
$this->setExecutionData($execution);
|
||||
$this->showStoryVars($executionID);
|
||||
if($execution->type == 'kanban') $this->taskZen->showKanbanRelatedVars($output);
|
||||
|
||||
/* Set Custom fields. */
|
||||
foreach(explode(',', $this->config->task->customCreateFields) as $field) $customFields[$field] = $this->lang->task->$field;
|
||||
|
||||
$this->view->title = $execution->name . $this->lang->colon . $this->lang->task->create;
|
||||
$this->view->customFields = $customFields;
|
||||
$this->view->showAllModule = $showAllModule;
|
||||
$this->view->moduleOptionMenu = $moduleOptionMenu;
|
||||
$this->view->showFields = $this->config->task->custom->createFields;
|
||||
$this->view->gobackLink = (isset($output['from']) and $output['from'] == 'global') ? $this->createLink('execution', 'task', "executionID=$executionID") : '';
|
||||
$this->view->execution = $execution;
|
||||
$this->view->task = $task;
|
||||
$this->view->storyID = $storyID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备创建任务前的数据信息。
|
||||
* Prepare the data information before creating the task.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param float $estimate
|
||||
* @param string $estStarted
|
||||
* @param string $deadline
|
||||
* @param bool $selectTestStory
|
||||
* @access protected
|
||||
* @return bool|array
|
||||
*/
|
||||
protected function prepareCreate(int $executionID, float $estimate, string $estStarted, string $deadline, bool $selectTestStory): bool|array
|
||||
{
|
||||
/* Check if the incoming creation data meets the requirements. */
|
||||
$result = $this->checkCreate($executionID, $estimate, $estStarted, $deadline);
|
||||
if(!$result) return false;
|
||||
|
||||
/* Process the request data for the creation task. */
|
||||
$formData = form::data($this->config->task->form->create);
|
||||
$task = $this->prepareTask4Create($executionID, $formData);
|
||||
|
||||
/* Prepare to create the data for the test subtask and to check the data format. */
|
||||
$testTasks = array();
|
||||
if($selectTestStory and $task->type == 'test')
|
||||
{
|
||||
$testTasks = $this->prepareTestTasks4Create($executionID, $formData);
|
||||
$result = $this->checkTestTasks($testTasks);
|
||||
if(!$result) return false;
|
||||
}
|
||||
|
||||
/* Check whether a task with the same name is created within the specified time. */
|
||||
$existTaskID = $this->checkDuplicateName($task);
|
||||
|
||||
return array($task, $testTasks, $existTaskID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据一级菜单设置任务模块的导航。
|
||||
* According to the main menu, set the navbar of the task module.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param int $projectID
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function setMenuByTab(int $executionID, int $projectID = 0): void
|
||||
{
|
||||
$this->execution->setMenu($executionID);
|
||||
|
||||
if($this->app->tab == 'project') $this->loadModel('project')->setMenu($projectID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为表单获取自定义字段。
|
||||
* Get task's custom fields for form.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param string $action
|
||||
* @access protected
|
||||
* return array
|
||||
*/
|
||||
protected function getCustomFields(object $execution, string $action): array
|
||||
{
|
||||
/* 设置自定义字段列表。 */
|
||||
$customFormField = 'custom' . ucfirst($action). 'Fields';
|
||||
foreach(explode(',', $this->config->task->{$customFormField}) as $field)
|
||||
{
|
||||
if($execution->type == 'stage' and strpos('estStarted,deadline', $field) !== false) continue;
|
||||
$customFields[$field] = $this->lang->task->$field;
|
||||
}
|
||||
|
||||
/* 设置已勾选的自定义字段。 */
|
||||
$showFields = $this->config->task->custom->{$action . 'Fields'};
|
||||
if($execution->lifetime == 'ops' or $execution->attribute == 'request' or $execution->attribute == 'review')
|
||||
{
|
||||
unset($customFields['story']);
|
||||
$showFields = str_replace(',story,', ',', ",$showFields,");
|
||||
$showFields = trim($showFields, ',');
|
||||
}
|
||||
|
||||
return array($customFields, $showFields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建批量创建任务的表单数据。
|
||||
* Build batch create form.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param int $storyID
|
||||
* @param int $moduleID
|
||||
* @param int $taskID
|
||||
* @param array $output
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function buildBatchCreateForm(object $execution, int $storyID, int $moduleID, int $taskID, array $output): void
|
||||
{
|
||||
/* 获取区域和泳道下拉数据,并设置区域和泳道的默认值。*/
|
||||
if($execution->type == 'kanban') $this->taskZen->showKanbanRelatedVars($output);
|
||||
|
||||
/* 任务拆解。 */
|
||||
if($taskID)
|
||||
{
|
||||
$task = $this->dao->findById($taskID)->from(TABLE_TASK)->fetch();
|
||||
$this->view->parentTitle = $task->name;
|
||||
$this->view->parentPri = $task->pri;
|
||||
}
|
||||
|
||||
/* 需求批量分解任务。 */
|
||||
$story = $this->story->getByID($storyID);
|
||||
$moduleID = $story ? $story->module : $moduleID;
|
||||
$moduleParam = $story ? $moduleID : 0;
|
||||
|
||||
/* 获取模块下拉数据。 */
|
||||
$showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
|
||||
$modules = $this->loadModel('tree')->getTaskOptionMenu($execution->id, 0, 0, $showAllModule ? 'allModule' : '');
|
||||
$stories = $this->story->getExecutionStoryPairs($execution->id, 0, 'all', $moduleParam, 'short', 'active');
|
||||
|
||||
/* Set Custom. */
|
||||
list($customFields, $showFields) = $this->getCustomFields($execution, 'batchCreate');
|
||||
|
||||
$this->view->title = $execution->name . $this->lang->colon . $this->lang->task->batchCreate;
|
||||
$this->view->execution = $execution;
|
||||
$this->view->modules = $modules;
|
||||
$this->view->parent = $taskID;
|
||||
$this->view->storyID = $storyID;
|
||||
$this->view->story = $story;
|
||||
$this->view->moduleID = $moduleID;
|
||||
$this->view->stories = $stories;
|
||||
$this->view->storyTasks = $this->task->getStoryTaskCounts(array_keys($stories), $execution->id);
|
||||
$this->view->members = $this->loadModel('user')->getTeamMemberPairs($execution->id, 'execution', 'nodeleted');
|
||||
$this->view->taskConsumed = isset($task) ? $task->consumed : 0;
|
||||
$this->view->customFields = $customFields;
|
||||
$this->view->showFields = $showFields;
|
||||
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取重定向链接。
|
||||
* Get redirected link.
|
||||
*
|
||||
* @param object $execution
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function getRedirectedLink(object $execution): string
|
||||
{
|
||||
if($this->app->tab == 'my')
|
||||
{
|
||||
$link = $this->createLink('my', 'work', 'mode=task');
|
||||
}
|
||||
elseif($this->app->tab == 'project' and $execution->multiple)
|
||||
{
|
||||
$link = $this->createLink('project', 'execution', "browseType=all&projectID={$execution->project}");
|
||||
}
|
||||
else
|
||||
{
|
||||
$link = $this->createLink('execution', 'browse', "executionID=$execution->id");
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务的数据更新之后,获取对应看板的数据。
|
||||
* Get R&D kanban's or task kanban's data after task's data is updated.
|
||||
*
|
||||
* @param object $execution
|
||||
* @access protected
|
||||
* @return string
|
||||
*/
|
||||
protected function getKanbanData(object $execution): string
|
||||
{
|
||||
$this->loadModel('kanban');
|
||||
|
||||
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
|
||||
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
|
||||
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
|
||||
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
|
||||
|
||||
/* 处理专业研发看板。 */
|
||||
if($execution->type == 'kanban')
|
||||
{
|
||||
$kanbanData = $this->kanban->getRDKanban($execution->id, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
|
||||
$kanbanData = json_encode($kanbanData);
|
||||
|
||||
return $kanbanData;
|
||||
}
|
||||
|
||||
/* 处理任务看板。 */
|
||||
$kanbanData = $this->kanban->getExecutionKanban($execution->id, $execLaneType, $execGroupBy, $taskSearchValue);
|
||||
$kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData);
|
||||
$kanbanData = json_encode($kanbanData[$kanbanType]);
|
||||
|
||||
return $kanbanData;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -82,8 +82,8 @@ class todoTao extends todoModel
|
||||
{
|
||||
$this->dao->insert(TABLE_TODO)->data($todo)
|
||||
->autoCheck()
|
||||
->checkIF(!in_array($todo->type, $this->config->todo->moduleList), $this->config->todo->create->requiredFields, 'notempty')
|
||||
->checkIF(in_array($todo->type, $this->config->todo->moduleList) && $todo->objectID == 0, 'objectID', 'notempty')
|
||||
->check($this->config->todo->create->requiredFields, 'notempty')
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, $this->config->todo->moduleList), 'objectID', 'notempty')
|
||||
->exec();
|
||||
|
||||
return (int)$this->dao->lastInsertID();
|
||||
@@ -102,8 +102,8 @@ class todoTao extends todoModel
|
||||
{
|
||||
$this->dao->update(TABLE_TODO)->data($todo)
|
||||
->autoCheck()
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, array('custom', 'feedback')), $this->config->todo->edit->requiredFields, 'notempty')
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, $this->config->todo->moduleList) && $todo->objectID == 0, 'objectID', 'notempty')
|
||||
->check($this->config->todo->edit->requiredFields, 'notempty')
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, $this->config->todo->moduleList), 'objectID', 'notempty')
|
||||
->where('id')->eq($todoID)
|
||||
->exec();
|
||||
|
||||
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
su('admin');
|
||||
|
||||
function initData ()
|
||||
{
|
||||
zdTable('todo')->gen(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试 todoTao::insert();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 正常插入待办数据 @2
|
||||
|
||||
- 正常插入task类型待办数据 @3
|
||||
|
||||
- 插入没有名称的待办数据 @0
|
||||
|
||||
- 插入bug类型待办,没有bugID的情况 @0
|
||||
|
||||
*/
|
||||
|
||||
initData();
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('todo');
|
||||
|
||||
$todo = new stdClass();
|
||||
$todo->account = 'admin';
|
||||
$todo->date = date('Y-m-d');
|
||||
$todo->begin = '1000';
|
||||
$todo->end = '1400';
|
||||
$todo->type = 'custom';
|
||||
$todo->name = '我的待办名字';
|
||||
$todo->desc = '我的待办描述';
|
||||
$todo->status = 'wait';
|
||||
|
||||
$todoNoName = clone $todo;
|
||||
$todoNoName->name = '';
|
||||
|
||||
$todoBug = clone $todo;
|
||||
$todoBug->type = 'bug';
|
||||
$todoBug->name = 'BUG待办';
|
||||
$todoBug->objectID = 0;
|
||||
|
||||
$todoTask = clone $todo;
|
||||
$todoTask->type = 'task';
|
||||
$todoTask->name = 'TASK待办';
|
||||
$todoTask->objectID = 1;
|
||||
|
||||
r($tester->todo->insert($todo)) && p() && e(2); // 正常插入待办数据
|
||||
r($tester->todo->insert($todoTask)) && p() && e(3); // 正常插入task类型待办数据
|
||||
r($tester->todo->insert($todoNoName)) && p() && e(0); // 插入没有名称的待办数据
|
||||
r($tester->todo->insert($todoBug)) && p() && e(0); // 插入bug类型待办,没有bugID的情况
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
su('admin');
|
||||
|
||||
function initData ()
|
||||
{
|
||||
zdTable('todo')->gen(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试 todoTao::updateRow();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 正常更新待办数据 @1
|
||||
|
||||
- 正常更新task类型待办数据 @1
|
||||
|
||||
- 更新没有名称的待办数据 @0
|
||||
|
||||
- 更新bug类型待办,没有bugID的情况 @0
|
||||
|
||||
*/
|
||||
|
||||
initData();
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('todo');
|
||||
|
||||
$todo = new stdClass();
|
||||
$todo->account = 'admin';
|
||||
$todo->date = date('Y-m-d');
|
||||
$todo->begin = '1000';
|
||||
$todo->end = '1400';
|
||||
$todo->type = 'custom';
|
||||
$todo->name = '我的待办名字';
|
||||
$todo->desc = '我的待办描述';
|
||||
$todo->status = 'wait';
|
||||
|
||||
$todoNoName = clone $todo;
|
||||
$todoNoName->name = '';
|
||||
|
||||
$todoBug = clone $todo;
|
||||
$todoBug->type = 'bug';
|
||||
$todoBug->name = 'BUG待办';
|
||||
$todoBug->objectID = 0;
|
||||
|
||||
$todoTask = clone $todo;
|
||||
$todoTask->type = 'task';
|
||||
$todoTask->name = 'TASK待办';
|
||||
$todoTask->objectID = 1;
|
||||
|
||||
r($tester->todo->updateRow(1, $todo)) && p() && e('1'); // 正常更新待办数据
|
||||
r($tester->todo->updateRow(2, $todoTask)) && p() && e('1'); // 正常更新task类型待办数据
|
||||
r($tester->todo->updateRow(3, $todoNoName)) && p() && e('0'); // 更新没有名称的待办数据
|
||||
r($tester->todo->updateRow(4, $todoBug)) && p() && e('0'); // 更新bug类型待办,没有bugID的情况
|
||||
Reference in New Issue
Block a user