This commit is contained in:
caoyanyi
2023-05-06 13:31:34 +08:00
11 changed files with 1632 additions and 937 deletions
+3
View File
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
helper::import(dirname(__FILE__) . 'config/form.php');
$config->task = new stdclass();
$config->task->batchCreate = 10;
+44
View File
@@ -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
View File
@@ -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&param=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
View File
@@ -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
View File
@@ -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();
}
}
@@ -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'); // 查看任务预计
+3 -3
View File
@@ -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
View File
@@ -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&param=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
View File
@@ -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();
+58
View File
@@ -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的情况
+58
View File
@@ -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的情况