Merge branch 'master' of git.zcorp.cc:easycorp/zentaopms
This commit is contained in:
@@ -258,7 +258,7 @@ class classlist
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function toStr()
|
||||
public function toStr(): string
|
||||
{
|
||||
$names = array();
|
||||
foreach($this->list as $name => $toggle)
|
||||
@@ -268,7 +268,7 @@ class classlist
|
||||
$name = trim($name);
|
||||
if(!strlen($name)) continue;
|
||||
|
||||
$names[] = str_replace('.', '.\\', $name);
|
||||
$names[] = $name;
|
||||
}
|
||||
return implode(' ', $names);
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ class programplan extends control
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function edit(string $planID = 0, string $projectID = 0)
|
||||
public function edit(string $planID = '0', string $projectID = '0')
|
||||
{
|
||||
$planID = (int)$planID;
|
||||
$projectID = (int)$projectID;
|
||||
|
||||
+27
-24
@@ -1866,10 +1866,8 @@ class project extends control
|
||||
*/
|
||||
public function updateOrder()
|
||||
{
|
||||
$postData = form::data();
|
||||
$rawdata = $postData->rawdata;
|
||||
$idList = explode(',', trim($rawdata->projects, ','));
|
||||
$orderBy = $rawdata->orderBy;
|
||||
$idList = explode(',', trim($this->post->projects, ','));
|
||||
$orderBy = $this->post->orderBy;
|
||||
|
||||
if(strpos($orderBy, 'order') === false) return false;
|
||||
|
||||
@@ -1877,57 +1875,62 @@ class project extends control
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取项目白名单列表
|
||||
* Get white list personnel.
|
||||
*
|
||||
* @param int $projectID
|
||||
* @param string $module
|
||||
* @param string $from project|program|programProject
|
||||
* @param string $objectType
|
||||
* @param string $orderby
|
||||
* @param int $recTotal
|
||||
* @param int $recPerPage
|
||||
* @param int $pageID
|
||||
* @param string $projectID
|
||||
* @param string $from project|program|programProject
|
||||
* @param string $recTotal
|
||||
* @param string $recPerPage
|
||||
* @param string $pageID
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function whitelist($projectID = 0, $module = 'project', $from = 'project', $objectType = 'project', $orderBy = 'id_desc', $recTotal = 0, $recPerPage = 20, $pageID = 1)
|
||||
public function whitelist(string $projectID = '0', string $from = 'project',int $recTotal = '0', int $recPerPage = '20', int $pageID = '1')
|
||||
{
|
||||
$projectID = (int)$projectID;
|
||||
$projectID = $this->project->setMenu($projectID);
|
||||
$project = $this->project->getById($projectID);
|
||||
$project = $this->project->getByID($projectID);
|
||||
if(isset($project->acl) and $project->acl == 'open') $this->locate($this->createLink('project', 'index', "projectID=$projectID"));
|
||||
|
||||
echo $this->fetch('personnel', 'whitelist', "objectID=$projectID&module=$module&browseType=$objectType&orderBy=$orderBy&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&projectID=$projectID&from=$from");
|
||||
echo $this->fetch('personnel', 'whitelist', "objectID=$projectID&module=project&browseType=project&orderBy=id_desc&recTotal=$recTotal&recPerPage=$recPerPage&pageID=$pageID&projectID=$projectID&from=$from");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户到项目白名单中
|
||||
* Adding users to the white list.
|
||||
*
|
||||
* @param int $projectID
|
||||
* @param int $deptID
|
||||
* @param int $copyID
|
||||
* @param int $programID
|
||||
* @param int $from
|
||||
* @param string $projectID
|
||||
* @param int $deptID
|
||||
* @param int $copyID
|
||||
* @param int $programID
|
||||
* @param string $from
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function addWhitelist($projectID = 0, $deptID = 0, $copyID = 0, $programID = 0, $from = 'project')
|
||||
public function addWhitelist(string $projectID = '0', int $deptID = 0, int $copyID = 0, int $programID = 0, string $from = 'project')
|
||||
{
|
||||
$projectID = (int)$projectID;
|
||||
$projectID = $this->project->setMenu($projectID);
|
||||
$project = $this->project->getById($projectID);
|
||||
$project = $this->project->getByID($projectID);
|
||||
if(isset($project->acl) and $project->acl == 'open') $this->locate($this->createLink('project', 'index', "projectID=$projectID"));
|
||||
|
||||
echo $this->fetch('personnel', 'addWhitelist', "objectID=$projectID&dept=$deptID©ID=$copyID&objectType=project&module=project&programID=$programID&from=$from");
|
||||
}
|
||||
|
||||
/*
|
||||
* 移除项目白名单人员
|
||||
* Removing users from the white list.
|
||||
*
|
||||
* @param int $id
|
||||
* @param string $id
|
||||
* @param string $confirm
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function unbindWhitelist($id = 0, $confirm = 'no')
|
||||
public function unbindWhitelist(string $id = '0', $confirm = 'no')
|
||||
{
|
||||
echo $this->fetch('personnel', 'unbindWhitelist', "id=$id&confirm=$confirm");
|
||||
}
|
||||
|
||||
+33
-37
@@ -221,11 +221,14 @@ class projectModel extends model
|
||||
*/
|
||||
public function getByID(int $projectID): object|false
|
||||
{
|
||||
/* Using demo data during tutorials. */
|
||||
if(defined('TUTORIAL')) return $this->loadModel('tutorial')->getProject();
|
||||
|
||||
/* Get project info. */
|
||||
$project = $this->projectTao->fetchProjectInfo($projectID);
|
||||
if(!$project) return false;
|
||||
|
||||
/* Replace image url. */
|
||||
$project = $this->loadModel('file')->replaceImgURL($project, 'desc');
|
||||
return $project;
|
||||
}
|
||||
@@ -261,21 +264,23 @@ class projectModel extends model
|
||||
*/
|
||||
public function getList(string $status = 'undone', string $orderBy = 'order_desc', bool $involved = false, object|null $pager = null): array
|
||||
{
|
||||
/* Init vars. */
|
||||
/* Get projec list by status. */
|
||||
$projects = $this->projectTao->fetchProjectList($status, $orderBy, $involved, $pager);
|
||||
if(empty($projects)) return array();
|
||||
|
||||
/* Get team members and estimates under the project. */
|
||||
$projectIdList = array_keys($projects);
|
||||
$teams = $this->projectTao->fetchMemberCountByIdList($projectIdList);
|
||||
$teamCount = $this->projectTao->fetchMemberCountByIdList($projectIdList);
|
||||
$estimates = $this->projectTao->fetchTaskEstimateByIdList($projectIdList);
|
||||
|
||||
/* Set project attribute. */
|
||||
$this->app->loadClass('pager', true);
|
||||
foreach($projects as $projectID => $project)
|
||||
{
|
||||
$orderBy = $project->model == 'waterfall' ? 'id_asc' : 'id_desc';
|
||||
$pager = $project->model == 'waterfall' ? null : new pager(0, 1, 1);
|
||||
$project->executions = $this->loadModel('execution')->getStatData($projectID, 'undone', 0, 0, false, '', $orderBy, $pager);
|
||||
$project->teamCount = isset($teams[$projectID]) ? $teams[$projectID] : 0;
|
||||
$project->teamCount = isset($teamCount[$projectID]) ? $teamCount[$projectID] : 0;
|
||||
$project->estimate = isset($estimates[$projectID]) ? round($estimates[$projectID]->estimate, 2) : 0;
|
||||
$project->parentName = $project->parent ? $this->projectTao->getParentProgram($project->path, $project->grade) : '';
|
||||
}
|
||||
@@ -293,52 +298,43 @@ class projectModel extends model
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getOverviewList($queryType = 'byStatus', $param = 'all', $orderBy = 'id_desc', $limit = 15, $excludedModel = '')
|
||||
public function getOverviewList(string $queryType = 'byStatus', string|int $param = 'all', string $orderBy = 'id_desc', int $limit = 15, string $excludedModel = ''): array
|
||||
{
|
||||
/* */
|
||||
$queryType = strtolower($queryType);
|
||||
$projects = $this->dao->select('*')->from(TABLE_PROJECT)
|
||||
->where('type')->eq('project')
|
||||
->andWhere('vision')->eq($this->config->vision)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->beginIF($excludedModel)->andWhere('model')->ne($excludedModel)->fi()
|
||||
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
|
||||
->beginIF($queryType == 'bystatus' and $param == 'undone')->andWhere('status')->notIN('done,closed')->fi()
|
||||
->beginIF($queryType == 'bystatus' and $param != 'all' and $param != 'undone')->andWhere('status')->eq($param)->fi()
|
||||
->beginIF($queryType == 'byid')->andWhere('id')->eq($param)->fi()
|
||||
->orderBy($orderBy)
|
||||
->beginIF($limit)->limit($limit)->fi()
|
||||
->fetchAll('id');
|
||||
|
||||
/* Get project list by query. */
|
||||
$projects = $this->projectTao->fetchProjectListByQuery($queryType, $param, $orderBy, $limit, $excludedModel);
|
||||
if(empty($projects)) return array();
|
||||
|
||||
/* Get team members under the project. */
|
||||
$projectIdList = array_keys($projects);
|
||||
$teams = $this->projectTao->fetchMemberCountByIdList($projectIdList);
|
||||
$teamCount = $this->projectTao->fetchMemberCountByIdList($projectIdList);
|
||||
|
||||
/* Get all consumed and all estimate under the project. */
|
||||
$fields = 't2.project, ROUND(SUM(t1.consumed), 1) AS consumed, ROUND(SUM(t1.estimate), 1) AS estimate';
|
||||
$hours = $this->projectTao->fetchTaskEstimateByIdList($projectIdList, $fields);
|
||||
|
||||
$storySummary = $this->projectTao->getTotalStoriesByProject($projectIdList);
|
||||
$taskSummary = $this->projectTao->getTotalTaskByProject($projectIdList);
|
||||
/* Get bug, task and story summary under the project. */
|
||||
$bugSummary = $this->projectTao->getTotalBugByProject($projectIdList);
|
||||
$taskSummary = $this->projectTao->getTotalTaskByProject($projectIdList);
|
||||
$storySummary = $this->projectTao->getTotalStoriesByProject($projectIdList);
|
||||
|
||||
/* Set project attribute. */
|
||||
foreach($projects as $projectID => $project)
|
||||
{
|
||||
$project->consumed = isset($hours[$projectID]) ? round((float)$hours[$projectID]->consumed, 1) : 0;
|
||||
$project->estimate = isset($hours[$projectID]) ? round((float)$hours[$projectID]->estimate, 1) : 0;
|
||||
$project->teamCount = isset($teams[$projectID]) ? $teams[$projectID] : 0;
|
||||
$project->leftBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->leftBugs : 0;
|
||||
$project->allBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->allBugs : 0;
|
||||
$project->doneBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->doneBugs : 0;
|
||||
$project->allStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->allStories: 0;
|
||||
$project->doneStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->doneStories: 0;
|
||||
$project->leftStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->leftStories: 0;
|
||||
$project->leftTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->leftTasks : 0;
|
||||
$project->allTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->allTasks : 0;
|
||||
$project->waitTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->waitTasks : 0;
|
||||
$project->doingTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->doingTasks : 0;
|
||||
$project->rndDoneTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->doneTasks : 0;
|
||||
$project->liteDoneTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->litedoneTasks : 0;
|
||||
$project->teamCount = zget($teamCount, $projectID, 0);
|
||||
$project->consumed = isset($hours[$projectID]) ? round((float)$hours[$projectID]->consumed, 1) : 0;
|
||||
$project->estimate = isset($hours[$projectID]) ? round((float)$hours[$projectID]->estimate, 1) : 0;
|
||||
$project->leftBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->leftBugs : 0;
|
||||
$project->allBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->allBugs : 0;
|
||||
$project->doneBugs = isset($bugSummary[$projectID]) ? $bugSummary[$projectID]->doneBugs : 0;
|
||||
$project->allStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->allStories : 0;
|
||||
$project->doneStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->doneStories : 0;
|
||||
$project->leftStories = isset($storySummary[$projectID]) ? $storySummary[$projectID]->leftStories : 0;
|
||||
$project->leftTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->leftTasks : 0;
|
||||
$project->allTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->allTasks : 0;
|
||||
$project->waitTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->waitTasks : 0;
|
||||
$project->doingTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->doingTasks : 0;
|
||||
$project->rndDoneTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->doneTasks : 0;
|
||||
$project->liteDoneTasks = isset($taskSummary[$projectID]) ? $taskSummary[$projectID]->litedoneTasks : 0;
|
||||
}
|
||||
|
||||
return $projects;
|
||||
|
||||
+30
-1
@@ -187,7 +187,7 @@ class projectTao extends projectModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project details, including all contents of the TABLE_PROJECT.
|
||||
* Get project details, including all contents of the project.
|
||||
* 获取项目的详情,包含project表的所有内容。
|
||||
*
|
||||
* @param int $projectID
|
||||
@@ -555,6 +555,35 @@ class projectTao extends projectModel
|
||||
->fetchAll('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过条件查询和数量限制查询项目列表。
|
||||
* Get project list by query and with limit.
|
||||
*
|
||||
* @param string $queryType
|
||||
* @param string|int $param
|
||||
* @param string $orderBy
|
||||
* @param int $limit
|
||||
* @param string $excludedModel
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchProjectListByQuery(string $queryType, string|int $param, string $orderBy, int $limit, string $excludedModel): array
|
||||
{
|
||||
$queryType = strtolower($queryType);
|
||||
return $this->dao->select('*')->from(TABLE_PROJECT)
|
||||
->where('type')->eq('project')
|
||||
->andWhere('vision')->eq($this->config->vision)
|
||||
->andWhere('deleted')->eq(0)
|
||||
->beginIF($excludedModel)->andWhere('model')->ne($excludedModel)->fi()
|
||||
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
|
||||
->beginIF($queryType == 'bystatus' and $param == 'undone')->andWhere('status')->notIN('done,closed')->fi()
|
||||
->beginIF($queryType == 'bystatus' and $param != 'all' and $param != 'undone')->andWhere('status')->eq($param)->fi()
|
||||
->beginIF($queryType == 'byid')->andWhere('id')->eq($param)->fi()
|
||||
->orderBy($orderBy)
|
||||
->beginIF($limit)->limit($limit)->fi()
|
||||
->fetchAll('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据项目ID列表查询团队成员数量。
|
||||
* Get project team member count by project id list.
|
||||
|
||||
@@ -1,20 +1,48 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
|
||||
function initData()
|
||||
{
|
||||
$project = zdTable('project');
|
||||
$project->id->range('11-19');
|
||||
$project->project->range('11-19');
|
||||
$project->name->prefix("项目")->range('11-19');
|
||||
$project->code->prefix("project")->range('11-19');
|
||||
$project->model->range("scrum");
|
||||
$project->auth->range("[]");
|
||||
$project->path->range("[]");
|
||||
$project->type->range("project");
|
||||
$project->grade->range("1");
|
||||
$project->days->range("1");
|
||||
$project->status->range("wait,doing,suspended,closed");
|
||||
$project->desc->range("[]");
|
||||
$project->budget->range("100000,200000");
|
||||
$project->budgetUnit->range("CNY");
|
||||
$project->percent->range("0-0");
|
||||
$project->openedDate->range("`2023-05-01 10:00:10`");
|
||||
$project->gen(9);
|
||||
|
||||
zdTable('team')->gen(10);
|
||||
|
||||
$stakeholder = zdTable('stakeholder');
|
||||
$stakeholder->id->range('1-9');
|
||||
$stakeholder->objectID->range('11-19');
|
||||
$stakeholder->objectType->range('program,project');
|
||||
$stakeholder->user->range("admin");
|
||||
$stakeholder->type->range("inside");
|
||||
$stakeholder->from->range("[]");
|
||||
$stakeholder->createdBy->range("admin");
|
||||
$stakeholder->createdDate->range("`2023-05-01 10:00:10`");
|
||||
$stakeholder->gen(9);
|
||||
}
|
||||
|
||||
su('admin');
|
||||
|
||||
/**
|
||||
|
||||
title=测试 projectModel::getOverviewList;
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
获取未开始的项目数量 >> 15
|
||||
获取状态不为done和closed的项目数量 >> 15
|
||||
根据项目ID获取项目 >> 1
|
||||
获取不存在的项目 >> 0
|
||||
按照ID正序获取项目列表,查看排第一个的项目详情 >> 11,项目1
|
||||
按照项目名称倒序获取项目列表,查看排第一个的项目详情 >> 100,项目90
|
||||
|
||||
*/
|
||||
|
||||
@@ -30,9 +58,9 @@ $byId2 = $tester->project->getOverviewList('byid', '10000');
|
||||
$byOrder1 = $tester->project->getOverviewList('byStatus', 'all', 'id_asc');
|
||||
$byOrder2 = $tester->project->getOverviewList('byStatus', 'all', 'name_desc');
|
||||
|
||||
r(count($byStatus1)) && p() && e('15'); // 获取未开始的项目数量
|
||||
r(count($byStatus2)) && p() && e('15'); // 获取状态不为done和closed的项目数量
|
||||
r(count($byId1)) && p() && e('1'); // 根据项目ID获取项目
|
||||
r(count($byId2)) && p() && e('0'); // 获取不存在的项目
|
||||
r(current($byOrder1)) && p('id,name') && e('11,项目1'); // 按照ID正序获取项目列表,查看排第一个的项目详情
|
||||
r(current($byOrder2)) && p('id,name') && e('100,项目90'); // 按照项目名称倒序获取项目列表,查看排第一个的项目详情
|
||||
r(count($byStatus1)) && p() && e('3'); // 获取未开始的项目数量
|
||||
r(count($byStatus2)) && p() && e('7'); // 获取状态不为done和closed的项目数量
|
||||
r(count($byId1)) && p() && e('1'); // 根据项目ID获取项目
|
||||
r(count($byId2)) && p() && e('0'); // 获取不存在的项目
|
||||
r(current($byOrder1)) && p('id,name') && e('11,项目11'); // 按照ID正序获取项目列表,查看排第一个的项目详情
|
||||
r(current($byOrder2)) && p('id,name') && e('19,项目19'); // 按照项目名称倒序获取项目列表,查看排第一个的项目详情
|
||||
|
||||
@@ -238,4 +238,18 @@ class Project
|
||||
$projects = $this->project->fetchProjectList($status, 'id_desc', $involved, null);
|
||||
return $projects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getList function.
|
||||
*
|
||||
* @param int $status
|
||||
* @param bool $involved
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function testFetchProjectListByQuery($queryType, $param, $orderBy = 'id_desc')
|
||||
{
|
||||
$projects = $this->project->fetchProjectListByQuery($queryType, $param, $orderBy, 15, '');
|
||||
return $projects;
|
||||
}
|
||||
}
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
include dirname(__FILE__, 2) . "/project.class.php";
|
||||
|
||||
function initData()
|
||||
{
|
||||
$project = zdTable('project');
|
||||
$project->id->range('11-19');
|
||||
$project->project->range('11-19');
|
||||
$project->name->prefix("项目")->range('11-19');
|
||||
$project->code->prefix("project")->range('11-19');
|
||||
$project->model->range("scrum");
|
||||
$project->auth->range("[]");
|
||||
$project->path->range("[]");
|
||||
$project->type->range("project");
|
||||
$project->grade->range("1");
|
||||
$project->days->range("1");
|
||||
$project->status->range("wait,doing,suspended,closed");
|
||||
$project->desc->range("[]");
|
||||
$project->budget->range("100000,200000");
|
||||
$project->budgetUnit->range("CNY");
|
||||
$project->percent->range("0-0");
|
||||
$project->openedDate->range("`2023-05-01 10:00:10`");
|
||||
$project->gen(9);
|
||||
|
||||
zdTable('team')->gen(10);
|
||||
|
||||
$stakeholder = zdTable('stakeholder');
|
||||
$stakeholder->id->range('1-9');
|
||||
$stakeholder->objectID->range('11-19');
|
||||
$stakeholder->objectType->range('program,project');
|
||||
$stakeholder->user->range("admin");
|
||||
$stakeholder->type->range("inside");
|
||||
$stakeholder->from->range("[]");
|
||||
$stakeholder->createdBy->range("admin");
|
||||
$stakeholder->createdDate->range("`2023-05-01 10:00:10`");
|
||||
$stakeholder->gen(9);
|
||||
}
|
||||
|
||||
su('admin');
|
||||
|
||||
/**
|
||||
|
||||
title=测试 projectModel::fetchProjectListByQuery();
|
||||
cid=1
|
||||
|
||||
*/
|
||||
|
||||
$projectTester = new Project();
|
||||
|
||||
$byStatus1 = $projectTester->testFetchProjectListByQuery('byStatus', 'wait');
|
||||
$byStatus2 = $projectTester->testFetchProjectListByQuery('byStatus', 'undone');
|
||||
|
||||
$byId1 = $projectTester->testFetchProjectListByQuery('byid', '11');
|
||||
$byId2 = $projectTester->testFetchProjectListByQuery('byid', '10000');
|
||||
|
||||
$byOrder1 = $projectTester->testFetchProjectListByQuery('byStatus', 'all', 'id_asc');
|
||||
$byOrder2 = $projectTester->testFetchProjectListByQuery('byStatus', 'all', 'name_desc');
|
||||
|
||||
r(count($byStatus1)) && p() && e('3'); // 获取未开始的项目数量
|
||||
r(count($byStatus2)) && p() && e('7'); // 获取状态不为done和closed的项目数量
|
||||
r(count($byId1)) && p() && e('1'); // 根据项目ID获取项目
|
||||
r(count($byId2)) && p() && e('0'); // 获取不存在的项目
|
||||
r(current($byOrder1)) && p('id,name') && e('11,项目11'); // 按照ID正序获取项目列表,查看排第一个的项目详情
|
||||
r(current($byOrder2)) && p('id,name') && e('19,项目19'); // 按照项目名称倒序获取项目列表,查看排第一个的项目详情
|
||||
@@ -684,14 +684,15 @@ class projectZen extends project
|
||||
* 记录多迭代及瀑布类项目移除的产品
|
||||
* Record multiple and waterfall project unlinked products
|
||||
*
|
||||
* @param array $oldProducts
|
||||
* @param array $newProductIDs
|
||||
* @param array $idList
|
||||
* @param object $project
|
||||
* @param array $oldProducts
|
||||
* @param array $newProductIDs
|
||||
* @param array $idList
|
||||
*
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function dealMultipleProduct(array $oldProducts, array $newProductIDs, array $idList): void
|
||||
protected function dealMultipleProduct(object $project, array $oldProducts, array $newProductIDs, array $idList): void
|
||||
{
|
||||
/* Update multiple project linked products. */
|
||||
if(empty($project->multiple))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
helper::import(dirname(__FILE__) . 'config/form.php');
|
||||
|
||||
$config->task = new stdclass();
|
||||
$config->task->batchCreate = 10;
|
||||
|
||||
@@ -227,3 +230,18 @@ $config->task->datatable->fieldList['actions']['title'] = 'actions';
|
||||
$config->task->datatable->fieldList['actions']['fixed'] = 'right';
|
||||
$config->task->datatable->fieldList['actions']['width'] = '180';
|
||||
$config->task->datatable->fieldList['actions']['required'] = 'yes';
|
||||
|
||||
$config->task->create->template = new stdclass();
|
||||
$config->task->create->template->module = 0;
|
||||
$config->task->create->template->mode = '';
|
||||
$config->task->create->template->assignedTo = '';
|
||||
$config->task->create->template->name = '';
|
||||
$config->task->create->template->story = 0;
|
||||
$config->task->create->template->type = '';
|
||||
$config->task->create->template->pri = 3;
|
||||
$config->task->create->template->estimate = '';
|
||||
$config->task->create->template->desc = '';
|
||||
$config->task->create->template->estStarted = null;
|
||||
$config->task->create->template->deadline = null;
|
||||
$config->task->create->template->mailto = '';
|
||||
$config->task->create->template->color = '';
|
||||
|
||||
@@ -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());
|
||||
|
||||
+97
-446
@@ -27,31 +27,33 @@ 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(empty($this->app->user->view->sprints) and !$executionID) $this->locate($this->createLink('execution', 'create'));
|
||||
/* Analytic parameter. */
|
||||
$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);
|
||||
/* 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'));
|
||||
|
||||
/* 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 +62,60 @@ class task extends control
|
||||
return print(js::locate($this->createLink('execution', 'task', "executionID=$executionID")));
|
||||
}
|
||||
|
||||
$task = new stdClass();
|
||||
$task->module = $moduleID;
|
||||
$task->mode = '';
|
||||
$task->assignedTo = '';
|
||||
$task->name = '';
|
||||
$task->story = $storyID;
|
||||
$task->type = '';
|
||||
$task->pri = '3';
|
||||
$task->estimate = '';
|
||||
$task->desc = '';
|
||||
$task->estStarted = '';
|
||||
$task->deadline = '';
|
||||
$task->mailto = '';
|
||||
$task->color = '';
|
||||
if($taskID > 0)
|
||||
{
|
||||
$task = $this->task->getByID($taskID);
|
||||
$executionID = $task->execution;
|
||||
|
||||
/* Emptying consumed hours when copy task. */
|
||||
if($task->mode == 'multi')
|
||||
{
|
||||
foreach($task->team as $teamMember) $teamMember->consumed = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($todoID > 0)
|
||||
{
|
||||
$todo = $this->loadModel('todo')->getById($todoID);
|
||||
$task->name = $todo->name;
|
||||
$task->pri = $todo->pri;
|
||||
$task->desc = $todo->desc;
|
||||
}
|
||||
|
||||
if($bugID > 0)
|
||||
{
|
||||
$bug = $this->loadModel('bug')->getById($bugID);
|
||||
$task->name = $bug->title;
|
||||
$task->pri = $bug->pri;
|
||||
$task->pri = !empty($bug->pri) ? $bug->pri : '3';
|
||||
$task->assignedTo = array($bug->assignedTo);
|
||||
}
|
||||
|
||||
$taskLink = $this->createLink('execution', 'browse', "executionID=$executionID&tab=task");
|
||||
|
||||
$this->loadModel('kanban');
|
||||
if($execution->type == 'kanban')
|
||||
{
|
||||
$regionPairs = $this->kanban->getRegionPairs($execution->id, 0, 'execution');
|
||||
$regionID = !empty($output['regionID']) ? $output['regionID'] : key($regionPairs);
|
||||
$lanePairs = $this->kanban->getLanePairsByRegion($regionID, 'task');
|
||||
$laneID = isset($output['laneID']) ? $output['laneID'] : key($lanePairs);
|
||||
|
||||
$this->view->regionID = $regionID;
|
||||
$this->view->laneID = $laneID;
|
||||
$this->view->regionPairs = $regionPairs;
|
||||
$this->view->lanePairs = $lanePairs;
|
||||
}
|
||||
|
||||
/* Submit the data processing after creating the task form. */
|
||||
if(!empty($_POST))
|
||||
{
|
||||
$response['result'] = 'success';
|
||||
/* Prepare the data information before creating the task. */
|
||||
$result = $this->prepareCreate($executionID, (float)$this->post->estimate, $this->post->estStarted, $this->post->deadline, (bool)$this->post->selectTestStory);
|
||||
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
|
||||
setcookie('lastTaskModule', (int)$this->post->module, $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
if($this->post->execution) $executionID = (int)$this->post->execution;
|
||||
list($task, $testTasks, $existTaskID) = $result;
|
||||
if($existTaskID) return $this->send(array('result' => 'success', 'message' => sprintf($this->lang->duplicate, $this->lang->task->common), 'locate' => $this->createLink('task', 'view', "taskID={$existTaskID}")));
|
||||
|
||||
/* Create task here. */
|
||||
$tasksID = $this->task->create($executionID, $bugID);
|
||||
if(dao::isError())
|
||||
{
|
||||
$response['result'] = 'fail';
|
||||
$response['message'] = dao::getError();
|
||||
return $this->send($response);
|
||||
}
|
||||
/* Create task. */
|
||||
$taskIdList = $this->task->create($task, $this->post->assignedTo, (int)$this->post->multiple, $this->post->team, (bool)$this->post->selectTestStory);
|
||||
if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));
|
||||
|
||||
/* if the count of tasksID is 1 then check exists. */
|
||||
if(count($tasksID) == 1)
|
||||
{
|
||||
$taskID = current($tasksID);
|
||||
if($taskID['status'] == 'exists')
|
||||
{
|
||||
$response['locate'] = $this->createLink('task', 'view', "taskID={$taskID['id']}");
|
||||
$response['message'] = sprintf($this->lang->duplicate, $this->lang->task->common);
|
||||
return $this->send($response);
|
||||
}
|
||||
}
|
||||
/* Update other data related to the task after it is created. */
|
||||
$task->id = current($taskIdList);
|
||||
$columnID = isset($output['columnID']) ? (int)$output['columnID'] : 0;
|
||||
$this->task->afterCreate($task, $taskIdList, $bugID, $todoID, $testTasks);
|
||||
$this->task->updateKanbanData($execution, $task, (int)$_POST['lane'], $columnID);
|
||||
setcookie('lastTaskModule', (int)$_POST['module'], $this->config->cookieLife, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
|
||||
/* Create actions. */
|
||||
$this->loadModel('action');
|
||||
foreach($tasksID as $taskID)
|
||||
{
|
||||
/* if status is exists then this task has exists not new create. */
|
||||
if($taskID['status'] == 'exists') continue;
|
||||
|
||||
$taskID = $taskID['id'];
|
||||
$this->action->create('task', $taskID, 'Opened', '');
|
||||
}
|
||||
|
||||
/* Create task in kanban. */
|
||||
$kanbanID = $execution->type == 'kanban' ? $executionID : $_POST['execution'];
|
||||
|
||||
$laneID = isset($output['laneID']) ? $output['laneID'] : 0;
|
||||
if(!empty($_POST['lane'])) $laneID = $_POST['lane'];
|
||||
|
||||
$columnID = $this->kanban->getColumnIDByLaneID($laneID, 'wait');
|
||||
if(empty($columnID)) $columnID = isset($output['columnID']) ? $output['columnID'] : 0;
|
||||
|
||||
if(!empty($laneID) and !empty($columnID)) $this->kanban->addKanbanCell($kanbanID, $laneID, $columnID, 'task', $taskID);
|
||||
if(empty($laneID) or empty($columnID)) $this->kanban->updateLane($kanbanID, 'task');
|
||||
|
||||
/* To do status. */
|
||||
if($todoID > 0)
|
||||
{
|
||||
$this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec();
|
||||
$this->action->create('todo', $todoID, 'finished', '', "TASK:$taskID");
|
||||
|
||||
if(($this->config->edition == 'biz' || $this->config->edition == 'max') && $todo->type == 'feedback' && $todo->idvalue) $this->loadModel('feedback')->updateStatus('todo', $todo->idvalue, 'done');
|
||||
}
|
||||
|
||||
$message = $this->executeHooks($taskID);
|
||||
if($message) $this->lang->saveSuccess = $message;
|
||||
$response['message'] = $this->lang->saveSuccess;
|
||||
|
||||
/* Return task id when call the API. */
|
||||
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $taskID));
|
||||
|
||||
/* If link from no head then reload. */
|
||||
if(isonlybody())
|
||||
{
|
||||
if($this->app->tab == 'execution' or $this->config->vision == 'lite')
|
||||
{
|
||||
$execLaneType = $this->session->execLaneType ? $this->session->execLaneType : 'all';
|
||||
$execGroupBy = $this->session->execGroupBy ? $this->session->execGroupBy : 'default';
|
||||
if($execution->type == 'kanban')
|
||||
{
|
||||
$rdSearchValue = $this->session->rdSearchValue ? $this->session->rdSearchValue : '';
|
||||
$kanbanData = $this->loadModel('kanban')->getRDKanban($executionID, $execLaneType, 'id_desc', 0, $execGroupBy, $rdSearchValue);
|
||||
$kanbanData = json_encode($kanbanData);
|
||||
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.updateKanban($kanbanData, 0)"));
|
||||
}
|
||||
else
|
||||
{
|
||||
$taskSearchValue = $this->session->taskSearchValue ? $this->session->taskSearchValue : '';
|
||||
$kanbanData = $this->kanban->getExecutionKanban($executionID, $execLaneType, $execGroupBy, $taskSearchValue);
|
||||
$kanbanType = $execLaneType == 'all' ? 'task' : key($kanbanData);
|
||||
$kanbanData = $kanbanData[$kanbanType];
|
||||
$kanbanData = json_encode($kanbanData);
|
||||
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'closeModal' => true, 'callback' => "parent.updateKanban(\"task\", $kanbanData)"));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->send(array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => 'parent'));
|
||||
}
|
||||
}
|
||||
|
||||
/* Locate the browser. */
|
||||
if($this->app->getViewType() == 'xhtml')
|
||||
{
|
||||
$taskLink = $this->createLink('task', 'view', "taskID=$taskID", 'html');
|
||||
$response['locate'] = $taskLink;
|
||||
return $this->send($response);
|
||||
}
|
||||
|
||||
if($this->post->after == 'continueAdding')
|
||||
{
|
||||
$storyParam = $this->post->story ? $this->post->story : '';
|
||||
|
||||
$response['message'] = $this->lang->task->successSaved . $this->lang->task->afterChoices['continueAdding'];
|
||||
$response['locate'] = $this->createLink('task', 'create', "executionID=$executionID&storyID={$storyParam}&moduleID=$moduleID");
|
||||
return $this->send($response);
|
||||
}
|
||||
elseif($this->post->after == 'toTaskList')
|
||||
{
|
||||
setcookie('moduleBrowseParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
$taskLink = $this->createLink('execution', 'task', "executionID=$executionID&status=unclosed¶m=0&orderBy=id_desc");
|
||||
$response['locate'] = $taskLink;
|
||||
return $this->send($response);
|
||||
}
|
||||
elseif($this->post->after == 'toStoryList')
|
||||
{
|
||||
$response['locate'] = $this->createLink('execution', 'story', "executionID=$executionID");
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$projectID = $this->dao->select('project')->from(TABLE_EXECUTION)->where('id')->eq($executionID)->fetch('project');
|
||||
$response['locate'] = $this->createLink('projectstory', 'story', "projectID=$projectID");
|
||||
}
|
||||
return $this->send($response);
|
||||
}
|
||||
else
|
||||
{
|
||||
$response['locate'] = $taskLink;
|
||||
return $this->send($response);
|
||||
}
|
||||
/* Get the information returned after a task is created. */
|
||||
$response = $this->taskZen->responseAfterCreate($task, $execution, $_POST['after']);
|
||||
return $this->send($response);
|
||||
}
|
||||
|
||||
$users = $this->loadModel('user')->getPairs('noclosed|nodeleted');
|
||||
$members = $this->loadModel('user')->getTeamMemberPairs($executionID, 'execution', 'nodeleted');
|
||||
$showAllModule = isset($this->config->execution->task->allModule) ? $this->config->execution->task->allModule : '';
|
||||
$moduleOptionMenu = $this->tree->getTaskOptionMenu($executionID, 0, 0, $showAllModule ? 'allModule' : '');
|
||||
|
||||
/* 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();
|
||||
/* Shows the variables needed to create the task page. */
|
||||
$this->taskZen->showCreateVars($execution, $storyID, $moduleID, $taskID, $todoID, $bugID, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 +123,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 +763,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;
|
||||
|
||||
@@ -2074,7 +1708,7 @@ class task extends control
|
||||
|
||||
/* Get related objects title or names. */
|
||||
$relatedStories = $this->dao->select('id,title')->from(TABLE_STORY)->where('id')->in($relatedStoryIdList)->fetchPairs();
|
||||
$relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq('task')->andWhere('objectID')->in(@array_keys($tasks))->andWhere('extra')->ne('editor')->fetchGroup('objectID');
|
||||
$relatedFiles = $this->dao->select('id, objectID, pathname, title')->from(TABLE_FILE)->where('objectType')->eq('task')->andWhere('objectID')->in(array_keys($tasks))->andWhere('extra')->ne('editor')->fetchGroup('objectID');
|
||||
$relatedModules = $this->loadModel('tree')->getAllModulePairs('task');
|
||||
|
||||
if($tasks)
|
||||
@@ -2333,4 +1967,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;
|
||||
}
|
||||
}
|
||||
|
||||
+313
-483
@@ -14,298 +14,142 @@
|
||||
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();
|
||||
$mails = array();
|
||||
|
||||
/* 运营管理界面移除泳道和泳道列。 */
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$lanes = $tasks->lane;
|
||||
@@ -313,43 +157,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 +176,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 +188,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 +199,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 +219,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 +245,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 +540,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,9 +558,9 @@ class taskModel extends model
|
||||
|
||||
if($taskStatus == 'doing')
|
||||
{
|
||||
$efforts = $this->getTaskEstimate($taskID);
|
||||
$efforts = $this->getTaskEfforts($taskID);
|
||||
$doingUsers = array();
|
||||
foreach($efforts as $i => $effort)
|
||||
foreach($efforts as $effort)
|
||||
{
|
||||
if($effort->left != 0) $doingUsers[$effort->account] = $effort->account;
|
||||
if($effort->left == 0) unset($doingUsers[$effort->account]);
|
||||
@@ -1012,7 +688,7 @@ class taskModel extends model
|
||||
}
|
||||
elseif($effortID)
|
||||
{
|
||||
$efforts = $this->getTaskEstimate($taskID, '', $effortID);
|
||||
$efforts = $this->getTaskEfforts($taskID, '', $effortID);
|
||||
|
||||
$prevTeam = null;
|
||||
$thisTeam = null;
|
||||
@@ -1138,7 +814,7 @@ class taskModel extends model
|
||||
if($this->post->team and count(array_filter($this->post->team)) > 1)
|
||||
{
|
||||
$teams = $this->manageTaskTeam($oldTask->mode, $taskID, $task->status);
|
||||
if(!empty($teams)) $task = $this->computeHours4Multiple($oldTask, $task, array(), $autoStatus = false);
|
||||
if(!empty($teams)) $task = $this->computeHours4Multiple($oldTask, $task, array(), false);
|
||||
}
|
||||
if(empty($teams)) $task->mode = '';
|
||||
|
||||
@@ -1381,7 +1057,7 @@ class taskModel extends model
|
||||
foreach($extendFields as $extendField)
|
||||
{
|
||||
$task->{$extendField->field} = $this->post->{$extendField->field}[$taskID];
|
||||
if(is_array($task->{$extendField->field})) $task->{$extendField->field} = join(',', $task->{$extendField->field});
|
||||
if(is_array($task->{$extendField->field})) $task->{$extendField->field} = implode(',', $task->{$extendField->field});
|
||||
|
||||
$task->{$extendField->field} = htmlSpecialString($task->{$extendField->field});
|
||||
}
|
||||
@@ -1769,7 +1445,7 @@ class taskModel extends model
|
||||
{
|
||||
$task->status = 'done';
|
||||
$task->finishedBy = $this->app->user->account;
|
||||
$task->finishedDate = $task->finishedDate;
|
||||
$task->finishedDate = $now;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1825,7 +1501,7 @@ class taskModel extends model
|
||||
foreach($record->dates as $id => $item) if($item > $today) dao::$errors[] = 'ID #' . $id . ' ' . $this->lang->task->error->date;
|
||||
if(dao::isError()) return false;
|
||||
|
||||
$task = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch();;
|
||||
$task = $this->dao->select('*')->from(TABLE_TASK)->where('id')->eq($taskID)->fetch();
|
||||
$task->team = $this->dao->select('*')->from(TABLE_TASKTEAM)->where('task')->eq($taskID)->orderBy('order')->fetchAll('id');
|
||||
|
||||
/* Check if the current user is in the team. */
|
||||
@@ -1870,8 +1546,7 @@ class taskModel extends model
|
||||
|
||||
$this->loadModel('action');
|
||||
|
||||
$allChanges = array();;
|
||||
$left = $task->left;
|
||||
$allChanges = array();
|
||||
$now = helper::now();
|
||||
$oldStatus = $task->status;
|
||||
$lastDate = $this->dao->select('*')->from(TABLE_EFFORT)->where('objectID')->eq($taskID)->andWhere('objectType')->eq('task')->orderBy('date_desc,id_desc')->limit(1)->fetch('date');
|
||||
@@ -1902,7 +1577,7 @@ class taskModel extends model
|
||||
$currentTeam = $this->getTeamByAccount($task->team, $this->app->user->account, $extra);
|
||||
}
|
||||
|
||||
if($newTask->left == 0 and ((empty($currentTeam) and strpos('done,cancel,closed', $task->status) === false) or (!empty($currentTeam) and $currentTeam->status != 'done')))
|
||||
if(!$newTask->left and ((empty($currentTeam) and in_array($task->status, array('done', 'cancel', 'closed'))) or (!empty($currentTeam) and $currentTeam->status != 'done')))
|
||||
{
|
||||
$newTask->status = 'done';
|
||||
$newTask->assignedTo = $task->openedBy;
|
||||
@@ -2008,7 +1683,6 @@ class taskModel extends model
|
||||
|
||||
$oldTask = $this->getById($taskID);
|
||||
$now = helper::now();
|
||||
$today = helper::today();
|
||||
|
||||
if($extra != 'DEVOPS' and strpos($this->config->task->finish->requiredFields, 'comment') !== false and !$this->post->comment)
|
||||
{
|
||||
@@ -2342,7 +2016,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 +2366,7 @@ class taskModel extends model
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task estimate.
|
||||
* Get task efforts.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param string $account
|
||||
@@ -2701,7 +2375,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 +2616,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 +3900,160 @@ class taskModel extends model
|
||||
if(!empty($deadline) and !helper::isZeroDate($deadline) and $deadline > $execution->end) dao::$errors['deadline'][] = $pre . sprintf($this->lang->task->error->endGtExecution, $this->lang->execution->common, $execution->end);
|
||||
if(!empty($deadline) and !helper::isZeroDate($deadline) and $deadline < $execution->begin) dao::$errors['deadline'][] = $pre . sprintf($this->lang->task->error->endLtExecution, $this->lang->execution->common, $execution->begin);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建任务后的其他数据处理。
|
||||
* Other data processing after task creation.
|
||||
*
|
||||
* @param object $task
|
||||
* @param array $taskIdList
|
||||
* @param int $bugID
|
||||
* @param object $rawData
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function afterCreate(object $task, array $taskIdList, int $bugID, int $todoID, array $testTasks): bool
|
||||
{
|
||||
$this->loadModel('file');
|
||||
$this->loadModel('score');
|
||||
foreach($taskIdList as $taskID)
|
||||
{
|
||||
/* If the task comes from a bug, update the task and bug information. */
|
||||
if($bugID > 0)
|
||||
{
|
||||
$this->dao->update(TABLE_TASK)->set('fromBug')->eq($bugID)->where('id')->eq($taskID)->exec();
|
||||
$this->dao->update(TABLE_BUG)->set('toTask')->eq($taskID)->where('id')->eq($bugID)->exec();
|
||||
$this->loadModel('action')->create('bug', $bugID, 'converttotask', '', $taskID);
|
||||
}
|
||||
|
||||
/* If the task comes from a todo, update the todo information. */
|
||||
if($todoID > 0)
|
||||
{
|
||||
$this->dao->update(TABLE_TODO)->set('status')->eq('done')->where('id')->eq($todoID)->exec();
|
||||
$this->loadModel('action')->create('todo', $todoID, 'finished', '', "TASK:$taskID");
|
||||
|
||||
/* If the todo comes from a feedback, update the feedback information. */
|
||||
$todo = $this->dao->findByID($todoID)->from(TABLE_TODO)->fetch();
|
||||
if($this->config->edition != 'open' and $todo->type == 'feedback' and $todo->objectID) $this->loadModel('feedback')->updateStatus('todo', $todo->idvalue, 'done');
|
||||
}
|
||||
|
||||
/* If the task comes from a design, update the task information. */
|
||||
if(!empty($task->design))
|
||||
{
|
||||
$design = $this->loadModel('design')->getByID($task->design);
|
||||
$this->dao->update(TABLE_TASK)->set('designVersion')->eq($design->version)->where('id')->eq($taskID)->exec();
|
||||
}
|
||||
|
||||
/* If the task comes from a story, update the stage of the story. */
|
||||
if($task->story) $this->loadModel('story')->setStage($task->story);
|
||||
|
||||
/* If the current task has test subtasks, create test subtasks and update the task information. */
|
||||
if(!empty($testTasks))
|
||||
{
|
||||
$this->taskTao->createTestChildTasks($taskID, $testTasks);
|
||||
$this->computeWorkingHours($taskID);
|
||||
$this->computeBeginAndEnd($taskID);
|
||||
$this->dao->update(TABLE_TASK)->set('`parent`')->eq(-1)->where('id')->eq($taskID)->exec();
|
||||
}
|
||||
if(dao::isError()) return false;
|
||||
|
||||
/* Update file information and create score. */
|
||||
$this->file->updateObjectID($this->post->uid, $taskID, 'task');
|
||||
$this->score->create('task', 'create', $taskID);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新看板中的任务泳道数据。
|
||||
* Update the task lane data in Kanban.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param object $rawData
|
||||
* @param int $taskID
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function updateKanbanData(object $execution, object $task, int $laneID, int $oldColumnID): void
|
||||
{
|
||||
$this->loadModel('kanban');
|
||||
|
||||
/* Get kanban id, lane id and column id. */
|
||||
$kanbanID = $execution->type == 'kanban' ? $execution->id : $task->execution;
|
||||
$laneID = empty($laneID) ? 0 : $laneID;
|
||||
$columnID = $this->kanban->getColumnIDByLaneID($laneID, 'wait');
|
||||
if(empty($columnID)) $columnID = $oldColumnID;
|
||||
|
||||
/* If both of lane id and column id are not empty, add task to the kanban cell. */
|
||||
if($laneID and $columnID) $this->kanban->addKanbanCell($kanbanID, $laneID, $columnID, 'task', $task->id);
|
||||
/* If lane id or column id is empty, update the task type lane of the kanban. */
|
||||
if(!$laneID or !$columnID) $this->kanban->updateLane($kanbanID, 'task');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置任务的附件。
|
||||
* Set attachments for tasks.
|
||||
*
|
||||
* @param array $taskFiles
|
||||
* @param int $taskID
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function setTaskFiles(array $taskFiles, int $taskID): array
|
||||
{
|
||||
/* If taskFiles is not empty, create task files. */
|
||||
if(!empty($taskFiles))
|
||||
{
|
||||
foreach($taskFiles as $taskFile)
|
||||
{
|
||||
$taskFile->objectID = $taskID;
|
||||
$this->dao->insert(TABLE_FILE)->data($taskFile)->exec();
|
||||
}
|
||||
}
|
||||
/* If taskFiles is empty, get task files. */
|
||||
else
|
||||
{
|
||||
$taskFileTitle = $this->loadModel('file')->saveUpload('task', $taskID);
|
||||
$taskFiles = $this->dao->select('*')->from(TABLE_FILE)->where('id')->in(array_keys($taskFileTitle))->fetchAll('id');
|
||||
foreach($taskFiles as $fileID => $taskFile) unset($taskFiles[$fileID]->id);
|
||||
}
|
||||
|
||||
return $taskFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联需求的测试类型的子任务。
|
||||
* Create a subtask for the test type story with the story.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param object[] $testTasks
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function createTestChildTasks(int $taskID, array $testTasks): void
|
||||
{
|
||||
$this->loadModel('action');
|
||||
|
||||
/* Get the stories of the test tasks. */
|
||||
$testStoryIdList = array_keys($testTasks);
|
||||
$testStories = $this->dao->select('id,title,version,module')->from(TABLE_STORY)->where('id')->in($testStoryIdList)->fetchAll('id');
|
||||
foreach($testStoryIdList as $i => $storyID)
|
||||
{
|
||||
/* If the story id is not exist, skip it. */
|
||||
if(!isset($testStories[$storyID])) continue;
|
||||
|
||||
/* Construct a task and create it. */
|
||||
$task = $testTasks[$storyID];
|
||||
$task->parent = $taskID;
|
||||
$task->storyVersion = $testStories[$storyID]->version;
|
||||
$task->name = $this->lang->task->lblTestStory . " #{$storyID} " . $testStories[$storyID]->title;
|
||||
$task->module = $testStories[$storyID]->module;
|
||||
$this->dao->insert(TABLE_TASK)->data($task)->exec();
|
||||
|
||||
/* Get task id and create a action. */
|
||||
$childTaskID = $this->dao->lastInsertID();
|
||||
$this->action->create('task', $childTaskID, 'Opened');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+285
-9
@@ -13,8 +13,45 @@ declare(strict_types=1);
|
||||
class taskTao extends taskModel
|
||||
{
|
||||
/**
|
||||
* Compute progress of a task.
|
||||
* 创建一个任务。
|
||||
* Create a task.
|
||||
*
|
||||
* @param object $task
|
||||
* @access protected
|
||||
* @return int|bool
|
||||
*/
|
||||
protected function doCreate(object $task): int|bool
|
||||
{
|
||||
/* Insert task data. */
|
||||
$this->dao->insert(TABLE_TASK)->data($task)
|
||||
->checkIF($task->estimate != '', 'estimate', 'float')
|
||||
->autoCheck()
|
||||
->batchCheck($this->config->task->create->requiredFields, 'notempty')
|
||||
->checkFlow()
|
||||
->exec();
|
||||
|
||||
if(dao::isError()) return false;
|
||||
|
||||
/* Get task id. */
|
||||
$taskID = (int)$this->dao->lastInsertID();
|
||||
|
||||
/* Insert task desc data. */
|
||||
$taskSpec = new stdClass();
|
||||
$taskSpec->task = $taskID;
|
||||
$taskSpec->version = $task->version;
|
||||
$taskSpec->name = $task->name;
|
||||
$taskSpec->estStarted = $task->estStarted ? $task->estStarted : null;
|
||||
$taskSpec->deadline = $task->deadline ? $task->deadline : null;
|
||||
$this->dao->insert(TABLE_TASKSPEC)->data($taskSpec)->autoCheck()->exec();
|
||||
|
||||
if(dao::isError()) return false;
|
||||
|
||||
return $taskID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务的进度。
|
||||
* Compute progress of a task.
|
||||
*
|
||||
* @param object $task
|
||||
* @access protected
|
||||
@@ -28,8 +65,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute progress of task list, include its' children.
|
||||
* 计算任务列表中每个任务的进度,包括子任务。
|
||||
* Compute progress of task list, include its' children.
|
||||
*
|
||||
* @param object[] $tasks
|
||||
* @access protected
|
||||
@@ -49,8 +86,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tasks under execution by executionID(Todo).
|
||||
* 获取执行下的任务。
|
||||
* Fetch tasks under execution by executionID(Todo).
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param int $productID
|
||||
@@ -110,6 +147,7 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过任务类型查找用户的任务。
|
||||
* Fetch user tasks by type.
|
||||
*
|
||||
* @param string $account
|
||||
@@ -153,8 +191,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task team by id list.
|
||||
* 通过任务ID列表查询任务团队信息。
|
||||
* Get task team by id list.
|
||||
*
|
||||
* @param array $taskIdList
|
||||
* @access protected
|
||||
@@ -166,8 +204,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get task list by report.
|
||||
* 根据报表条件查询任务.
|
||||
* Get task list by report.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $condition
|
||||
@@ -182,8 +220,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assignedTo for the multiply linear task.
|
||||
* 获取多人串行任务的指派人。
|
||||
* Get the assignedTo for the multiply linear task.
|
||||
*
|
||||
* @param string|array $members
|
||||
* @param object $task
|
||||
@@ -216,8 +254,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the hierarchy of tasks to a parent-child structure.
|
||||
* 将任务的层级改为父子结构。
|
||||
* Change the hierarchy of tasks to a parent-child structure.
|
||||
*
|
||||
* @param object[] $tasks
|
||||
* @param object[] $parentTasks
|
||||
@@ -245,8 +283,8 @@ class taskTao extends taskModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the status of the current task.
|
||||
* 计算当前任务的状态。
|
||||
* Compute the status of the current task.
|
||||
*
|
||||
* @param object $currentTask
|
||||
* @param object $oldTask
|
||||
@@ -259,15 +297,18 @@ class taskTao extends taskModel
|
||||
*/
|
||||
protected function computeCurrentTaskStatus(object $currentTask, object $oldTask, object $task, bool $autoStatus, bool $hasEfforts, array $members): object
|
||||
{
|
||||
/* If the status is not automatic, return the current task. */
|
||||
if(!$autoStatus) return $currentTask;
|
||||
|
||||
if($currentTask->consumed == 0 and $hasEfforts)
|
||||
/* If consumed of the current task is empty and current task has no efforts, the current task status should be wait. */
|
||||
if($currentTask->consumed == 0 and !$hasEfforts)
|
||||
{
|
||||
if(!isset($task->status)) $currentTask->status = 'wait';
|
||||
$currentTask->finishedBy = null;
|
||||
$currentTask->finishedDate = null;
|
||||
}
|
||||
|
||||
/* If neither consumed nor left of the current task is empty, the current task status should be doing. */
|
||||
if($currentTask->consumed > 0 && $currentTask->left > 0)
|
||||
{
|
||||
$currentTask->status = 'doing';
|
||||
@@ -275,9 +316,11 @@ class taskTao extends taskModel
|
||||
$currentTask->finishedDate = null;
|
||||
}
|
||||
|
||||
/* If consumed of the current task is not empty and left of the current task is empty, the current task status should be done or doing. */
|
||||
if($currentTask->consumed > 0 and $currentTask->left == 0)
|
||||
{
|
||||
$finisedUsers = $this->getFinishedUsers($oldTask->id, $members);
|
||||
/* If the number of finisher is less than the number of team members , the current task status should be doing. */
|
||||
if(count($finisedUsers) != count($members))
|
||||
{
|
||||
if(strpos('cancel,pause', $oldTask->status) === false or ($oldTask->status == 'closed' and $oldTask->reason == 'done'))
|
||||
@@ -287,6 +330,7 @@ class taskTao extends taskModel
|
||||
$currentTask->finishedDate = null;
|
||||
}
|
||||
}
|
||||
/* If status of old task is wait or doing or pause, the current task status should be done. */
|
||||
elseif(strpos('wait,doing,pause', $oldTask->status) !== false)
|
||||
{
|
||||
$currentTask->status = 'done';
|
||||
@@ -332,4 +376,236 @@ class taskTao extends taskModel
|
||||
->where('id')->eq($taskID)
|
||||
->exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据条件移除创建任务的必填项。
|
||||
* Remove required fields for creating tasks based on conditions.
|
||||
*
|
||||
* @param object $task
|
||||
* @param bool $selectTestStory
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function removeCreateRequiredFields(object $task, bool $selectTestStory): void
|
||||
{
|
||||
/* Get create required fields and the execution of the task. */
|
||||
$requiredFields = "," . $this->config->task->create->requiredFields . ",";
|
||||
$execution = $this->dao->findByID($task->execution)->from(TABLE_PROJECT)->fetch();
|
||||
|
||||
/* If the lifetime if the execution is ops and the attribute of execution is request or review, remove story from required fields. */
|
||||
if($execution->lifetime == 'ops' or in_array($execution->attribute, array('request', 'review')))
|
||||
{
|
||||
$requiredFields = str_replace(",story,", ',', "$requiredFields");
|
||||
}
|
||||
|
||||
/* If the type of the task is test and select story is true, remove some required fields. */
|
||||
if($task->type == 'test' and $selectTestStory)
|
||||
{
|
||||
$requiredFields = str_replace(array(",estimate,", ",story,", ",estStarted,", ",deadline,", ",module,"), ',', "$requiredFields");
|
||||
}
|
||||
|
||||
$this->config->task->create->requiredFields = trim($requiredFields, ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* 在批量创建之前移除post数据中重复的数据。
|
||||
* Remove the duplicate data before batch create tasks.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @param object $tasks
|
||||
* @access protected
|
||||
* @return object|false
|
||||
*/
|
||||
protected function removeDuplicate4BatchCreate(int $executionID, object $tasks): object|false
|
||||
{
|
||||
$storyIDs = array();
|
||||
$taskNames = array();
|
||||
|
||||
foreach($tasks->story as $key => $storyID)
|
||||
{
|
||||
/* 过滤事务型和任务名称为空的数据。 */
|
||||
if(empty($tasks->name[$key])) continue;
|
||||
if($tasks->type[$key] == 'affair') continue;
|
||||
if($tasks->type[$key] == 'ditto' and isset($tasks->type[$key - 1]) and $tasks->type[$key - 1] == 'affair') continue;
|
||||
|
||||
if($storyID == 'ditto') $storyID = $preStory;
|
||||
$preStory = $storyID;
|
||||
|
||||
if(!isset($tasks->story[$key - 1]) and $key > 1 and !empty($tasks->name[$key - 1]))
|
||||
{
|
||||
$storyIDs[] = 0;
|
||||
$taskNames[] = $tasks->name[$key - 1];
|
||||
}
|
||||
|
||||
/* 判断Post传过来的任务有没有重复数据。 */
|
||||
$hasExistsName = in_array($tasks->name[$key], $taskNames);
|
||||
if($hasExistsName and in_array($storyID, $storyIDs))
|
||||
{
|
||||
dao::$errors['message'][] = sprintf($this->lang->duplicate, $this->lang->task->common) . ' ' . $tasks->name[$key];
|
||||
return false;
|
||||
}
|
||||
|
||||
$storyIDs[] = $storyID;
|
||||
$taskNames[] = $tasks->name[$key];
|
||||
}
|
||||
|
||||
/* 去重并赋值。 */
|
||||
$result = $this->loadModel('common')->removeDuplicate('task', $tasks, "execution=$executionID and story " . helper::dbIN($storyIDs));
|
||||
return $result['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建任务之前构造数据。
|
||||
* Construct data before batch create tasks.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param object $tasks
|
||||
* @param int $index
|
||||
* @param array $dittoFields
|
||||
* @param array $extendFields
|
||||
* @access protected
|
||||
* @return array
|
||||
*/
|
||||
protected function constructData4BatchCreate(object $execution, object $tasks, int $index, array $dittoFields, array $extendFields): array
|
||||
{
|
||||
extract($dittoFields);
|
||||
$now = helper::now();
|
||||
|
||||
$data[$index] = new stdclass();
|
||||
$data[$index]->story = (int)$story;
|
||||
$data[$index]->type = $type;
|
||||
$data[$index]->module = (int)$module;
|
||||
$data[$index]->assignedTo = $assignedTo;
|
||||
$data[$index]->color = $tasks->color[$index];
|
||||
$data[$index]->name = $tasks->name[$index];
|
||||
$data[$index]->desc = nl2br($tasks->desc[$index]);
|
||||
$data[$index]->pri = $tasks->pri[$index];
|
||||
$data[$index]->estimate = $tasks->estimate[$index] ? $tasks->estimate[$index] : 0;
|
||||
$data[$index]->left = $tasks->estimate[$index] ? $tasks->estimate[$index] : 0;
|
||||
$data[$index]->project = $execution->project;
|
||||
$data[$index]->execution = $execution->id;
|
||||
$data[$index]->estStarted = $estStarted;
|
||||
$data[$index]->deadline = $deadline;
|
||||
$data[$index]->status = 'wait';
|
||||
$data[$index]->openedBy = $this->app->user->account;
|
||||
$data[$index]->openedDate = $now;
|
||||
$data[$index]->parent = $tasks->parent[$index];
|
||||
$data[$index]->vision = isset($tasks->vision[$index]) ? $tasks->vision[$index] : 'rnd';
|
||||
if($story) $data[$index]->storyVersion = (int)$this->dao->findById($data[$index]->story)->from(TABLE_STORY)->fetch('version');
|
||||
if($assignedTo) $data[$index]->assignedDate = $now;
|
||||
if(strpos($this->config->task->create->requiredFields, 'estStarted') !== false and empty($estStarted)) $data[$index]->estStarted = '';
|
||||
if(strpos($this->config->task->create->requiredFields, 'deadline') !== false and empty($deadline)) $data[$index]->deadline = '';
|
||||
if(isset($tasks->lanes[$index])) $data[$index]->laneID = $tasks->lanes[$index];
|
||||
|
||||
/* 附加工作流字段。 */
|
||||
foreach($extendFields as $extendField)
|
||||
{
|
||||
$data[$index]->{$extendField->field} = $tasks->{$extendField->field}[$index];
|
||||
if(is_array($data[$index]->{$extendField->field})) $data[$index]->{$extendField->field} = join(',', $data[$index]->{$extendField->field});
|
||||
|
||||
$data[$index]->{$extendField->field} = htmlSpecialString($data[$index]->{$extendField->field});
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建前检查必填项
|
||||
* Check required fields before batch create tasks.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param object[] $data
|
||||
* @access protected
|
||||
* @return object[]|false
|
||||
*/
|
||||
protected function checkRequired4BatchCreate(object $execution, array $data): array|false
|
||||
{
|
||||
/* 设置必填项。 */
|
||||
$requiredFields = ',' . $this->config->task->create->requiredFields . ',';
|
||||
if($execution->lifetime == 'ops' or $execution->attribute == 'request' or $execution->attribute == 'review') $requiredFields = str_replace(',story,', ',', $requiredFields);
|
||||
$requiredFields = trim($requiredFields, ',');
|
||||
|
||||
/* check data. */
|
||||
foreach($data as $i => $task)
|
||||
{
|
||||
/* 检查任务是否开启了起止日期必填的配置(limitTaskDate)。 */
|
||||
if(!empty($this->config->limitTaskDate))
|
||||
{
|
||||
$this->checkEstStartedAndDeadline($execution->id, $task->estStarted, $task->deadline);
|
||||
if(dao::isError()) return false;
|
||||
}
|
||||
|
||||
/* 检查任务截止日期是否为空以及是否小于预计开始日期。 */
|
||||
if(!helper::isZeroDate($task->deadline) and $task->deadline < $task->estStarted)
|
||||
{
|
||||
dao::$errors['message'][] = $this->lang->task->error->deadlineSmall;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 检查任务预计是否为数字类型。 */
|
||||
if($task->estimate and !preg_match("/^[0-9]+(.[0-9]{1,3})?$/", $task->estimate))
|
||||
{
|
||||
dao::$errors['message'][] = $this->lang->task->error->estimateNumber;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 验证必填字段。 */
|
||||
$requiredFields = array_filter(explode(',', $requiredFields));
|
||||
foreach($requiredFields as $field)
|
||||
{
|
||||
if(empty($task->$field))
|
||||
{
|
||||
dao::$errors['message'][] = sprintf($this->lang->error->notempty, $this->lang->task->$field);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if($task->estimate) $task->estimate = (float)$task->estimate;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拆分已消耗的任务。
|
||||
* Split the consumed task.
|
||||
*
|
||||
* @param object $parentTask
|
||||
* @access protected
|
||||
* @return bool
|
||||
*/
|
||||
protected function splitConsumedTask(object $parentTask): bool
|
||||
{
|
||||
$clonedTask = clone $parentTask;
|
||||
$clonedTask->parent = $parentTask->id;
|
||||
unset($clonedTask->id);
|
||||
$this->dao->insert(TABLE_TASK)->data($clonedTask)->autoCheck()->exec();
|
||||
if(dao::isError()) return false;
|
||||
|
||||
$clonedTaskID = $this->dao->lastInsertID();
|
||||
|
||||
$this->dao->update(TABLE_EFFORT)->set('objectID')->eq($clonedTaskID)
|
||||
->where('objectID')->eq($parentTask->id)
|
||||
->andWhere('objectType')->eq('task')
|
||||
->exec();
|
||||
if(dao::isError()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拆分之后更新父任务的parent、lastEditedBy、lastEditedDate字段。
|
||||
* Update the parent's parent, lastEditedBy, and lastEditedDate fields after split.
|
||||
*
|
||||
* @param int $parentID
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function updateParentAfterSplit(int $parentID): void
|
||||
{
|
||||
$task = new stdclass();
|
||||
$task->parent = '-1';
|
||||
$task->lastEditedBy = $this->app->user->account;
|
||||
$task->lastEditedDate = helper::now();
|
||||
$this->dao->update(TABLE_TASK)->data($task)->where('id')->eq($parentID)->exec();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ su('admin');
|
||||
|
||||
/**
|
||||
|
||||
title=taskModel->getTaskEstimate();
|
||||
title=taskModel->getTaskEfforts();
|
||||
cid=1
|
||||
pid=1
|
||||
|
||||
@@ -19,4 +19,4 @@ $waitstart = array('assignedTo' => 'user92','consumed' => '10');
|
||||
|
||||
$task = new taskTest();
|
||||
$task->startTest($taskID,$waitstart);
|
||||
r($task->getTaskEstimateTest($taskID)) && p('0:task') && e('55'); // 查看任务预计
|
||||
r($task->getTaskEffortsTest($taskID)) && p('0:task') && e('55'); // 查看任务预计
|
||||
@@ -652,7 +652,7 @@ class taskTest
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get task estimate.
|
||||
* Test get task efforts.
|
||||
*
|
||||
* @param int $taskID
|
||||
* @param string $account
|
||||
@@ -660,9 +660,9 @@ class taskTest
|
||||
* @access public
|
||||
* @return object
|
||||
*/
|
||||
public function getTaskEstimateTest($taskID, $account = '', $append = '')
|
||||
public function getTaskEffortsTest($taskID, $account = '', $append = '')
|
||||
{
|
||||
$object = $this->objectModel->getTaskEstimate($taskID, $account, $append);
|
||||
$object = $this->objectModel->getTaskEfforts($taskID, $account, $append);
|
||||
if(dao::isError())
|
||||
{
|
||||
$error = dao::getError();
|
||||
|
||||
+744
-1
@@ -11,6 +11,16 @@ declare(strict_types=1);
|
||||
*/
|
||||
class taskZen extends task
|
||||
{
|
||||
|
||||
/**
|
||||
* 执行数组。
|
||||
* Execution's [id] => 'name' pairs.
|
||||
*
|
||||
* @var array
|
||||
* @access private
|
||||
*/
|
||||
private $executionPairs = array();
|
||||
|
||||
/**
|
||||
* 准备创建数据。
|
||||
* Prepare edit data.
|
||||
@@ -53,7 +63,6 @@ class taskZen extends task
|
||||
->setIF($oldTask->parent < 0, 'left', $oldTask->left)
|
||||
|
||||
->setIF($oldTask->name != $postData->name || $oldTask->estStarted != $postData->estStarted || $oldTask->deadline != $postData->deadline, 'version', $oldTask->version + 1)
|
||||
|
||||
->add('lastEditedDate', $now)
|
||||
->stripTags($this->config->task->editor->edit['id'], $this->config->allowedTags)
|
||||
->join('mailto', ',')
|
||||
@@ -255,4 +264,738 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过传入的对象ID设置任务信息。
|
||||
* Set task information through the incoming object ID.
|
||||
*
|
||||
* @param int $storyID
|
||||
* @param int $moduleID
|
||||
* @param int $taskID
|
||||
* @param int $todoID
|
||||
* @param int $bugID
|
||||
* @access private
|
||||
* @return object
|
||||
*/
|
||||
private function setTaskInfoByObjectID(int $storyID, int $moduleID, int $taskID, int $todoID, int $bugID): object
|
||||
{
|
||||
$task = $this->config->task->create->template;
|
||||
$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 int $executionID
|
||||
* @param array $output
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function showKanbanRelatedVars(int $executionID, 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($executionID, 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。
|
||||
* Show the ID of the block to be processed on the my.
|
||||
*
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function showAssignedToMeBlockID(): 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示执行相关数据。
|
||||
* Show execution related data.
|
||||
*
|
||||
* @param object $execution
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function showExecutionData(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. */
|
||||
$taskID = $task->id;
|
||||
if($this->viewType == 'json' or (defined('RUN_MODE') && RUN_MODE == 'api')) return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'id' => $taskID);
|
||||
|
||||
$response['result'] = 'success';
|
||||
$response['message'] = $this->lang->saveSuccess;
|
||||
|
||||
/* Send Webhook notifications. */
|
||||
$message = $this->executeHooks($taskID);
|
||||
if($message) $response['message'] = $message;
|
||||
|
||||
/* Processing the return information of pop-up windows. */
|
||||
if(isonlybody())
|
||||
{
|
||||
/* If it is Kanban execution, refresh the Kanban statically through callback. */
|
||||
if($this->app->tab == 'execution' or $this->config->vision == 'lite')
|
||||
{
|
||||
$kanbanData = $this->getKanbanData($execution);
|
||||
$response['closeModal'] = true;
|
||||
$response['callback'] = $execution->type == 'kanban' ? "parent.updateKanban($kanbanData, 0)" : "parent.updateKanban(\"task\", $kanbanData)";
|
||||
return $response;
|
||||
}
|
||||
$response['locate'] = 'parent';
|
||||
return $response;
|
||||
}
|
||||
|
||||
/* Locate the browser. */
|
||||
if($this->app->getViewType() == 'xhtml')
|
||||
{
|
||||
$response['locate'] = $this->createLink('task', 'view', "taskID=$taskID", 'html');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/* Process the return information for selecting a jump after creation. */
|
||||
return $this->getLocateAfterCreate($task, $execution->id, $afterChoice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理创建后选择跳转的返回信息。
|
||||
* Process the return information for selecting a jump after creation.
|
||||
*
|
||||
* @param object $task
|
||||
* @param int $executionID
|
||||
* @param string $afterChoice
|
||||
* @access private
|
||||
* @return array
|
||||
*/
|
||||
private function getLocateAfterCreate(object $task, int $executionID, string $afterChoice): array
|
||||
{
|
||||
/* Set the universal return value. */
|
||||
$response['result'] = 'success';
|
||||
$response['message'] = $this->lang->saveSuccess;
|
||||
$response['locate'] = $this->createLink('execution', 'browse', "executionID=$executionID&tab=task");
|
||||
|
||||
/* Set the response to continue adding task to story. */
|
||||
$executionID = $task->execution;
|
||||
if($afterChoice == 'continueAdding')
|
||||
{
|
||||
$storyID = $task->story ? $task->story : 0;
|
||||
$moduleID = $task->module ? $task->module : 0;
|
||||
$response['message'] = $this->lang->task->successSaved . $this->lang->task->afterChoices['continueAdding'];
|
||||
$response['locate'] = $this->createLink('task', 'create', "executionID=$executionID&storyID=$storyID&moduleID=$moduleID");
|
||||
}
|
||||
/* Set the response to return task list. */
|
||||
elseif($afterChoice == 'toTaskList')
|
||||
{
|
||||
setcookie('moduleBrowseParam', 0, 0, $this->config->webRoot, '', $this->config->cookieSecure, false);
|
||||
$response['locate'] = $this->createLink('execution', 'task', "executionID=$executionID&status=unclosed¶m=0&orderBy=id_desc");
|
||||
}
|
||||
/* Set the response to return story list. */
|
||||
elseif($afterChoice == 'toStoryList')
|
||||
{
|
||||
$response['locate'] = $this->createLink('execution', 'story', "executionID=$executionID");
|
||||
if($this->config->vision == 'lite')
|
||||
{
|
||||
$projectID = $this->execution->getProjectID($executionID);
|
||||
$response['locate'] = $this->createLink('projectstory', 'story', "projectID=$projectID");
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示需求相关变量。
|
||||
* Show requirements related variables.
|
||||
*
|
||||
* @param int $executionID
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function showStoryVars(int $executionID): void
|
||||
{
|
||||
$stories = $this->story->getExecutionStoryPairs($executionID, 0, 'all', '', '', 'active');
|
||||
$testStoryIdList = $this->loadModel('story')->getTestStories(array_keys($stories), $executionID);
|
||||
$testStories = array();
|
||||
foreach($stories as $testStoryID => $storyTitle)
|
||||
{
|
||||
if(empty($testStoryID) or isset($testStoryIdList[$testStoryID])) continue;
|
||||
$testStories[$testStoryID] = $storyTitle;
|
||||
}
|
||||
$this->view->testStories = $testStories;
|
||||
$this->view->testStoryIdList = $testStoryIdList;
|
||||
$this->view->stories = $stories;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示创建任务的相关变量。
|
||||
* Show the variables associated with the creation task.
|
||||
*
|
||||
* @param object $execution
|
||||
* @param int $storyID
|
||||
* @param int $moduleID
|
||||
* @param int $taskID
|
||||
* @param int $todoID
|
||||
* @param int $bugID
|
||||
* @param array $output
|
||||
* @access protected
|
||||
* @return void
|
||||
*/
|
||||
protected function showCreateVars(object $execution, int $storyID, int $moduleID, int $taskID, int $todoID, int $bugID, array $output): void
|
||||
{
|
||||
/* Get information about the task. */
|
||||
$executionID = $execution->id;
|
||||
$task = $this->setTaskInfoByObjectID($storyID, $moduleID, $taskID, $todoID, $bugID);
|
||||
|
||||
/* Get module information. */
|
||||
$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;
|
||||
|
||||
/* Display relevant variables. */
|
||||
$this->showAssignedToMeBlockID();
|
||||
$this->showExecutionData($execution);
|
||||
$this->showStoryVars($executionID);
|
||||
if($execution->type == 'kanban') $this->showKanbanRelatedVars($executionID, $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;
|
||||
|
||||
$this->display();
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备创建任务前的数据信息。
|
||||
* 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($execution->id, $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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class todo extends control
|
||||
$todo = $this->todoZen->prepareCreateData($todoData, $uid);
|
||||
if(!$todo) return print(js::error(dao::getError()));
|
||||
|
||||
$todoID = $this->todo->create($todo, $formData);
|
||||
$todoID = $this->todo->create($todo);
|
||||
if($todoID === false) return print(js::error(dao::getError()));
|
||||
|
||||
$todo->id = $todoID;
|
||||
|
||||
+4
-4
@@ -82,8 +82,8 @@ class todoTao extends todoModel
|
||||
{
|
||||
$this->dao->insert(TABLE_TODO)->data($todo)
|
||||
->autoCheck()
|
||||
->checkIF(!in_array($todo->type, $this->config->todo->moduleList), $this->config->todo->create->requiredFields, 'notempty')
|
||||
->checkIF(in_array($todo->type, $this->config->todo->moduleList) && $todo->objectID == 0, 'objectID', 'notempty')
|
||||
->check($this->config->todo->create->requiredFields, 'notempty')
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, $this->config->todo->moduleList), 'objectID', 'notempty')
|
||||
->exec();
|
||||
|
||||
return (int)$this->dao->lastInsertID();
|
||||
@@ -102,8 +102,8 @@ class todoTao extends todoModel
|
||||
{
|
||||
$this->dao->update(TABLE_TODO)->data($todo)
|
||||
->autoCheck()
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, array('custom', 'feedback')), $this->config->todo->edit->requiredFields, 'notempty')
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, $this->config->todo->moduleList) && $todo->objectID == 0, 'objectID', 'notempty')
|
||||
->check($this->config->todo->edit->requiredFields, 'notempty')
|
||||
->checkIF(isset($todo->type) && in_array($todo->type, $this->config->todo->moduleList), 'objectID', 'notempty')
|
||||
->where('id')->eq($todoID)
|
||||
->exec();
|
||||
|
||||
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
include dirname(__FILE__, 2) . '/todo.class.php';
|
||||
su('admin');
|
||||
|
||||
function initData()
|
||||
{
|
||||
zdTable('todo')->config('fetch')->gen(10);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试完成待办 todoModel->finish();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 查询id=1的todo并且验证状态
|
||||
- 属性id @1
|
||||
- 属性status @wait
|
||||
|
||||
- 查询id=9的todo并且验证状态
|
||||
- 属性id @9
|
||||
- 属性status @done
|
||||
|
||||
*/
|
||||
|
||||
initData();
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('todo')->todoTao;
|
||||
|
||||
r($tester->todo->fetch(1)) && p('id,status') && e('1,wait'); // 查询id=1的todo并且验证状态
|
||||
r($tester->todo->fetch(9)) && p('id,status') && e('9,done'); // 查询id=9的todo并且验证状态
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
su('admin');
|
||||
|
||||
function initData()
|
||||
{
|
||||
zdTable('todo')->config('getcountbyaccount')->gen(20);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试 todoModel->getCount();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 获取用户admin的所有待办个数 @4
|
||||
|
||||
- 获取用户user1的所有待办个数 @3
|
||||
|
||||
- 获取用户user2的所有待办个数 @3
|
||||
|
||||
- 获取不存在的用户所有待办个数 @0
|
||||
|
||||
- 获取用户admin的vision=>rand所有待办个数 @4
|
||||
|
||||
*/
|
||||
|
||||
initData();
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('todo')->todoTao;
|
||||
|
||||
$accountList = array('admin', 'user1', 'user2', 'user10001');
|
||||
|
||||
r($tester->todo->getCountByAccount($accountList[0])) && p() && e('4'); // 获取用户admin的所有待办个数
|
||||
r($tester->todo->getCountByAccount($accountList[1])) && p() && e('3'); // 获取用户user1的所有待办个数
|
||||
r($tester->todo->getCountByAccount($accountList[2])) && p() && e('3'); // 获取用户user2的所有待办个数
|
||||
r($tester->todo->getCountByAccount($accountList[3])) && p() && e('0'); // 获取不存在的用户所有待办个数
|
||||
r($tester->todo->getCountByAccount($accountList[0],'rnd')) && p() && e('4'); // 获取用户admin的vision=>rand所有待办个数
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . "/test/lib/init.php";
|
||||
include dirname(__FILE__, 4) . '/task/test/task.class.php';
|
||||
su('admin');
|
||||
|
||||
function initData()
|
||||
{
|
||||
zdTable('todo')->config('getprojectlist')->gen(10);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试 todoModel->getCount();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 验证task获得的键值对的个数 @4
|
||||
|
||||
*/
|
||||
|
||||
initData();
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('todo');
|
||||
|
||||
$taskData1 = array('id' => 10, 'name' => '开发任务一', 'type' => 'devel');
|
||||
$taskData2 = array('id' => 11, 'name' => '开发任务二', 'type' => 'devel');
|
||||
$taskData3 = array('id' => 12, 'name' => '开发任务三', 'type' => 'devel');
|
||||
$taskData4 = array('id' => 13, 'name' => '开发任务四', 'type' => 'devel');
|
||||
|
||||
$task = new taskTest();
|
||||
$task->createObject($taskData1,101);
|
||||
$task->createObject($taskData2,102);
|
||||
$task->createObject($taskData3,103);
|
||||
$task->createObject($taskData4,104);
|
||||
|
||||
$ids = range(1,10);
|
||||
$list = $tester->todo->getByList($ids);
|
||||
$projectIDList = array_column($list, 'objectID');
|
||||
|
||||
$tester->loadModel('todo')->todoTao;
|
||||
$projectList = $tester->todo->getProjectList('zt_task', $projectIDList);
|
||||
|
||||
r(count($projectList)) && p() && e('4'); //验证task获得的键值对的个数
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
su('admin');
|
||||
|
||||
function initData ()
|
||||
{
|
||||
zdTable('todo')->gen(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试 todoTao::insert();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 正常插入待办数据 @2
|
||||
|
||||
- 正常插入task类型待办数据 @3
|
||||
|
||||
- 插入没有名称的待办数据 @0
|
||||
|
||||
- 插入bug类型待办,没有bugID的情况 @0
|
||||
|
||||
*/
|
||||
|
||||
initData();
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('todo');
|
||||
|
||||
$todo = new stdClass();
|
||||
$todo->account = 'admin';
|
||||
$todo->date = date('Y-m-d');
|
||||
$todo->begin = '1000';
|
||||
$todo->end = '1400';
|
||||
$todo->type = 'custom';
|
||||
$todo->name = '我的待办名字';
|
||||
$todo->desc = '我的待办描述';
|
||||
$todo->status = 'wait';
|
||||
|
||||
$todoNoName = clone $todo;
|
||||
$todoNoName->name = '';
|
||||
|
||||
$todoBug = clone $todo;
|
||||
$todoBug->type = 'bug';
|
||||
$todoBug->name = 'BUG待办';
|
||||
$todoBug->objectID = 0;
|
||||
|
||||
$todoTask = clone $todo;
|
||||
$todoTask->type = 'task';
|
||||
$todoTask->name = 'TASK待办';
|
||||
$todoTask->objectID = 1;
|
||||
|
||||
r($tester->todo->insert($todo)) && p() && e(2); // 正常插入待办数据
|
||||
r($tester->todo->insert($todoTask)) && p() && e(3); // 正常插入task类型待办数据
|
||||
r($tester->todo->insert($todoNoName)) && p() && e(0); // 插入没有名称的待办数据
|
||||
r($tester->todo->insert($todoBug)) && p() && e(0); // 插入bug类型待办,没有bugID的情况
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
include dirname(__FILE__, 5) . '/test/lib/init.php';
|
||||
su('admin');
|
||||
|
||||
function initData ()
|
||||
{
|
||||
zdTable('todo')->gen(1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
title=测试 todoTao::updateRow();
|
||||
timeout=0
|
||||
cid=1
|
||||
|
||||
- 正常更新待办数据 @1
|
||||
|
||||
- 正常更新task类型待办数据 @1
|
||||
|
||||
- 更新没有名称的待办数据 @0
|
||||
|
||||
- 更新bug类型待办,没有bugID的情况 @0
|
||||
|
||||
*/
|
||||
|
||||
initData();
|
||||
|
||||
global $tester;
|
||||
$tester->loadModel('todo');
|
||||
|
||||
$todo = new stdClass();
|
||||
$todo->account = 'admin';
|
||||
$todo->date = date('Y-m-d');
|
||||
$todo->begin = '1000';
|
||||
$todo->end = '1400';
|
||||
$todo->type = 'custom';
|
||||
$todo->name = '我的待办名字';
|
||||
$todo->desc = '我的待办描述';
|
||||
$todo->status = 'wait';
|
||||
|
||||
$todoNoName = clone $todo;
|
||||
$todoNoName->name = '';
|
||||
|
||||
$todoBug = clone $todo;
|
||||
$todoBug->type = 'bug';
|
||||
$todoBug->name = 'BUG待办';
|
||||
$todoBug->objectID = 0;
|
||||
|
||||
$todoTask = clone $todo;
|
||||
$todoTask->type = 'task';
|
||||
$todoTask->name = 'TASK待办';
|
||||
$todoTask->objectID = 1;
|
||||
|
||||
r($tester->todo->updateRow(1, $todo)) && p() && e('1'); // 正常更新待办数据
|
||||
r($tester->todo->updateRow(2, $todoTask)) && p() && e('1'); // 正常更新task类型待办数据
|
||||
r($tester->todo->updateRow(3, $todoNoName)) && p() && e('0'); // 更新没有名称的待办数据
|
||||
r($tester->todo->updateRow(4, $todoBug)) && p() && e('0'); // 更新bug类型待办,没有bugID的情况
|
||||
@@ -0,0 +1,35 @@
|
||||
title: zt_fetch
|
||||
author: liuchenglong
|
||||
version: "1.0"
|
||||
fields:
|
||||
- field: id
|
||||
range: 1-100
|
||||
- field: account
|
||||
range: 'admin'
|
||||
- field: date
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
- field: begin
|
||||
range: 1000-1100
|
||||
- field: end
|
||||
range: 1200-1300
|
||||
- field: type
|
||||
range: 'custom'
|
||||
- field: cycle
|
||||
range: 0
|
||||
- field: pri
|
||||
range: 3
|
||||
- field: name
|
||||
range: 1-5
|
||||
prefix: '我的待办'
|
||||
- field: desc
|
||||
range: 1-5
|
||||
prefix: '这是待办描述'
|
||||
- field: status
|
||||
range: 'wait,doing,done'
|
||||
- field: finishedDate
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
- field: closedDate
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
@@ -0,0 +1,38 @@
|
||||
title: zt_getcountbyaccount
|
||||
author: liuchenglong
|
||||
version: "1.0"
|
||||
fields:
|
||||
- field: id
|
||||
range: 1-100
|
||||
- field: account
|
||||
range: 'admin,user1,user2'
|
||||
- field: date
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
- field: begin
|
||||
range: 1000-1100
|
||||
- field: end
|
||||
range: 1200-1300
|
||||
- field: type
|
||||
range: 'custom'
|
||||
- field: cycle
|
||||
range: 0
|
||||
- field: pri
|
||||
range: 3
|
||||
- field: name
|
||||
range: 1-5
|
||||
prefix: '我的待办'
|
||||
- field: desc
|
||||
range: 1-5
|
||||
prefix: '这是待办描述'
|
||||
- field: status
|
||||
range: 'wait,doing,done'
|
||||
- field: vision
|
||||
range: 'rnd,litle'
|
||||
- field: finishedDate
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
- field: closedDate
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
title: zt_getprojectlist
|
||||
author: liuchenglong
|
||||
version: "1.0"
|
||||
fields:
|
||||
- field: id
|
||||
range: 1-100
|
||||
- field: account
|
||||
range: 'admin,user1,user2'
|
||||
- field: objectID
|
||||
range: 10-20
|
||||
- field: date
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
- field: begin
|
||||
range: 1000-1100
|
||||
- field: end
|
||||
range: 1200-1300
|
||||
- field: type
|
||||
range: 'task'
|
||||
- field: cycle
|
||||
range: 0
|
||||
- field: pri
|
||||
range: 3
|
||||
- field: name
|
||||
range: 1-5
|
||||
prefix: '我的待办'
|
||||
- field: desc
|
||||
range: 1-5
|
||||
prefix: '这是待办描述'
|
||||
- field: status
|
||||
range: 'wait,doing,done'
|
||||
- field: vision
|
||||
range: 'rnd,litle'
|
||||
- field: finishedDate
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
- field: closedDate
|
||||
range: 1-9
|
||||
prefix: '2023-04-2'
|
||||
|
||||
|
||||
|
||||
@@ -99,8 +99,6 @@ fields:
|
||||
format: "YYYY-MM-DD"
|
||||
- field: closedBy
|
||||
range: "[]{50},admin{30}"
|
||||
- field: closedDate
|
||||
range:
|
||||
- field: duplicateBug
|
||||
range: 0{55},1-5,0{40},10-15
|
||||
- field: linkBug
|
||||
@@ -127,7 +125,5 @@ fields:
|
||||
range: 0
|
||||
- field: lastEditedBy
|
||||
range:
|
||||
- field: lastEditedDate
|
||||
range:
|
||||
- field: deleted
|
||||
range: 0{40},1{10},0{50}
|
||||
|
||||
+1
-10
@@ -100,7 +100,7 @@ fields:
|
||||
format: ""
|
||||
- field: status
|
||||
note: "当前状态"
|
||||
range: draft,active,closed,changed
|
||||
range: draft,active,closed,changing
|
||||
prefix: ""
|
||||
postfix: ""
|
||||
loop: 0
|
||||
@@ -181,9 +181,6 @@ fields:
|
||||
postfix: ""
|
||||
loop: 0
|
||||
format: ""
|
||||
- field: lastEditedDate
|
||||
note: "最后修改日期"
|
||||
range: ""
|
||||
- field: reviewedBy
|
||||
note: "评审者"
|
||||
fields:
|
||||
@@ -194,9 +191,6 @@ fields:
|
||||
prefix: ""
|
||||
postfix: ""
|
||||
format: ""
|
||||
- field: reviewedDate
|
||||
note: "评审日期"
|
||||
range: ""
|
||||
- field: closedBy
|
||||
note: "关闭者"
|
||||
fields:
|
||||
@@ -208,9 +202,6 @@ fields:
|
||||
postfix: ""
|
||||
loop: 0
|
||||
format: ""
|
||||
- field: closedDate
|
||||
note: "关闭日期"
|
||||
range: ""
|
||||
- field: closedReason
|
||||
note: "关闭原因"
|
||||
range: done,subdivided,duplicate,postponed,willnotdo,cancel,bydesign
|
||||
|
||||
+24
-26
@@ -359,7 +359,7 @@ class yaml
|
||||
if($pos !== false) $runFileName = mb_substr($runFileName, $pos+1);
|
||||
|
||||
$runFileDir = dirname($_SERVER['SCRIPT_FILENAME']);
|
||||
|
||||
|
||||
return array($runFileDir, $runFileName);
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ class yaml
|
||||
$scriptPath = sprintf("%s%s%s.php", $runFileDir, DS, $runFileName);
|
||||
$yamlPath = sprintf("%s%sdata%s%s_%s.yaml", $runFileDir, DS, DS, $this->tableName, $runFileName);
|
||||
|
||||
if($this->checkNeedReGenerateSql($sqlPath, $scriptPath, $yamlPath))
|
||||
if($rows && $this->checkNeedReGenerateSql($sqlPath, $scriptPath, $yamlPath))
|
||||
{
|
||||
$runtimeRoot = dirname(dirname(__FILE__)) . '/runtime/';
|
||||
$zdPath = $runtimeRoot . 'zd';
|
||||
@@ -437,11 +437,6 @@ class yaml
|
||||
*/
|
||||
function insertDB($sqlPath, $tableName, $isClear = true)
|
||||
{
|
||||
$tableSqlDir = dirname($sqlPath);
|
||||
|
||||
if(!is_dir($tableSqlDir)) mkdir($tableSqlDir, 0777, true);
|
||||
$dumpCommand = "mysqldump -u%s -p%s -h%s -P%s %s %s > {$tableSqlDir}/{$tableName}.sql 2>/dev/null";
|
||||
|
||||
$tableName = $this->config->db->prefix . $tableName;
|
||||
$dbName = $this->config->db->name;
|
||||
$dbHost = $this->config->db->host;
|
||||
@@ -449,10 +444,11 @@ class yaml
|
||||
$dbUser = $this->config->db->user;
|
||||
$dbPWD = $this->config->db->password;
|
||||
|
||||
$command = "mysql -u%s -p%s -h%s -P%s --default-character-set=utf8 -D%s < %s";
|
||||
$execInsert = sprintf($command, $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $sqlPath);
|
||||
$execDump = sprintf($dumpCommand, $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $tableName);
|
||||
|
||||
$tableSqlDir = dirname($sqlPath);
|
||||
|
||||
if(!is_dir($tableSqlDir)) mkdir($tableSqlDir, 0777, true);
|
||||
$dumpCommand = "mysqldump -u%s -p%s -h%s -P%s %s %s > {$tableSqlDir}/{$tableName}.sql 2>/dev/null";
|
||||
$execDump = sprintf($dumpCommand, $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $tableName);
|
||||
system($execDump);
|
||||
|
||||
if($isClear === true)
|
||||
@@ -461,36 +457,38 @@ class yaml
|
||||
system(sprintf("mysql -u%s -p%s -h%s -P%s %s -e 'truncate %s' 2>/dev/null", $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $tableName));
|
||||
}
|
||||
|
||||
$stderr = '';
|
||||
$this->execWithStderr($execInsert, $stderr);
|
||||
|
||||
if(empty($stderr)) return;
|
||||
if(!file_exists($sqlPath)) return;
|
||||
|
||||
$errors = explode(PHP_EOL, $stderr);
|
||||
$errors = array_filter($errors, function($error)
|
||||
{
|
||||
return !empty($error) && !strpos($error, 'Using a password on the command line interface can be insecure');
|
||||
});
|
||||
|
||||
if(!empty($errors)) echo implode(PHP_EOL, $errors) . PHP_EOL;
|
||||
$command = "mysql -u%s -p%s -h%s -P%s --default-character-set=utf8 -D%s < %s";
|
||||
$execInsert = sprintf($command, $dbUser, $dbPWD, $dbHost, $dbPort, $dbName, $sqlPath);
|
||||
$this->execWithStderr($execInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行系统命令,并捕捉stderror
|
||||
* 执行系统命令,并捕捉错误。
|
||||
* exec command and catch stderror.
|
||||
*
|
||||
* @param string $cmd
|
||||
* @param string $stderr
|
||||
* @access private
|
||||
* @return string
|
||||
* @return void
|
||||
*/
|
||||
private function execWithStderr($cmd, &$stderr=null)
|
||||
private function execWithStderr($cmd)
|
||||
{
|
||||
$proc = proc_open($cmd, array(2 => array('pipe', 'w')), $pipes);
|
||||
$stderr = stream_get_contents($pipes[2]);
|
||||
|
||||
fclose($pipes[2]);
|
||||
proc_close($proc);
|
||||
|
||||
if(empty($stderr)) return;
|
||||
|
||||
$errors = explode(PHP_EOL, $stderr);
|
||||
$errors = array_filter($errors, function($error)
|
||||
{
|
||||
return !empty($error) && strpos($error, 'Using a password on the command line interface can be insecure') !== false;
|
||||
});
|
||||
|
||||
if(!empty($errors)) echo implode(PHP_EOL, $errors) . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user