* Split the function that create the task.

This commit is contained in:
tianshujie
2023-05-06 11:33:40 +08:00
parent 645a190dbf
commit c9c340d0e8
8 changed files with 1015 additions and 626 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;
+20
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);
+33 -298
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,298 +58,35 @@ 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();
}
@@ -1129,7 +864,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;
+259 -313
View File
@@ -14,278 +14,121 @@
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;
}
/**
* 计算多人任务工时。
* 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;
}
/**
* Create a batch task.
*
@@ -811,59 +654,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 +672,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 +802,7 @@ class taskModel extends model
}
elseif($effortID)
{
$efforts = $this->getTaskEstimate($taskID, '', $effortID);
$efforts = $this->getTaskEfforts($taskID, '', $effortID);
$prevTeam = null;
$thisTeam = null;
@@ -2342,7 +2132,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 +2482,7 @@ class taskModel extends model
}
/**
* Get task estimate.
* Get task efforts.
*
* @param int $taskID
* @param string $account
@@ -2701,7 +2491,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 +2732,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 +4016,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');
}
}
}
+83 -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,34 @@ 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, ',');
}
}
@@ -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();
+612 -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,606 @@ 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);
}
}